Module Name:    src
Committed By:   jmcneill
Date:           Mon Nov 17 00:49:33 UTC 2014

Modified Files:
        src/sys/arch/arm/allwinner: awin_hdmi.c

Log Message:
Add support for DVI displays. Detect HDMI vs DVI mode by looking for a
CEA-861-D extension block in the EDID, and then searching this block for
an HDMI vendor-specific data block (HDMI VSDB).


To generate a diff of this commit:
cvs rdiff -u -r1.12 -r1.13 src/sys/arch/arm/allwinner/awin_hdmi.c

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/allwinner/awin_hdmi.c
diff -u src/sys/arch/arm/allwinner/awin_hdmi.c:1.12 src/sys/arch/arm/allwinner/awin_hdmi.c:1.13
--- src/sys/arch/arm/allwinner/awin_hdmi.c:1.12	Fri Nov 14 01:05:57 2014
+++ src/sys/arch/arm/allwinner/awin_hdmi.c	Mon Nov 17 00:49:32 2014
@@ -1,4 +1,4 @@
-/* $NetBSD: awin_hdmi.c,v 1.12 2014/11/14 01:05:57 jmcneill Exp $ */
+/* $NetBSD: awin_hdmi.c,v 1.13 2014/11/17 00:49:32 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2014 Jared D. McNeill <jmcne...@invisible.ca>
@@ -32,7 +32,7 @@
 #define AWIN_HDMI_PLL	3	/* PLL7 or PLL3 */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: awin_hdmi.c,v 1.12 2014/11/14 01:05:57 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: awin_hdmi.c,v 1.13 2014/11/17 00:49:32 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -65,6 +65,11 @@ struct awin_hdmi_softc {
 
 	bool sc_connected;
 
+	u_int sc_display_mode;
+#define DISPLAY_MODE_AUTO	0
+#define DISPLAY_MODE_HDMI	1
+#define DISPLAY_MODE_DVI	2
+
 	uint32_t sc_ver;
 	unsigned int sc_i2c_blklen;
 };
@@ -85,17 +90,19 @@ static int	awin_hdmi_i2c_acquire_bus(voi
 static void	awin_hdmi_i2c_release_bus(void *, int);
 static int	awin_hdmi_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
 				   size_t, void *, size_t, int);
-static int	awin_hdmi_i2c_xfer(void *, i2c_addr_t, uint8_t,
+static int	awin_hdmi_i2c_xfer(void *, i2c_addr_t, uint8_t, uint8_t,
 				   size_t, int, int);
 static int	awin_hdmi_i2c_reset(struct awin_hdmi_softc *, int);
 
 static void	awin_hdmi_enable(struct awin_hdmi_softc *);
 static void	awin_hdmi_read_edid(struct awin_hdmi_softc *);
+static u_int	awin_hdmi_get_display_mode(struct awin_hdmi_softc *,
+					   const struct edid_info *);
 static void	awin_hdmi_video_enable(struct awin_hdmi_softc *, bool);
 static void	awin_hdmi_set_videomode(struct awin_hdmi_softc *,
-					const struct videomode *);
+					const struct videomode *, u_int);
 static void	awin_hdmi_set_audiomode(struct awin_hdmi_softc *,
-					const struct videomode *);
+					const struct videomode *, u_int);
 static void	awin_hdmi_hpd(struct awin_hdmi_softc *);
 static void	awin_hdmi_thread(void *);
 #if 0
@@ -127,6 +134,7 @@ awin_hdmi_attach(device_t parent, device
 	struct awin_hdmi_softc *sc = device_private(self);
 	struct awinio_attach_args * const aio = aux;
 	const struct awin_locators * const loc = &aio->aio_loc;
+	prop_dictionary_t cfg = device_properties(self);
 	uint32_t ver, clk;
 
 	sc->sc_dev = self;
@@ -172,6 +180,15 @@ awin_hdmi_attach(device_t parent, device
 	sc->sc_ver = ver;
 	sc->sc_i2c_blklen = 16;
 
+	const char *display_mode = NULL;
+	prop_dictionary_get_cstring_nocopy(cfg, "display-mode", &display_mode);
+	if (display_mode) {
+		if (strcasecmp(display_mode, "hdmi") == 0)
+			sc->sc_display_mode = DISPLAY_MODE_HDMI;
+		else if (strcasecmp(display_mode, "dvi") == 0)
+			sc->sc_display_mode = DISPLAY_MODE_DVI;
+	}
+
 #if 0
 	sc->sc_ih = intr_establish(loc->loc_intr, IPL_SCHED, IST_LEVEL,
 	    awin_hdmi_intr, sc);
@@ -237,6 +254,7 @@ awin_hdmi_i2c_exec(void *priv, i2c_op_t 
 {
 	struct awin_hdmi_softc *sc = priv;
 	uint8_t *pbuf;
+	uint8_t block;
 	int resid;
 	off_t off;
 	int err;
@@ -251,14 +269,15 @@ awin_hdmi_i2c_exec(void *priv, i2c_op_t 
 	if (err)
 		goto done;
 
-	off = *(const uint8_t *)cmdbuf;
+	block = *(const uint8_t *)cmdbuf;
+	off = (block & 1) ? 128 : 0;
 
 	pbuf = buf;
 	resid = len;
 	while (resid > 0) {
 		size_t blklen = min(resid, sc->sc_i2c_blklen);
 
-		err = awin_hdmi_i2c_xfer(sc, addr, off, blklen,
+		err = awin_hdmi_i2c_xfer(sc, addr, block >> 1, off, blklen,
 		      AWIN_HDMI_DDC_COMMAND_ACCESS_CMD_EOREAD, flags);
 		if (err)
 			goto done;
@@ -288,7 +307,7 @@ done:
 }
 
 static int
-awin_hdmi_i2c_xfer_1_3(void *priv, i2c_addr_t addr, uint8_t reg,
+awin_hdmi_i2c_xfer_1_3(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
     size_t len, int type, int flags)
 {
 	struct awin_hdmi_softc *sc = priv;
@@ -299,7 +318,8 @@ awin_hdmi_i2c_xfer_1_3(void *priv, i2c_a
 	val &= ~AWIN_HDMI_DDC_CTRL_FIFO_DIR;
 	HDMI_WRITE(sc, AWIN_HDMI_DDC_CTRL_REG, val);
 
-	val = __SHIFTIN(0x60, AWIN_HDMI_DDC_SLAVE_ADDR_1);
+	val |= __SHIFTIN(block, AWIN_HDMI_DDC_SLAVE_ADDR_0);
+	val |= __SHIFTIN(0x60, AWIN_HDMI_DDC_SLAVE_ADDR_1);
 	val |= __SHIFTIN(reg, AWIN_HDMI_DDC_SLAVE_ADDR_2);
 	val |= __SHIFTIN(addr, AWIN_HDMI_DDC_SLAVE_ADDR_3);
 	HDMI_WRITE(sc, AWIN_HDMI_DDC_SLAVE_ADDR_REG, val);
@@ -336,7 +356,7 @@ awin_hdmi_i2c_xfer_1_3(void *priv, i2c_a
 }
 
 static int
-awin_hdmi_i2c_xfer_1_4(void *priv, i2c_addr_t addr, uint8_t reg,
+awin_hdmi_i2c_xfer_1_4(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
     size_t len, int type, int flags)
 {
 	struct awin_hdmi_softc *sc = priv;
@@ -347,7 +367,7 @@ awin_hdmi_i2c_xfer_1_4(void *priv, i2c_a
 	val |= AWIN_A31_HDMI_DDC_FIFO_CTRL_RST;
 	HDMI_WRITE(sc, AWIN_A31_HDMI_DDC_FIFO_CTRL_REG, val);
 
-	val = __SHIFTIN(0, AWIN_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR);
+	val = __SHIFTIN(block, AWIN_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR);
 	val |= __SHIFTIN(0x60, AWIN_A31_HDMI_DDC_SLAVE_ADDR_DDC_CMD);
 	val |= __SHIFTIN(reg, AWIN_A31_HDMI_DDC_SLAVE_ADDR_OFF_ADR);
 	val |= __SHIFTIN(addr, AWIN_A31_HDMI_DDC_SLAVE_ADDR_DEV_ADR);
@@ -378,16 +398,18 @@ awin_hdmi_i2c_xfer_1_4(void *priv, i2c_a
 }
 
 static int
-awin_hdmi_i2c_xfer(void *priv, i2c_addr_t addr, uint8_t reg,
+awin_hdmi_i2c_xfer(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
     size_t len, int type, int flags)
 {
 	struct awin_hdmi_softc *sc = priv;
 	int rv;
 
 	if (HDMI_1_3_P(sc)) {
-		rv = awin_hdmi_i2c_xfer_1_3(priv, addr, reg, len, type, flags);
+		rv = awin_hdmi_i2c_xfer_1_3(priv, addr, block, reg, len,
+		    type, flags);
 	} else {
-		rv = awin_hdmi_i2c_xfer_1_4(priv, addr, reg, len, type, flags);
+		rv = awin_hdmi_i2c_xfer_1_4(priv, addr, block, reg, len,
+		    type, flags);
 	}
 
 	return rv;
@@ -468,12 +490,13 @@ awin_hdmi_read_edid(struct awin_hdmi_sof
 	char edid[128];
 	struct edid_info ei;
 	int retry = 4;
+	u_int display_mode;
 
 	memset(edid, 0, sizeof(edid));
 	memset(&ei, 0, sizeof(ei));
 
 	while (--retry > 0) {
-		if (ddc_read_edid(&sc->sc_ic, edid, sizeof(edid)) == 0)
+		if (!ddc_read_edid_block(&sc->sc_ic, edid, sizeof(edid), 0))
 			break;
 	}
 	if (retry == 0) {
@@ -489,6 +512,16 @@ awin_hdmi_read_edid(struct awin_hdmi_sof
 #endif
 	}
 
+	if (sc->sc_display_mode == DISPLAY_MODE_AUTO)
+		display_mode = awin_hdmi_get_display_mode(sc, &ei);
+	else
+		display_mode = sc->sc_display_mode;
+
+	const char *forced = sc->sc_display_mode == DISPLAY_MODE_AUTO ?
+	    "auto-detected" : "forced";
+	device_printf(sc->sc_dev, "%s mode (%s)\n",
+	    display_mode == DISPLAY_MODE_HDMI ? "HDMI" : "DVI", forced);
+
 	mode = ei.edid_preferred_mode;
 	if (mode == NULL)
 		mode = pick_mode_by_ref(640, 480, 60);
@@ -500,8 +533,8 @@ awin_hdmi_read_edid(struct awin_hdmi_sof
 
 		awin_debe_set_videomode(mode);
 		awin_tcon_set_videomode(mode);
-		awin_hdmi_set_videomode(sc, mode);
-		awin_hdmi_set_audiomode(sc, mode);
+		awin_hdmi_set_videomode(sc, mode, display_mode);
+		awin_hdmi_set_audiomode(sc, mode, display_mode);
 		awin_debe_enable(true);
 		delay(20000);
 		awin_tcon_enable(true);
@@ -510,6 +543,90 @@ awin_hdmi_read_edid(struct awin_hdmi_sof
 	}
 }
 
+static u_int
+awin_hdmi_get_display_mode(struct awin_hdmi_softc *sc,
+    const struct edid_info *ei)
+{
+	char edid[128];
+	bool found_hdmi = false;
+	unsigned int n, p;
+
+	/*
+	 * Scan through extension blocks, looking for a CEA-861-D v3
+	 * block. If an HDMI Vendor-Specific Data Block (HDMI VSDB) is
+	 * found in that, assume HDMI mode.
+	 */
+	for (n = 1; n <= MIN(ei->edid_ext_block_count, 4); n++) {
+		if (ddc_read_edid_block(&sc->sc_ic, edid, sizeof(edid), n)) {
+#ifdef AWIN_HDMI_DEBUG
+			device_printf(sc->sc_dev,
+			    "Failed to read EDID block %d\n", n);
+#endif
+			break;
+		}
+
+#ifdef AWIN_HDMI_DEBUG
+		device_printf(sc->sc_dev, "EDID block #%d:\n", n);
+#endif
+
+		const uint8_t tag = edid[0];
+		const uint8_t rev = edid[1];
+		const uint8_t off = edid[2];
+
+#ifdef AWIN_HDMI_DEBUG
+		device_printf(sc->sc_dev, "  Tag %d, Revision %d, Offset %d\n",
+		    tag, rev, off);
+		device_printf(sc->sc_dev, "  Flags: 0x%02x\n", edid[3]);
+#endif
+
+		/* We are looking for a CEA-861-D tag (02h) with revision 3 */
+		if (tag != 0x02 || rev != 3)
+			continue;
+		/*
+		 * CEA data block collection starts at byte 4, so the
+		 * DTD blocks must start after it.
+		 */
+		if (off <= 4)
+			continue;
+
+		/* Parse the CEA data blocks */
+		for (p = 4; p < off;) {
+			const uint8_t btag = (edid[p] >> 5) & 0x7;
+			const uint8_t blen = edid[p] & 0x1f;
+
+#ifdef AWIN_HDMI_DEBUG
+			device_printf(sc->sc_dev, "  CEA data block @ %d\n", p);
+			device_printf(sc->sc_dev, "    Tag %d, Length %d\n",
+			    btag, blen);
+#endif
+
+			/* Make sure the length is sane */
+			if (p + blen + 1 > off)
+				break;
+			/* Looking for a VSDB tag */
+			if (btag != 3)
+				goto next_block;
+			/* HDMI VSDB is at least 5 bytes long */
+			if (blen < 5)
+				goto next_block;
+
+#ifdef AWIN_HDMI_DEBUG
+			device_printf(sc->sc_dev, "    ID: %02x%02x%02x\n",
+			    edid[p + 1], edid[p + 2], edid[p + 3]);
+#endif
+
+			/* HDMI 24-bit IEEE registration ID is 0x000C03 */
+			if (memcmp(&edid[p + 1], "\x03\x0c\x00", 3) == 0)
+				found_hdmi = true;
+
+next_block:
+			p += (1 + blen);
+		}
+	}
+
+	return found_hdmi ? DISPLAY_MODE_HDMI : DISPLAY_MODE_DVI;
+}
+
 static void
 awin_hdmi_video_enable(struct awin_hdmi_softc *sc, bool enable)
 {
@@ -538,7 +655,7 @@ awin_hdmi_video_enable(struct awin_hdmi_
 
 static void
 awin_hdmi_set_videomode(struct awin_hdmi_softc *sc,
-    const struct videomode *mode)
+    const struct videomode *mode, u_int display_mode)
 {
 	uint32_t val;
 	const u_int dblscan_p = !!(mode->flags & VID_DBLSCAN);
@@ -603,8 +720,14 @@ awin_hdmi_set_videomode(struct awin_hdmi
 #endif
 
 	val = HDMI_READ(sc, AWIN_HDMI_VID_CTRL_REG);
-	val |= __SHIFTIN(AWIN_HDMI_VID_CTRL_HDMI_MODE_HDMI,
-			 AWIN_HDMI_VID_CTRL_HDMI_MODE);
+	val &= ~AWIN_HDMI_VID_CTRL_HDMI_MODE;
+	if (display_mode == DISPLAY_MODE_DVI) {
+		val |= __SHIFTIN(AWIN_HDMI_VID_CTRL_HDMI_MODE_DVI,
+				 AWIN_HDMI_VID_CTRL_HDMI_MODE);
+	} else {
+		val |= __SHIFTIN(AWIN_HDMI_VID_CTRL_HDMI_MODE_HDMI,
+				 AWIN_HDMI_VID_CTRL_HDMI_MODE);
+	}
 	val &= ~AWIN_HDMI_VID_CTRL_OUTPUT_FMT;
 	if (dblscan_p) {
 		val |= __SHIFTIN(AWIN_HDMI_VID_CTRL_REPEATER_SEL_2X,
@@ -658,7 +781,7 @@ awin_hdmi_set_videomode(struct awin_hdmi
 
 static void
 awin_hdmi_set_audiomode(struct awin_hdmi_softc *sc,
-    const struct videomode *mode)
+    const struct videomode *mode, u_int display_mode)
 {
 	uint32_t cts, n, val;
 
@@ -675,6 +798,11 @@ awin_hdmi_set_audiomode(struct awin_hdmi
 		val = HDMI_READ(sc, AWIN_HDMI_AUD_CTRL_REG);
 	} while (val & AWIN_HDMI_AUD_CTRL_RST);
 
+	/* No audio support in DVI mode */
+	if (display_mode != DISPLAY_MODE_HDMI) {
+		return;
+	}
+
 	/* DMA & FIFO control */
 	val = HDMI_READ(sc, AWIN_HDMI_ADMA_CTRL_REG);
 	if (awin_chip_id() == AWIN_CHIP_ID_A31) {

Reply via email to