[PATCH RFC v2 6/7] drm/i2c: tda998x: Register ASoC HDMI codec for audio functionality DO NOT MERGE
Jyri Sarha
jsarha at ti.com
Tue May 26 11:59:10 PDT 2015
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 at 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;
--
1.9.1
More information about the dri-devel
mailing list