svn commit: r310777 - in head/sys: arm/freescale/imx dev/hdmi
Jared McNeill
jmcneill at FreeBSD.org
Thu Dec 29 14:08:26 UTC 2016
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
More information about the svn-src-head
mailing list