diff mbox

[RFC,v2,6/7] drm/i2c: tda998x: Register ASoC HDMI codec for audio functionality DO NOT MERGE

Message ID 1432666751-24794-7-git-send-email-jsarha@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jyri Sarha May 26, 2015, 6:59 p.m. UTC
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 mbox

Patch

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(&params->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;