Module Name:    src
Committed By:   bouyer
Date:           Sun Oct 16 14:56:04 UTC 2022

Modified Files:
        src/sys/arch/arm/sunxi [bouyer-sunxi-drm]: sunxi_hdmi.c sunxi_hdmireg.h

Log Message:
Work in progress: convert sunxi_hdmi to drm


To generate a diff of this commit:
cvs rdiff -u -r1.14.16.1 -r1.14.16.2 src/sys/arch/arm/sunxi/sunxi_hdmi.c
cvs rdiff -u -r1.1 -r1.1.32.1 src/sys/arch/arm/sunxi/sunxi_hdmireg.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/sunxi/sunxi_hdmi.c
diff -u src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.14.16.1 src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.14.16.2
--- src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.14.16.1	Sun Oct  2 10:37:12 2022
+++ src/sys/arch/arm/sunxi/sunxi_hdmi.c	Sun Oct 16 14:56:04 2022
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_hdmi.c,v 1.14.16.1 2022/10/02 10:37:12 bouyer Exp $ */
+/* $NetBSD: sunxi_hdmi.c,v 1.14.16.2 2022/10/16 14:56:04 bouyer Exp $ */
 
 /*-
  * Copyright (c) 2014 Jared D. McNeill <jmcne...@invisible.ca>
@@ -29,7 +29,7 @@
 #include "opt_ddb.h"
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c,v 1.14.16.1 2022/10/02 10:37:12 bouyer Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c,v 1.14.16.2 2022/10/16 14:56:04 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -52,6 +52,8 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c
 #include <arm/sunxi/sunxi_hdmireg.h>
 #include <arm/sunxi/sunxi_display.h>
 
+#include <arm/sunxi/sunxi_drm.h>
+
 #include <drm/drm_bridge.h>
 #include <drm/drm_connector.h>
 #include <drm/drm_drv.h>
@@ -62,6 +64,14 @@ enum sunxi_hdmi_type {
 	HDMI_A31,
 };
 
+struct sunxi_hdmi_softc;
+
+struct sunxi_hdmi_encoder {
+	struct drm_encoder      base;
+	struct sunxi_hdmi_softc *sc;
+	struct drm_display_mode curmode;
+};
+
 struct sunxi_hdmi_softc {
 	device_t sc_dev;
 	int sc_phandle;
@@ -75,16 +85,22 @@ struct sunxi_hdmi_softc {
 	void *sc_ih;
 	lwp_t *sc_thread;
 
-	struct i2c_controller sc_ic;
-	kmutex_t sc_exec_lock;
+	struct sunxi_hdmi_encoder sc_encoder;
+	struct drm_connector sc_connector;
+	struct i2c_adapter sc_i2c_adapt;
+
+	enum sc_display_mode {
+		DISPLAY_MODE_NONE = 0,
+		DISPLAY_MODE_DVI,
+		DISPLAY_MODE_HDMI
+	} sc_display_mode;
 
-	bool sc_display_connected;
+	kmutex_t sc_exec_lock;
 
 	kmutex_t sc_pwr_lock;
 	int	sc_pwr_refcount; /* reference who needs HDMI */
 
 	uint32_t sc_ver;
-	unsigned int sc_i2c_blklen;
 
 	struct fdt_device_ports sc_ports;
 	struct fdt_endpoint *sc_in_ep;
@@ -93,6 +109,56 @@ struct sunxi_hdmi_softc {
 	struct drm_display_mode sc_curmode;
 };
 
+#define to_sunxi_hdmi_encoder(x)	container_of(x, struct sunxi_hdmi_encoder, base)
+
+#define connector_to_hdmi(x)		container_of(x, struct sunxi_hdmi_softc, sc_connector)
+
+static void
+sunxi_hdmi_destroy(struct drm_encoder *encoder)
+{
+	drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs sunxi_hdmi_enc_funcs = {
+	.destroy = sunxi_hdmi_destroy,
+};
+
+static int sunxi_hdmi_atomic_check(struct drm_encoder *,
+    struct drm_crtc_state *, struct drm_connector_state *);
+static void sunxi_hdmi_disable(struct drm_encoder *);
+static void sunxi_hdmi_enable(struct drm_encoder *);
+static void sunxi_hdmi_mode_set(struct drm_encoder *,
+    struct drm_display_mode *, struct drm_display_mode *);
+static void sunxi_hdmi_mode_valid(struct drm_encoder *,
+    struct drm_display_mode *);
+
+static const struct drm_encoder_helper_funcs sunxi_hdmi_enc_helper_funcs = {
+	.atomic_check	= sunxi_hdmi_atomic_check,
+	.disable	= sunxi_hdmi_disable,
+	.enable	 	= sunxi_hdmi_enable,
+	.mode_set	= sunxi_hdmi_mode_set,
+	.mode_valid	= sunxi_hdmi_mode_valid,
+};
+
+static enum drm_connector_status sunxi_hdmi_connector_detect(
+    struct drm_connector *, bool);
+
+static const struct drm_connector_funcs sunxi_hdmi_connector_funcs = {
+	.detect			= sunxi_hdmi_connector_detect,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= drm_connector_cleanup,
+	.reset			= drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+};
+
+static int sunxi_hdmi_get_modes(struct drm_connector *);
+
+static const struct drm_connector_helper_funcs sunxi_hdmi_connector_helper_funcs
+ = {
+	 .get_modes      = sunxi_hdmi_get_modes,
+};
+
 #define HDMI_READ(sc, reg)			\
     bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
 #define HDMI_WRITE(sc, reg, val)		\
@@ -110,11 +176,11 @@ static const struct device_compatible_en
 static int	sunxi_hdmi_match(device_t, cfdata_t, void *);
 static void	sunxi_hdmi_attach(device_t, device_t, void *);
 static void	sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *);
-static int	sunxi_hdmi_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
-				   size_t, void *, size_t, int);
-static int	sunxi_hdmi_i2c_xfer(void *, i2c_addr_t, uint8_t, uint8_t,
-				   size_t, int, int);
-static int	sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *, int);
+static int	sunxi_hdmi_i2c_drm_xfer(struct i2c_adapter *,
+				   struct i2c_msg *, int);
+static int	sunxi_hdmi_i2c_xfer(struct sunxi_hdmi_softc *,
+					struct i2c_msg *);
+static int	sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *);
 
 static int	sunxi_hdmi_ep_activate(device_t, struct fdt_endpoint *, bool);
 static int	sunxi_hdmi_ep_enable(device_t, struct fdt_endpoint *, bool);
@@ -127,11 +193,10 @@ static u_int	sunxi_hdmi_get_display_mode
 					   const struct edid_info *);
 #endif
 static void	sunxi_hdmi_video_enable(struct sunxi_hdmi_softc *, bool);
+static void	sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *, int);
 #if 0
 static void	sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *,
 					const struct videomode *, u_int);
-static void	sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *,
-					const struct videomode *, u_int);
 static void	sunxi_hdmi_hpd(struct sunxi_hdmi_softc *);
 static void	sunxi_hdmi_thread(void *);
 #endif
@@ -168,6 +233,7 @@ sunxi_hdmi_attach(device_t parent, devic
 	sc->sc_dev = self;
 	sc->sc_phandle = phandle;
 	sc->sc_bst = faa->faa_bst;
+	sc->sc_encoder.sc = sc;
 
 	sc->sc_type =
 	    of_compatible_lookup(faa->faa_phandle, compat_data)->value;
@@ -209,7 +275,6 @@ sunxi_hdmi_attach(device_t parent, devic
 	aprint_normal(": HDMI %d.%d\n", vmaj, vmin);
 
 	sc->sc_ver = ver;
-	sc->sc_i2c_blklen = 16;
 
 	sc->sc_ports.dp_ep_activate = sunxi_hdmi_ep_activate;
 	sc->sc_ports.dp_ep_enable = sunxi_hdmi_ep_enable;
@@ -257,71 +322,83 @@ sunxi_hdmi_doreset(void)
 	}
 }
 
+static int
+sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
+    struct drm_crtc_state * crtc_state,
+    struct drm_connector_state *conn_state)
+{
+	if (crtc_state->mode.flags & DRM_MODE_FLAG_DBLCLK)
+		return -EINVAL;
+	return 0;
+}
+
 static void
-sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *sc)
+sunxi_hdmi_disable(struct drm_encoder *enc)
+{
+	struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc);
+	struct sunxi_hdmi_softc *sc = hdmi_encoder->sc;
+	sunxi_hdmi_video_enable(sc, false);
+}
+
+static void
+sunxi_hdmi_enable(struct drm_encoder *enc)
+{
+	struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc);
+	struct sunxi_hdmi_softc *sc = hdmi_encoder->sc;
+	struct drm_display_mode *mode = &enc->crtc->state->adjusted_mode;
+	sunxi_hdmi_video_enable(sc, true);
+}
+
+static uint32_t sunxi_hdmi_i2c_func(struct i2c_adapter *adap)
 {
-	struct i2c_controller *ic = &sc->sc_ic;
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
 
+static const struct i2c_algorithm sunxi_hdmi_i2c_algorithm = {
+        .master_xfer    = sunxi_hdmi_i2c_drm_xfer,
+	.functionality  = sunxi_hdmi_i2c_func, 
+};      
+
+static void
+sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *sc)
+{
 	mutex_init(&sc->sc_exec_lock, MUTEX_DEFAULT, IPL_NONE);
 
-	iic_tag_init(ic);
-	ic->ic_cookie = sc;
-	ic->ic_exec = sunxi_hdmi_i2c_exec;
+	sc->sc_i2c_adapt.owner = THIS_MODULE;
+	sc->sc_i2c_adapt.class = I2C_CLASS_DDC;
+	sc->sc_i2c_adapt.algo = &sunxi_hdmi_i2c_algorithm;
+	strlcpy(sc->sc_i2c_adapt.name, "sunxi_hdmi_i2c",
+	    sizeof(sc->sc_i2c_adapt.name));
 }
 
 static int
-sunxi_hdmi_i2c_exec(void *priv, i2c_op_t op, i2c_addr_t addr,
-    const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
-{
-	struct sunxi_hdmi_softc *sc = priv;
-	uint8_t *pbuf;
-	uint8_t block;
-	int resid;
-	off_t off;
-	int err;
+sunxi_hdmi_i2c_drm_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){
+	struct sunxi_hdmi_softc *sc = i2c_get_adapdata(adap);
+	int i, err;
 
 	mutex_enter(&sc->sc_exec_lock);
 
-	KASSERT(op == I2C_OP_READ_WITH_STOP);
-	KASSERT(addr == DDC_ADDR);
-	KASSERT(cmdlen > 0);
-	KASSERT(buf != NULL);
+	for (i = 0; i < num; i++) {
+		if (msg[i].len == 0 ||
+		    msg[i].len > SUNXI_HDMI_DDC_BYTE_COUNTER) 
+			return -EINVAL;
+	}
 
-	err = sunxi_hdmi_i2c_reset(sc, flags);
+	err = sunxi_hdmi_i2c_reset(sc);
 	if (err)
 		goto done;
 
-	block = *(const uint8_t *)cmdbuf;
-	off = (block & 1) ? 128 : 0;
-
-	pbuf = buf;
-	resid = len;
-	while (resid > 0) {
-		size_t blklen = uimin(resid, sc->sc_i2c_blklen);
-
-		err = sunxi_hdmi_i2c_xfer(sc, addr, block >> 1, off, blklen,
-		      SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOREAD, flags);
+	for (i = 0; i < num; i++) {
+		err = sunxi_hdmi_i2c_xfer(sc, &msg[i]);
 		if (err)
-			goto done;
-
-		if (HDMI_1_3_P(sc)) {
-			bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
-			    SUNXI_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen);
-		} else {
-			bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
-			    SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen);
-		}
-
+			break;
 #ifdef SUNXI_HDMI_DEBUG
-		printf("off=%d:", (int)off);
-		for (int i = 0; i < blklen; i++)
-			printf(" %02x", pbuf[i]);
+		printf("msg %d:", i);
+		for (int j = 0; j < msg[i].len; j++)
+			printf(" %02x", msg[i].buf[j]);
 		printf("\n");
 #endif
-
-		pbuf += blklen;
-		off += blklen;
-		resid -= blklen;
 	}
 
 done:
@@ -329,117 +406,212 @@ done:
 	return err;
 }
 
+#define SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK ( \
+		SUNXI_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OP | \
+		SUNXI_HDMI_DDC_INT_STATUS_RX_UNDERFLOW | \
+		SUNXI_HDMI_DDC_INT_STATUS_TX_OVERFLOW | \
+		SUNXI_HDMI_DDC_INT_STATUS_ARB_ERR | \
+		SUNXI_HDMI_DDC_INT_STATUS_ACK_ERR | \
+		SUNXI_HDMI_DDC_INT_STATUS_BUS_ERR)
+
 static int
-sunxi_hdmi_i2c_xfer_1_3(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
-    size_t len, int type, int flags)
+sunxi_hdmi_i2c_xfer_1_3(struct sunxi_hdmi_softc *sc, struct i2c_msg *msg)
 {
 	struct sunxi_hdmi_softc *sc = priv;
 	uint32_t val;
+	uint8_t *buf;
+	int len;
 	int retry;
 
 	val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
 	val &= ~SUNXI_HDMI_DDC_CTRL_FIFO_DIR;
+	if (msg->flags & I2C_M_RD) 
+		val |= SUNXI_HDMI_DDC_CTRL_FIFO_DIR_READ;
+	else
+		val |= SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
 	HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val);
 
-	val |= __SHIFTIN(block, SUNXI_HDMI_DDC_SLAVE_ADDR_0);
-	val |= __SHIFTIN(0x60, SUNXI_HDMI_DDC_SLAVE_ADDR_1);
-	val |= __SHIFTIN(reg, SUNXI_HDMI_DDC_SLAVE_ADDR_2);
-	val |= __SHIFTIN(addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3);
+	val = __SHIFTIN(msg->addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3);
 	HDMI_WRITE(sc, SUNXI_HDMI_DDC_SLAVE_ADDR_REG, val);
 
-	val = HDMI_READ(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG);
+	val = __SHIFTIN(0, SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH);
+	val |= __SHIFTIN(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX,
+	    SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH);
 	val |= SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR;
 	HDMI_WRITE(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG, val);
 
-	HDMI_WRITE(sc, SUNXI_HDMI_DDC_BYTE_COUNTER_REG, len);
-
-	HDMI_WRITE(sc, SUNXI_HDMI_DDC_COMMAND_REG, type);
+	retry = 20;
+	while (HDMI_READ(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG) & SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR) {
+		if (--retry == 0)
+			return -EIO;
+		delay(100);
+	}
+
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_BYTE_COUNTER_REG, msg->len);
+
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_COMMAND_REG,
+	    msg->flags & I2C_M_RD ?
+	    SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOREAD :
+	    SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOWRITE;
+
+	val = SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ |
+	       SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK |
+	       SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_INT_STATUS_REG, val);
 
 	val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
 	val |= SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START;
 	HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val);
 
-	retry = 1000;
-	while (--retry > 0) {
-		val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
-		if ((val & SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0)
-			break;
+	/* read data when some is available */
+	len = msg->len;
+	buf = msg->buf;
+	while (len > 0) {
+		int blklen = imin(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX, len);
+		retry = 0;
+		do {
+			val = HDMI_READ(sc, SUNXI_HDMI_DDC_INT_STATUS_REG);
+			if (++retry > 1000)
+				return -ETIMEOUT;
+		} while (val & (SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ |
+			      SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE |
+			      SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) == 0);
+		if (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) {
+			device_printf(sc->sc_dev,
+			    "xfer failed, status=%08x\n", val);
+			return -EIO;
+		}
+		if (msg->flags & I2C_M_RD)
+			bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen);
+		else 
+			bus_space_write_multi_1(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen);
+		buf += blklen;
+		len -= blklen;
+		HDMI_WRITE(sc, SUNXI_HDMI_DDC_INT_STATUS_REG,
+		    SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ);
+	}
+
+	retry = 0;
+	while (HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG) & SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START) {
+		if (++retry == 1000)
+			return EIO;
 		delay(1000);
 	}
-	if (retry == 0)
-		return ETIMEDOUT;
 
 	val = HDMI_READ(sc, SUNXI_HDMI_DDC_INT_STATUS_REG);
-	if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0) {
+	if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0 ||
+	    (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK)) {
 		device_printf(sc->sc_dev, "xfer failed, status=%08x\n", val);
-		return EIO;
+		return -EIO;
 	}
-
 	return 0;
 }
 
 static int
-sunxi_hdmi_i2c_xfer_1_4(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
-    size_t len, int type, int flags)
+sunxi_hdmi_i2c_xfer_1_4(struct sunxi_hdmi_softc *sc, struct i2c_msg *msg)
 {
-	struct sunxi_hdmi_softc *sc = priv;
 	uint32_t val;
+	uint8_t *buf;
+	int len;
 	int retry;
 
-	val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG);
+	val = __SHIFTIN(1, SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH);
+	val |= __SHIFTIN(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX,
+	    SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH);
 	val |= SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST;
 	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG, val);
+	retry = 0;
+	while (HDMI_READ(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG) &
+	    SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST) {
+		if (++rety > 2000)
+			return -EIO;
+		delay(100);
+	}
 
-	val = __SHIFTIN(block, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR);
-	val |= __SHIFTIN(0x60, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DDC_CMD);
-	val |= __SHIFTIN(reg, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_OFF_ADR);
-	val |= __SHIFTIN(addr, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DEV_ADR);
+	val = __SHIFTIN(msg->addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3);
 	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_REG, val);
 
-	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_COMMAND_REG,
-	    __SHIFTIN(len, SUNXI_A31_HDMI_DDC_COMMAND_DTC) |
-	    __SHIFTIN(type, SUNXI_A31_HDMI_DDC_COMMAND_CMD));
+	val = __SHIFTIN(msg->len, SUNXI_A31_HDMI_DDC_COMMAND_DTC);
+	val |= __SHIFTIN(msg->flags & I2C_M_RD ?
+		SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOREAD :
+		SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOWRITE);
+	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_COMMAND_REG, val,
+	    SUNXI_A31_HDMI_DDC_COMMAND_CMD));
+
+	val = SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ |
+	       SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK |
+	       SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
+	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG, val);
 
 	val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG);
 	val |= SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START;
 	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG, val);
 
+	/* read data when some is available */
+	len = msg->len;
+	buf = msg->buf;
+	while (len > 0) {
+		int blklen;
+		retry = 0;
+		do {
+			val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG);
+			if (++retry > 1000)
+				return -ETIMEOUT;
+		} while (val & (SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ |
+			      SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE |
+			      SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) == 0);
+		if (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) {
+			device_printf(sc->sc_dev,
+			    "xfer failed, status=%08x\n", val);
+			return -EIO;
+		}
+		if (msg->flags & I2C_M_RD) {
+			blklen =
+			    imin(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX - 1, len);
+			bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen);
+		} else {
+			blklen =
+			    imin(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX, len);
+			bus_space_write_multi_1(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen);
+		}
+		buf += blklen;
+		len -= blklen;
+		HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG,
+		    SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ);
+	}
+
 	retry = 1000;
-	while (--retry > 0) {
-		val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG);
-		if ((val & SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0)
-			break;
-		if (flags & I2C_F_POLL)
-			delay(1000);
-		else
-			kpause("hdmiddc", false, mstohz(10), &sc->sc_exec_lock);
+	while (HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG) & SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START) {
+		if (--retry == 0)
+			return -ETIMEDOUT;
+		delay(1000);
+	}
+	val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG);
+	if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0 ||
+	    (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK)) {
+		device_printf(sc->sc_dev, "xfer failed, status=%08x\n", val);
+		return -EIO;
 	}
-	if (retry == 0)
-		return ETIMEDOUT;
 
 	return 0;
 }
 
 static int
-sunxi_hdmi_i2c_xfer(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
-    size_t len, int type, int flags)
+sunxi_hdmi_i2c_xfer(struct sunxi_hdmi_softc *sc, struct i2c_msg *msg)
 {
-	struct sunxi_hdmi_softc *sc = priv;
-	int rv;
-
 	if (HDMI_1_3_P(sc)) {
-		rv = sunxi_hdmi_i2c_xfer_1_3(priv, addr, block, reg, len,
-		    type, flags);
+		return sunxi_hdmi_i2c_xfer_1_3(sc, msg);
 	} else {
-		rv = sunxi_hdmi_i2c_xfer_1_4(priv, addr, block, reg, len,
-		    type, flags);
+		return sunxi_hdmi_i2c_xfer_1_4(sc, msg);
 	}
-
-	return rv;
 }
 
 static int
-sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *sc, int flags)
+sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *sc)
 {
 	uint32_t hpd, ctrl;
 
@@ -491,6 +663,7 @@ sunxi_hdmi_ep_activate(device_t dev, str
 {
 	struct sunxi_hdmi_softc *sc = device_private(dev);
 	struct fdt_endpoint *in_ep, *out_ep;
+	struct drm_crtc *crtc;
 	int error;
 
 	/* our input is activated by tcon, we activate our output */
@@ -528,10 +701,36 @@ sunxi_hdmi_ep_activate(device_t dev, str
 		aprint_error_dev(dev, "no output endpoint\n");
 		return ENODEV;
 	}
+	sc->sc_in_rep = fdt_endpoint_remote(ep);
+	KASSERT(sc->sc_in_rep != NULL);
+
+	if (fdt_endpoint_type(sc->sc_in_rep) != EP_DRM_ENCODER) {
+		device_printf(sc->sc_dev, ": in endpoint %d wrong type\n",
+		    fdt_endpoint_type(sc->sc_in_rep));
+		panic("hdmi fdt_endpoint_type");
+	}
+	crtc = sunxi_tcon_get_crtc(fdt_endpoint_device(sc->sc_in_rep));
+
+	sc->sc_encoder.sc = sc;
+	sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc);
+	drm_encoder_helper_add(&sc->sc_encoder.base,
+	    &sunxi_hdmi_enc_helper_funcs);
+	error = drm_encoder_init(crtc->dev, &sc->sc_encoder.base,
+	    sunxi_hdmi_enc_funcs, DRM_MODE_ENCODER_TMDS, NULL);
+	if (error)
+		return -error;
+	
+	drm_connector_helper_add(&sc->sc_connector,
+	    &sunxi_hdmi_connector_helper_funcs);
+	error = drm_connector_init_with_ddc(crtc->dev, &sc->sc_connector,
+	    DRM_MODE_CONNECTOR_HDMIA, &sc->sc_i2c_adapt);
+	if (error)
+		return -error;
+	drm_connector_attach_encoder(&sc->sc_connector, &sc->sc_encoder.base);
+
 	error = fdt_endpoint_activate(out_ep, activate);
 	if (error == 0) {
 		sc->sc_in_ep = ep;
-		sc->sc_in_rep = fdt_endpoint_remote(ep);
 		sc->sc_out_ep = out_ep;
 		sunxi_hdmi_do_enable(sc);
 		return 0;
@@ -819,39 +1018,12 @@ sunxi_hdmi_video_enable(struct sunxi_hdm
 #endif
 }
 
-#if 0
-static void
-sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *sc,
-    const struct videomode *mode, u_int display_mode)
+static int
+hdmi_clk_find(struct sunxi_hdmi_softc *sc, int rate, int *div, int *dbl)
 {
-	uint32_t val;
-	const u_int dblscan_p = !!(mode->flags & VID_DBLSCAN);
-	const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
-	const u_int phsync_p = !!(mode->flags & VID_PHSYNC);
-	const u_int pvsync_p = !!(mode->flags & VID_PVSYNC);
-	const u_int hfp = mode->hsync_start - mode->hdisplay;
-	const u_int hspw = mode->hsync_end - mode->hsync_start;
-	const u_int hbp = mode->htotal - mode->hsync_start;
-	const u_int vfp = mode->vsync_start - mode->vdisplay;
-	const u_int vspw = mode->vsync_end - mode->vsync_start;
-	const u_int vbp = mode->vtotal - mode->vsync_start;
-	struct clk *clk_pll;
+	int best_diff;
 	int parent_rate;
-	int best_div, best_dbl, best_diff;
-	int target_rate = mode->dot_clock * 1000;
-
-#ifdef SUNXI_HDMI_DEBUG
-	device_printf(sc->sc_dev,
-	    "dblscan %d, interlace %d, phsync %d, pvsync %d\n",
-	    dblscan_p, interlace_p, phsync_p, pvsync_p);
-	device_printf(sc->sc_dev, "h: %u %u %u %u\n",
-	    mode->hdisplay, hbp, hfp, hspw);
-	device_printf(sc->sc_dev, "v: %u %u %u %u\n",
-	    mode->vdisplay, vbp, vfp, vspw);
-#endif
-
-	HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, 0xffffffff);
-
+	struct clk *clk_pll;
 	/* assume tcon0 uses pll3, tcon1 uses pll7 */
 	switch(fdt_endpoint_index(sc->sc_in_ep)) {
 	case 0:
@@ -865,7 +1037,7 @@ sunxi_hdmi_set_videomode(struct sunxi_hd
 	}
 	parent_rate = clk_get_rate(clk_pll);
 	KASSERT(parent_rate > 0);
-	best_div = best_dbl = 0;
+	*div = *dbl = 0;
 	best_diff = INT_MAX;
 	for (int d = 2; d > 0 && best_diff != 0; d--) {
 		for (int m = 1; m <= 16 && best_diff != 0; m++) {
@@ -873,20 +1045,56 @@ sunxi_hdmi_set_videomode(struct sunxi_hd
 			int diff = abs(target_rate - cur_rate);
 			if (diff >= 0 && diff < best_diff) {
 				best_diff = diff;
-				best_div = m;
-				best_dbl = d;
+				*div = m;
+				*dbl = d;
 			}
 		}
 	}
 
 #ifdef SUNXI_HDMI_DEBUG
 	device_printf(sc->sc_dev, "parent rate: %d\n", parent_rate);
-	device_printf(sc->sc_dev, "dot_clock: %d\n", mode->dot_clock);
+	device_printf(sc->sc_dev, "crtc_clock: %d\n", mode->crtc_clock);
 	device_printf(sc->sc_dev, "clkdiv: %d\n", best_div);
 	device_printf(sc->sc_dev, "clkdbl: %c\n", (best_dbl == 1) ? 'Y' : 'N');
 #endif
+	return best_diff;
+}
+
+static void
+sunxi_hdmi_mode_set(struct drm_encoder *enc,
+    struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc);
+	struct sunxi_hdmi_softc *sc = hdmi_encoder->sc;
+	uint32_t val;
+	const u_int dblscan_p = !!(mode->flags & DRM_MODE_FLAG_DBLSCAN);
+	const u_int interlace_p = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
+	const u_int phsync_p = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+	const u_int pvsync_p = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+	const u_int hfp = mode->hsync_start - mode->hdisplay;
+	const u_int hspw = mode->hsync_end - mode->hsync_start;
+	const u_int hbp = mode->htotal - mode->hsync_start;
+	const u_int vfp = mode->vsync_start - mode->vdisplay;
+	const u_int vspw = mode->vsync_end - mode->vsync_start;
+	const u_int vbp = mode->vtotal - mode->vsync_start;
+	int div, dbl, diff;
+	int target_rate = mode->crtc_clock * 1000;
+
+#ifdef SUNXI_HDMI_DEBUG
+	device_printf(sc->sc_dev,
+	    "dblscan %d, interlace %d, phsync %d, pvsync %d\n",
+	    dblscan_p, interlace_p, phsync_p, pvsync_p);
+	device_printf(sc->sc_dev, "h: %u %u %u %u\n",
+	    mode->hdisplay, hbp, hfp, hspw);
+	device_printf(sc->sc_dev, "v: %u %u %u %u\n",
+	    mode->vdisplay, vbp, vfp, vspw);
+#endif
+
+	HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, 0xffffffff);
 
-	if (best_div == 0) {
+	diff = hdmi_clk_find(target_rate, &div, &dbl);
+
+	if (div == 0) {
 		device_printf(sc->sc_dev, "ERROR: TCON clk not configured\n");
 		return;
 	}
@@ -896,14 +1104,14 @@ sunxi_hdmi_set_videomode(struct sunxi_hd
 		pad_ctrl0 = 0x7e8000ff;
 		pad_ctrl1 = 0x01ded030;
 		pll_ctrl = 0xba48a308;
-		pll_ctrl |= __SHIFTIN(best_div - 1, SUNXI_HDMI_PLL_CTRL_PREDIV);
+		pll_ctrl |= __SHIFTIN(div - 1, SUNXI_HDMI_PLL_CTRL_PREDIV);
 	} else {
 		pad_ctrl0 = 0xfe800000;
 		pad_ctrl1 = 0x00d8c830;
 		pll_ctrl = 0xfa4ef708;
-		pll_ctrl |= __SHIFTIN(best_div, SUNXI_HDMI_PLL_CTRL_PREDIV);
+		pll_ctrl |= __SHIFTIN(div, SUNXI_HDMI_PLL_CTRL_PREDIV);
 	}
-	if (best_dbl == 2)
+	if (dbl == 2)
 		pad_ctrl1 |= 0x40;
 
 	HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, pad_ctrl0);
@@ -923,7 +1131,7 @@ sunxi_hdmi_set_videomode(struct sunxi_hd
 
 	val = HDMI_READ(sc, SUNXI_HDMI_VID_CTRL_REG);
 	val &= ~SUNXI_HDMI_VID_CTRL_HDMI_MODE;
-	if (display_mode == DISPLAY_MODE_DVI) {
+	if (sc->sc_display_mode == DISPLAY_MODE_DVI) {
 		val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_HDMI_MODE_DVI,
 				 SUNXI_HDMI_VID_CTRL_HDMI_MODE);
 	} else {
@@ -982,11 +1190,28 @@ sunxi_hdmi_set_videomode(struct sunxi_hd
 	HDMI_WRITE(sc, SUNXI_HDMI_GP_PKT1_REG, 0);
 	HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL0_REG, 0x00005321);
 	HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL1_REG, 0x0000000f);
+	sunxi_hdmi_set_audiomode(sc, mode->crtc_clock);
+}
+
+static void
+sunxi_hdmi_mode_valid(struct drm_encoder *enc,
+    struct drm_display_mode *mode)
+{
+	struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc);
+	struct sunxi_hdmi_softc *sc = hdmi_encoder->sc;
+	int diff, div, dbl;
+	int target_rate = mode->clock * 10000;
+
+	if (target_rate > 165000000) /* max pixelclock for HDMI <= 1.2 */
+		return MODE_CLOCK_HIGH;
+	diff = hdmi_clk_find(target_rate, &div, &dbl);
+	if (diff >= target_rate / 200) /* HDMI allows max 0.5% */
+		return MODE_NOCLOCK;
+	return MODE_OK;
 }
 
 static void
-sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *sc,
-    const struct videomode *mode, u_int display_mode)
+sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *sc, int dot_clock)
 {
 	uint32_t cts, n, val;
 
@@ -1004,7 +1229,7 @@ sunxi_hdmi_set_audiomode(struct sunxi_hd
 	} while (val & SUNXI_HDMI_AUD_CTRL_RST);
 
 	/* No audio support in DVI mode */
-	if (display_mode != DISPLAY_MODE_HDMI) {
+	if (sc->sc_display_mode != DISPLAY_MODE_HDMI) {
 		return;
 	}
 
@@ -1036,7 +1261,7 @@ sunxi_hdmi_set_audiomode(struct sunxi_hd
 
 	/* Clock setup */
 	n = 6144;	/* 48 kHz */
-	cts = ((mode->dot_clock * 10) * (n / 128)) / 480;
+	cts = ((dot_clock * 10) * (n / 128)) / 480;
 	HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTS_REG, cts);
 	HDMI_WRITE(sc, SUNXI_HDMI_AUD_N_REG, n);
 
@@ -1063,48 +1288,42 @@ sunxi_hdmi_set_audiomode(struct sunxi_hd
 	sunxi_hdmi_dump_regs();
 #endif
 }
-#endif
 
-#if 0
-static void
-sunxi_hdmi_hpd(struct sunxi_hdmi_softc *sc)
+static enum drm_connector_status   
+sunxi_hdmi_connector_detect(struct drm_connector *connector, bool force)
 {
-	uint32_t hpd = HDMI_READ(sc, SUNXI_HDMI_HPD_REG);
-	bool con = !!(hpd & SUNXI_HDMI_HPD_HOTPLUG_DET);
-
-	KASSERT(mutex_owned(&sc->sc_pwr_lock));
-	if (sc->sc_display_connected == con)
-		return;
+	struct sunxi_hdmi_softc *sc = connector_to_hdmi(connector);
 
-	if (con) {
-		device_printf(sc->sc_dev, "display connected\n");
-		sc->sc_pwr_refcount  = 1;
-		sunxi_hdmi_read_edid(sc);
-	} else {
-		device_printf(sc->sc_dev, "display disconnected\n");
-		sc->sc_pwr_refcount = 0;
-		sunxi_hdmi_video_enable(sc, false);
-		fdt_endpoint_enable(sc->sc_in_ep, false);
-		sunxi_tcon1_set_videomode(
-		    fdt_endpoint_device(sc->sc_in_rep), NULL);
+	if (HDMI_READ(sc, SUNXI_HDMI_HPD_REG) & SUNXI_HDMI_HPD_HOTPLUG_DET) {
+		return connector_status_connected;
 	}
-
-	sc->sc_display_connected = con;
+	sc->sc_display_mode = DISPLAY_MODE_NONE;
+	return connector_status_disconnected;
 }
 
-static void
-sunxi_hdmi_thread(void *priv)
+static int
+sunxi_hdmi_get_modes(struct drm_connector * connector)
 {
-	struct sunxi_hdmi_softc *sc = priv;
+	struct sunxi_hdmi_softc *sc = connector_to_hdmi(connector);
+	struct edid *edid;
+	int ret;
+
+	edid = drm_get_edid(connector, &sc->sc_i2c_adapt);
+	if (!edid) {
+		sc->sc_display_mode = DISPLAY_MODE_NONE;
+		return 0;
+	}
 
-	for (;;) {
-		mutex_enter(&sc->sc_pwr_lock);
-		sunxi_hdmi_hpd(sc);
-		mutex_exit(&sc->sc_pwr_lock);
-		kpause("hdmihotplug", false, mstohz(1000), NULL);
+	if (drm_detect_hdmi_monitor(edid)) {
+		sc->sc_display_mode = DISPLAY_MODE_HDMI;
+	} else {
+		sc->sc_display_mode = DISPLAY_MODE_DVI;
 	}
+	drm_connector_update_edid_property(connector, edid);
+	ret = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+	return ret;
 }
-#endif
 
 static int
 sunxi_hdmi_poweron(struct sunxi_hdmi_softc *sc, bool enable)

Index: src/sys/arch/arm/sunxi/sunxi_hdmireg.h
diff -u src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1 src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1.32.1
--- src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_hdmireg.h	Sun Oct 16 14:56:04 2022
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_hdmireg.h,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
+/* $NetBSD: sunxi_hdmireg.h,v 1.1.32.1 2022/10/16 14:56:04 bouyer Exp $ */
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -214,7 +214,7 @@
 #define SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START __BIT(30)
 #define SUNXI_HDMI_DDC_CTRL_FIFO_DIR	__BIT(8)
 #define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_READ	0
-#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE	1
+#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE	__BIT(8)
 #define SUNXI_HDMI_DDC_CTRL_SW_RST	__BIT(0)
 
 #define SUNXI_HDMI_DDC_SLAVE_ADDR_0	__BITS(31,24)
@@ -236,6 +236,9 @@
 #define SUNXI_HDMI_DDC_FIFO_CTRL_REQUEST_EN __BIT(8)
 #define SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH __BITS(7,4)
 #define SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH __BITS(3,0)
+#define SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX __BITS(3,0)
+
+#define SUNXI_HDMI_DDC_BYTE_COUNTER	__BITS(9, 0)
 
 #define SUNXI_HDMI_DDC_FIFO_STATUS_REQ_READY	__BIT(7)
 #define SUNXI_HDMI_DDC_FIFO_STATUS_FULL		__BIT(6)

Reply via email to