This patch is here to demonstrate how to use the ASoC hdmi-codec to implement ASoC codec API in tda998x driver.
I do not have proper documentation for tda998x family chips so I lack the necessary information for making a decent binding for audio part of the chip. In stead I use binding from Jean-Francois Moine's "ASoC: tda998x: add a codec to the HDMI transmitter" patch series.
Signed-off-by: Jyri Sarha jsarha@ti.com --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 238 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+)
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..088f278 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -28,6 +28,7 @@ config DRM_I2C_SIL164 config DRM_I2C_NXP_TDA998X tristate "NXP Semiconductors TDA998X HDMI encoder" default m if DRM_TILCDC + select SND_SOC_HDMI_CODEC if SND_SOC help Support for NXP Semiconductors TDA998X HDMI encoders.
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index bcf96f7..3c38911 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <sound/asoundef.h> +#include <sound/hdmi-codec.h>
#include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -45,6 +46,9 @@ struct tda998x_priv { u8 vip_cntrl_2; struct tda998x_encoder_params params;
+ struct platform_device *audio_pdev; + uint8_t eld[MAX_ELD_BYTES]; + wait_queue_head_t wq_edid; volatile int wq_edid_wait; struct drm_encoder *encoder; @@ -1120,6 +1124,9 @@ tda998x_encoder_get_modes(struct tda998x_priv *priv, drm_mode_connector_update_edid_property(connector, edid); n = drm_add_edid_modes(connector, edid); priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); + drm_edid_to_eld(connector, edid); + memcpy(priv->eld, connector->eld, sizeof(priv->eld)); + kfree(edid);
return n; @@ -1156,6 +1163,9 @@ static void tda998x_destroy(struct tda998x_priv *priv) }
i2c_unregister_device(priv->cec); + + if (priv->audio_pdev) + platform_device_unregister(priv->audio_pdev); }
/* Slave encoder support */ @@ -1230,6 +1240,230 @@ static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = { .set_property = tda998x_encoder_set_property, };
+static int +tda998x_configure_audio2(struct tda998x_priv *priv, + int mode_clock, + int ena_ap, + struct hdmi_codec_params *params, + struct hdmi_codec_daifmt *daifmt) +{ + uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv; + uint8_t infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + int infoframe_len; + uint32_t n; + + infoframe_len = hdmi_audio_infoframe_pack(¶ms->cea, infoframe_buf, + sizeof(infoframe_buf)); + if (infoframe_len < 0) { + dev_err(&priv->hdmi->dev, + "Failed to pack audio infoframe: %d\n", + infoframe_len); + return infoframe_len; + } + + /* Enable audio ports */ + reg_write(priv, REG_ENA_AP, ena_ap); + reg_write(priv, REG_ENA_ACLK, daifmt->fmt == HDMI_SPDIF ? 0 : 1); + + /* Set audio input source */ + switch (daifmt->fmt) { + case HDMI_SPDIF: + reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF); + clksel_aip = AIP_CLKSEL_AIP_SPDIF; + clksel_fs = AIP_CLKSEL_FS_FS64SPDIF; + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + + case HDMI_I2S: + reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); + clksel_aip = AIP_CLKSEL_AIP_I2S; + clksel_fs = AIP_CLKSEL_FS_ACLK; + switch (params->sample_width) { + case 16: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case 18: + case 20: + case 24: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case 32: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } + break; + + default: + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); + return -EINVAL; + } + + reg_write(priv, REG_AIP_CLKSEL, clksel_aip); + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT | + AIP_CNTRL_0_ACR_MAN); /* auto CTS */ + reg_write(priv, REG_CTS_N, cts_n); + + /* + * Audio input somehow depends on HDMI line rate which is + * related to pixclk. Testing showed that modes with pixclk + * >100MHz need a larger divider while <40MHz need the default. + * There is no detailed info in the datasheet, so we just + * assume 100MHz requires larger divider. + */ + adiv = AUDIO_DIV_SERCLK_8; + if (mode_clock > 100000) + adiv++; /* AUDIO_DIV_SERCLK_16 */ + + /* S/PDIF asks for a larger divider */ + if (daifmt->fmt == HDMI_SPDIF) + adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */ + + reg_write(priv, REG_AUDIO_DIV, adiv); + + /* + * This is the approximate value of N, which happens to be + * the recommended values for non-coherent clocks. + */ + n = 128 * params->sample_rate / 1000; + + /* Write the CTS and N values */ + buf[0] = 0x44; + buf[1] = 0x42; + buf[2] = 0x01; + buf[3] = n; + buf[4] = n >> 8; + buf[5] = n >> 16; + reg_write_range(priv, REG_ACR_CTS_0, buf, 6); + + /* Set CTS clock reference */ + reg_write(priv, REG_AIP_CLKSEL, clksel_aip | clksel_fs); + + /* Reset CTS generator */ + reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); + + /* Write the channel status */ + reg_write_range(priv, REG_CH_STAT_B(0), params->iec.status, 4); + + tda998x_audio_mute(priv, true); + msleep(20); + tda998x_audio_mute(priv, false); + + /* Write the audio information packet */ + tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, + infoframe_buf, + infoframe_len); + return 0; +} + +static int tda998x_audio_hw_params(struct device *dev, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + unsigned int ena_ap = -1; + int i; + + if (!priv->encoder->crtc) + return -ENODEV; + + switch (daifmt->fmt) { + case HDMI_I2S: + if (daifmt->bit_clk_inv || daifmt->frame_clk_inv || + daifmt->bit_clk_master || daifmt->frame_clk_master) { + dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__, + daifmt->bit_clk_inv, daifmt->frame_clk_inv, + daifmt->bit_clk_master, + daifmt->frame_clk_master); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) + if (priv->audio.port_types[i] == AFMT_I2S) + ena_ap = priv->audio.ports[i]; + break; + case HDMI_SPDIF: + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) + if (priv->audio.port_types == AFMT_SPDIF) + ena_ap = priv->audio.ports[i]; + break; + default: + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); + return -EINVAL; + } + + if (ena_ap < 0) { + dev_err(dev, "%s: No audio configutation found\n", __func__); + return -EINVAL; + } + + + return tda998x_configure_audio2(priv, + priv->encoder->crtc->hwmode.clock, + ena_ap, + params, + daifmt); +} + +static void tda998x_audio_shutdown(struct device *dev) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + reg_write(priv, REG_ENA_AP, 0); +} + +int tda998x_audio_digital_mute(struct device *dev, bool enable) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + tda998x_audio_mute(priv, enable); + + return 0; +} + +static uint8_t *tda998x_audio_get_eld(struct device *dev) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + return priv->eld; +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = tda998x_audio_hw_params, + .audio_shutdown = tda998x_audio_shutdown, + .digital_mute = tda998x_audio_digital_mute, + .get_eld = tda998x_audio_get_eld, +}; + +static int tda998x_audio_codec_init(struct tda998x_priv *priv, + struct device *dev) +{ + struct hdmi_codec_pdata codec_data = { + .dev = dev, + .ops = &audio_codec_ops, + .max_i2s_channels = 2, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) { + if (priv->audio.port_types[i] == AFMT_I2S && + priv->audio.ports[i] != 0) + codec_data.i2s = 1; + if (priv->audio.port_types[i] == AFMT_SPDIF && + priv->audio.ports[i] != 0) + codec_data.spdif = 1; + } + + priv->audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + if (IS_ERR(priv->audio_pdev)) + return PTR_ERR(priv->audio_pdev); + + return 0; +} + /* I2C driver functions */
static int tda998x_parse_ports(struct tda998x_priv *priv, @@ -1417,6 +1651,8 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) } }
+ tda998x_audio_codec_init(priv, &client->dev); + return 0;
fail: @@ -1447,6 +1683,8 @@ static int tda998x_encoder_init(struct i2c_client *client, return ret; }
+ dev_set_drvdata(&client->dev, priv); + encoder_slave->slave_priv = priv; encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs;