Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 29 Dec 2016 14:08:24 +0000 (UTC)
From:      Jared McNeill <jmcneill@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r310777 - in head/sys: arm/freescale/imx dev/hdmi
Message-ID:  <201612291408.uBTE8O73030217@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: jmcneill
Date: Thu Dec 29 14:08:24 2016
New Revision: 310777
URL: https://svnweb.freebsd.org/changeset/base/310777

Log:
  Add support for audio on I2S based DesignWare HDMI controllers.
  
  Relnotes:	yes

Modified:
  head/sys/arm/freescale/imx/imx6_hdmi.c
  head/sys/dev/hdmi/dwc_hdmi.c
  head/sys/dev/hdmi/dwc_hdmi.h
  head/sys/dev/hdmi/dwc_hdmi_fdt.c
  head/sys/dev/hdmi/dwc_hdmireg.h

Modified: head/sys/arm/freescale/imx/imx6_hdmi.c
==============================================================================
--- head/sys/arm/freescale/imx/imx6_hdmi.c	Thu Dec 29 14:00:10 2016	(r310776)
+++ head/sys/arm/freescale/imx/imx6_hdmi.c	Thu Dec 29 14:08:24 2016	(r310777)
@@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$");
 #include <machine/bus.h>
 
 #include <dev/videomode/videomode.h>
+#include <dev/videomode/edidvar.h>
 
 #include <arm/freescale/imx/imx_ccmvar.h>
 #include <arm/freescale/imx/imx_iomuxvar.h>

Modified: head/sys/dev/hdmi/dwc_hdmi.c
==============================================================================
--- head/sys/dev/hdmi/dwc_hdmi.c	Thu Dec 29 14:00:10 2016	(r310776)
+++ head/sys/dev/hdmi/dwc_hdmi.c	Thu Dec 29 14:08:24 2016	(r310777)
@@ -53,8 +53,23 @@ __FBSDID("$FreeBSD$");
 #include "hdmi_if.h"
 
 #define	I2C_DDC_ADDR	(0x50 << 1)
+#define	I2C_DDC_SEGADDR	(0x30 << 1)
 #define	EDID_LENGTH	0x80
 
+#define	EXT_TAG			0x00
+#define	CEA_TAG_ID		0x02
+#define	CEA_DTD			0x03
+#define	DTD_BASIC_AUDIO		(1 << 6)
+#define	CEA_REV			0x02
+#define	CEA_DATA_OFF		0x03
+#define	CEA_DATA_START		4
+#define	BLOCK_TAG(x)		(((x) >> 5) & 0x7)
+#define	BLOCK_TAG_VSDB		3
+#define	BLOCK_LEN(x)		((x) & 0x1f)
+#define	HDMI_VSDB_MINLEN	5
+#define	HDMI_OUI		"\x03\x0c\x00"
+#define	HDMI_OUI_LEN		3
+
 static void
 dwc_hdmi_phy_wait_i2c_done(struct dwc_hdmi_softc *sc, int msec)
 {
@@ -122,7 +137,7 @@ dwc_hdmi_av_composer(struct dwc_hdmi_sof
 		HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE);
 
 	/* TODO: implement HDMI part */
-	is_dvi = 1;
+	is_dvi = sc->sc_has_audio == 0;
 	inv_val |= (is_dvi ?
 		HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE :
 		HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE);
@@ -419,6 +434,70 @@ dwc_hdmi_enable_video_path(struct dwc_hd
 }
 
 static void
+dwc_hdmi_configure_audio(struct dwc_hdmi_softc *sc)
+{
+	unsigned int n;
+	uint8_t val;
+
+	if (sc->sc_has_audio == 0)
+		return;
+
+	/* The following values are for 48 kHz */
+	switch (sc->sc_mode.dot_clock) {
+	case 25170:
+		n = 6864;
+		break;
+	case 27020:
+		n = 6144;
+		break;
+	case 74170:
+		n = 11648;
+		break;
+	case 148350:
+		n = 5824;
+		break;
+	default:
+		n = 6144;
+		break;
+	}
+
+	WR1(sc, HDMI_AUD_N1, (n >> 0) & 0xff);
+	WR1(sc, HDMI_AUD_N2, (n >> 8) & 0xff);
+	WR1(sc, HDMI_AUD_N3, (n >> 16) & 0xff);
+
+	val = RD1(sc, HDMI_AUD_CTS3);
+	val &= ~(HDMI_AUD_CTS3_N_SHIFT_MASK | HDMI_AUD_CTS3_CTS_MANUAL);
+	WR1(sc, HDMI_AUD_CTS3, val);
+
+	val = RD1(sc, HDMI_AUD_CONF0);
+	val &= ~HDMI_AUD_CONF0_INTERFACE_MASK;
+	val |= HDMI_AUD_CONF0_INTERFACE_IIS;
+	val &= ~HDMI_AUD_CONF0_I2SINEN_MASK;
+	val |= HDMI_AUD_CONF0_I2SINEN_CH2;
+	WR1(sc, HDMI_AUD_CONF0, val);
+
+	val = RD1(sc, HDMI_AUD_CONF1);
+	val &= ~HDMI_AUD_CONF1_DATAMODE_MASK;
+	val |= HDMI_AUD_CONF1_DATAMODE_IIS;
+	val &= ~HDMI_AUD_CONF1_DATWIDTH_MASK;
+	val |= HDMI_AUD_CONF1_DATWIDTH_16BIT;
+	WR1(sc, HDMI_AUD_CONF1, val);
+
+	WR1(sc, HDMI_AUD_INPUTCLKFS, HDMI_AUD_INPUTCLKFS_64);
+
+	WR1(sc, HDMI_FC_AUDICONF0, 1 << 4);	/* CC=1 */
+	WR1(sc, HDMI_FC_AUDICONF1, 0);
+	WR1(sc, HDMI_FC_AUDICONF2, 0);		/* CA=0 */
+	WR1(sc, HDMI_FC_AUDICONF3, 0);
+	WR1(sc, HDMI_FC_AUDSV, 0xee);		/* channels valid */
+
+	/* Enable audio clock */
+	val = RD1(sc, HDMI_MC_CLKDIS);
+	val &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE;
+	WR1(sc, HDMI_MC_CLKDIS, val);
+}
+
+static void
 dwc_hdmi_video_packetize(struct dwc_hdmi_softc *sc)
 {
 	unsigned int color_depth = 0;
@@ -552,11 +631,15 @@ static int
 dwc_hdmi_set_mode(struct dwc_hdmi_softc *sc)
 {
 
+	/* XXX */
+	sc->sc_has_audio = 1;
+
 	dwc_hdmi_disable_overflow_interrupts(sc);
 	dwc_hdmi_av_composer(sc);
 	dwc_hdmi_phy_init(sc);
 	dwc_hdmi_enable_video_path(sc);
-	/* TODO: AVI infoframes */
+	dwc_hdmi_configure_audio(sc);
+	/* TODO:  dwc_hdmi_config_avi(sc); */
 	dwc_hdmi_video_packetize(sc);
 	/* TODO:  dwc_hdmi_video_csc(sc); */
 	dwc_hdmi_video_sample(sc);
@@ -567,14 +650,17 @@ dwc_hdmi_set_mode(struct dwc_hdmi_softc 
 }
 
 static int
-hdmi_edid_read(struct dwc_hdmi_softc *sc, uint8_t **edid, uint32_t *edid_len)
+hdmi_edid_read(struct dwc_hdmi_softc *sc, int block, uint8_t **edid,
+    uint32_t *edid_len)
 {
 	device_t i2c_dev;
 	int result;
-	uint8_t addr = 0;
+	uint8_t addr = block & 1 ? EDID_LENGTH : 0;
+	uint8_t segment = block >> 1;
 	struct iic_msg msg[] = {
-		{ 0, IIC_M_WR, 1, &addr },
-		{ 0, IIC_M_RD, EDID_LENGTH, NULL}
+		{ I2C_DDC_SEGADDR, IIC_M_WR, 1, &segment },
+		{ I2C_DDC_ADDR, IIC_M_WR, 1, &addr },
+		{ I2C_DDC_ADDR, IIC_M_RD, EDID_LENGTH, sc->sc_edid }
 	};
 
 	*edid = NULL;
@@ -588,12 +674,10 @@ hdmi_edid_read(struct dwc_hdmi_softc *sc
 		return (ENXIO);
 	}
 
-	device_printf(sc->sc_dev, "reading EDID from %s, addr %02x\n",
-	    device_get_nameunit(i2c_dev), I2C_DDC_ADDR/2);
-
-	msg[0].slave = I2C_DDC_ADDR;
-	msg[1].slave = I2C_DDC_ADDR;
-	msg[1].buf = sc->sc_edid;
+	if (bootverbose)
+		device_printf(sc->sc_dev,
+		    "reading EDID from %s, block %d, addr %02x\n",
+		    device_get_nameunit(i2c_dev), block, I2C_DDC_ADDR/2);
 
 	result = iicbus_request_bus(i2c_dev, sc->sc_dev, IIC_INTRWAIT);
 
@@ -602,7 +686,7 @@ hdmi_edid_read(struct dwc_hdmi_softc *sc
 		return (result);
 	}
 
-	result = iicbus_transfer(i2c_dev, msg, 2);
+	result = iicbus_transfer(i2c_dev, msg, 3);
 	iicbus_release_bus(i2c_dev, sc->sc_dev);
 
 	if (result) {
@@ -670,11 +754,84 @@ out:
 	return (err);
 }
 
+static int
+dwc_hdmi_detect_hdmi_vsdb(uint8_t *edid)
+{
+	int off, p, btag, blen;
+
+	if (edid[EXT_TAG] != CEA_TAG_ID)
+		return (0);
+
+	off = edid[CEA_DATA_OFF];
+
+	/* CEA data block collection starts at byte 4 */
+	if (off <= CEA_DATA_START)
+		return (0);
+
+	/* Parse the CEA data blocks */
+	for (p = CEA_DATA_START; p < off;) {
+		btag = BLOCK_TAG(edid[p]);
+		blen = BLOCK_LEN(edid[p]);
+
+		/* Make sure the length is sane */
+		if (p + blen + 1 > off)
+			break;
+
+		/* Look for a VSDB with the HDMI 24-bit IEEE registration ID */
+		if (btag == BLOCK_TAG_VSDB && blen >= HDMI_VSDB_MINLEN &&
+		    memcmp(&edid[p + 1], HDMI_OUI, HDMI_OUI_LEN) == 0)
+			return (1);
+
+		/* Next data block */
+		p += (1 + blen);
+	}
+
+	/* Not found */
+	return (0);
+}
+
+static void
+dwc_hdmi_detect_hdmi(struct dwc_hdmi_softc *sc)
+{
+	uint8_t *edid;
+	uint32_t edid_len;
+	int block;
+
+	sc->sc_has_audio = 0;
+
+	/* Scan through extension blocks, looking for a CEA-861 block */
+	for (block = 1; block <= sc->sc_edid_info.edid_ext_block_count;
+	    block++) {
+		if (hdmi_edid_read(sc, block, &edid, &edid_len) != 0)
+			return;
+		if (dwc_hdmi_detect_hdmi_vsdb(edid) != 0) {
+			if (bootverbose)
+				device_printf(sc->sc_dev,
+				    "enabling audio support\n");
+			sc->sc_has_audio =
+			    (edid[CEA_DTD] & DTD_BASIC_AUDIO) != 0;
+			return;
+		}
+	}
+}
+
 int
 dwc_hdmi_get_edid(device_t dev, uint8_t **edid, uint32_t *edid_len)
 {
+	struct dwc_hdmi_softc *sc;
+	int error;
+
+	sc = device_get_softc(dev);
 
-	return (hdmi_edid_read(device_get_softc(dev), edid, edid_len));
+	memset(&sc->sc_edid_info, 0, sizeof(sc->sc_edid_info));
+
+	error = hdmi_edid_read(sc, 0, edid, edid_len);
+	if (error != 0)
+		return (error);
+
+	edid_parse(*edid, &sc->sc_edid_info);
+
+	return (0);
 }
 
 int
@@ -685,6 +842,8 @@ dwc_hdmi_set_videomode(device_t dev, con
 	sc = device_get_softc(dev);
 	memcpy(&sc->sc_mode, mode, sizeof(*mode));
 
+	dwc_hdmi_detect_hdmi(sc);
+
 	dwc_hdmi_set_mode(sc);
 
 	return (0);

Modified: head/sys/dev/hdmi/dwc_hdmi.h
==============================================================================
--- head/sys/dev/hdmi/dwc_hdmi.h	Thu Dec 29 14:00:10 2016	(r310776)
+++ head/sys/dev/hdmi/dwc_hdmi.h	Thu Dec 29 14:08:24 2016	(r310777)
@@ -40,6 +40,9 @@ struct dwc_hdmi_softc {
 	uint8_t			sc_edid_len;
 	struct intr_config_hook	sc_mode_hook;
 	struct videomode	sc_mode;
+
+	struct edid_info	sc_edid_info;
+	int			sc_has_audio;
 };
 
 static inline uint8_t

Modified: head/sys/dev/hdmi/dwc_hdmi_fdt.c
==============================================================================
--- head/sys/dev/hdmi/dwc_hdmi_fdt.c	Thu Dec 29 14:00:10 2016	(r310776)
+++ head/sys/dev/hdmi/dwc_hdmi_fdt.c	Thu Dec 29 14:08:24 2016	(r310777)
@@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$");
 #include <dev/extres/clk/clk.h>
 
 #include <dev/videomode/videomode.h>
+#include <dev/videomode/edidvar.h>
 
 #include <dev/hdmi/dwc_hdmi.h>
 

Modified: head/sys/dev/hdmi/dwc_hdmireg.h
==============================================================================
--- head/sys/dev/hdmi/dwc_hdmireg.h	Thu Dec 29 14:00:10 2016	(r310776)
+++ head/sys/dev/hdmi/dwc_hdmireg.h	Thu Dec 29 14:08:24 2016	(r310777)
@@ -250,6 +250,7 @@
 #define	HDMI_FC_SPDDEVICEINF			0x1062
 #define	HDMI_FC_AUDSCONF			0x1063
 #define	HDMI_FC_AUDSSTAT			0x1064
+#define	HDMI_FC_AUDSV				0x1065
 #define	HDMI_FC_DATACH0FILL			0x1070
 #define	HDMI_FC_DATACH1FILL			0x1071
 #define	HDMI_FC_DATACH2FILL			0x1072
@@ -472,7 +473,24 @@
 
 /* Audio Sampler Registers */
 #define	HDMI_AUD_CONF0				0x3100
+#define	  HDMI_AUD_CONF0_INTERFACE_MASK		0x20
+#define	    HDMI_AUD_CONF0_INTERFACE_IIS	0x20
+#define	    HDMI_AUD_CONF0_INTERFACE_SPDIF	0x00
+#define	  HDMI_AUD_CONF0_I2SINEN_MASK		0x0f
+#define	    HDMI_AUD_CONF0_I2SINEN_CH2		0x01
+#define	    HDMI_AUD_CONF0_I2SINEN_CH4		0x03
+#define	    HDMI_AUD_CONF0_I2SINEN_CH6		0x07
+#define	    HDMI_AUD_CONF0_I2SINEN_CH8		0x0f
 #define	HDMI_AUD_CONF1				0x3101
+#define	  HDMI_AUD_CONF1_DATAMODE_MASK		0xe0
+#define	    HDMI_AUD_CONF1_DATAMODE_IIS		0x00
+#define	    HDMI_AUD_CONF1_DATAMODE_RIGHT_J	0x20
+#define	    HDMI_AUD_CONF1_DATAMODE_LEFT_J	0x40
+#define	    HDMI_AUD_CONF1_DATAMODE_BURST_1	0x60
+#define	    HDMI_AUD_CONF1_DATAMDOE_BURST_2	0x80
+#define	  HDMI_AUD_CONF1_DATWIDTH_MASK		0x1f
+#define	    HDMI_AUD_CONF1_DATWIDTH_16BIT	16
+#define	    HDMI_AUD_CONF1_DATWIDTH_24BIT	24
 #define	HDMI_AUD_INT				0x3102
 #define	HDMI_AUD_CONF2				0x3103
 #define	HDMI_AUD_N1				0x3200
@@ -481,7 +499,14 @@
 #define	HDMI_AUD_CTS1				0x3203
 #define	HDMI_AUD_CTS2				0x3204
 #define	HDMI_AUD_CTS3				0x3205
+#define	  HDMI_AUD_CTS3_N_SHIFT_MASK		0xe0
+#define	  HDMI_AUD_CTS3_CTS_MANUAL		0x10
 #define	HDMI_AUD_INPUTCLKFS			0x3206
+#define	  HDMI_AUD_INPUTCLKFS_128		0
+#define	  HDMI_AUD_INPUTCLKFS_256		1
+#define	  HDMI_AUD_INPUTCLKFS_512		2
+#define	  HDMI_AUD_INPUTCLKFS_1024		3
+#define	  HDMI_AUD_INPUTCLKFS_64		4
 #define	HDMI_AUD_SPDIFINT			0x3302
 #define	HDMI_AUD_CONF0_HBR			0x3400
 #define	HDMI_AUD_HBR_STATUS			0x3401



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201612291408.uBTE8O73030217>