Add support for audio on I2S based DesignWare HDMI controllers.

Relnotes:	yes
This commit is contained in:
Jared McNeill 2016-12-29 14:08:24 +00:00
parent 7fd348080f
commit 6443acaa6c
5 changed files with 203 additions and 14 deletions

View File

@ -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>

View File

@ -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;
return (hdmi_edid_read(device_get_softc(dev), edid, edid_len));
sc = device_get_softc(dev);
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);

View File

@ -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

View File

@ -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>

View File

@ -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