From 6443acaa6cde9d71f601f9cd8bdabaf1146b1543 Mon Sep 17 00:00:00 2001 From: Jared McNeill Date: Thu, 29 Dec 2016 14:08:24 +0000 Subject: [PATCH] Add support for audio on I2S based DesignWare HDMI controllers. Relnotes: yes --- sys/arm/freescale/imx/imx6_hdmi.c | 1 + sys/dev/hdmi/dwc_hdmi.c | 187 +++++++++++++++++++++++++++--- sys/dev/hdmi/dwc_hdmi.h | 3 + sys/dev/hdmi/dwc_hdmi_fdt.c | 1 + sys/dev/hdmi/dwc_hdmireg.h | 25 ++++ 5 files changed, 203 insertions(+), 14 deletions(-) diff --git a/sys/arm/freescale/imx/imx6_hdmi.c b/sys/arm/freescale/imx/imx6_hdmi.c index 3ac958fe898..7059f09a86f 100644 --- a/sys/arm/freescale/imx/imx6_hdmi.c +++ b/sys/arm/freescale/imx/imx6_hdmi.c @@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include diff --git a/sys/dev/hdmi/dwc_hdmi.c b/sys/dev/hdmi/dwc_hdmi.c index 745bcb70214..27bb4739163 100644 --- a/sys/dev/hdmi/dwc_hdmi.c +++ b/sys/dev/hdmi/dwc_hdmi.c @@ -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_softc *sc) 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); @@ -418,6 +433,70 @@ dwc_hdmi_enable_video_path(struct dwc_hdmi_softc *sc) WR1(sc, HDMI_MC_CLKDIS, clkdis); } +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) { @@ -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 *sc) } 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, uint8_t **edid, uint32_t *edid_len) 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, uint8_t **edid, uint32_t *edid_len) 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 @@ dwc_hdmi_init(device_t dev) 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, const struct videomode *mode) sc = device_get_softc(dev); memcpy(&sc->sc_mode, mode, sizeof(*mode)); + dwc_hdmi_detect_hdmi(sc); + dwc_hdmi_set_mode(sc); return (0); diff --git a/sys/dev/hdmi/dwc_hdmi.h b/sys/dev/hdmi/dwc_hdmi.h index e8a259b1c21..7e0e678d0d1 100644 --- a/sys/dev/hdmi/dwc_hdmi.h +++ b/sys/dev/hdmi/dwc_hdmi.h @@ -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 diff --git a/sys/dev/hdmi/dwc_hdmi_fdt.c b/sys/dev/hdmi/dwc_hdmi_fdt.c index b1fc817af12..2bf717082f7 100644 --- a/sys/dev/hdmi/dwc_hdmi_fdt.c +++ b/sys/dev/hdmi/dwc_hdmi_fdt.c @@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include diff --git a/sys/dev/hdmi/dwc_hdmireg.h b/sys/dev/hdmi/dwc_hdmireg.h index c1e421fd835..9174ddbb3df 100644 --- a/sys/dev/hdmi/dwc_hdmireg.h +++ b/sys/dev/hdmi/dwc_hdmireg.h @@ -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 -- 2.45.0