[RFC,v3,6/7] drm/i2c: tda998x: Register ASoC HDMI codec for audio functionality
diff mbox

Message ID 1e8e3215caa932dab68de8576aee81f5e1008163.1439542916.git.jsarha@ti.com
State New
Headers show

Commit Message

Jyri Sarha Aug. 14, 2015, 9:30 a.m. UTC
Register ASoC HDMI codec for audio functionality. This is an initial
ASoC audio implementation for tda998x driver and it does not use all
the features provided by hdmi-codec.

HDMI audio info-frame and audio stream header is generated by the ASoC
HDMI codec. The codec also applies constraints for available
sample-rates.

Implementation of audio_startup for hdmi_codec_ops would enable
tda998x driver to abort ongoing playback if the HDMI cable is
unplugged or re-plugged to a device without audio capability.

Signed-off-by: Jyri Sarha <jsarha@ti.com>
---
 drivers/gpu/drm/i2c/Kconfig       |   1 +
 drivers/gpu/drm/i2c/tda998x_drv.c | 241 +++++++++++++++++++++++++++++++++-----
 2 files changed, 214 insertions(+), 28 deletions(-)

Comments

Russell King - ARM Linux Aug. 14, 2015, 10:06 a.m. UTC | #1
On Fri, Aug 14, 2015 at 12:30:44PM +0300, Jyri Sarha wrote:
> +static int tda998x_write_aif(struct tda998x_priv *priv,
> +			     struct hdmi_audio_infoframe *cea)
> +{
> +	uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)];
> +	int len;
> +
> +	len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf));
> +	if (len < 0) {
> +		dev_err(&priv->hdmi->dev,
> +			"Failed to pack audio infoframe: %d\n", len);
> +		return len;
> +	}
> +
> +	/* Write the audio information packet */
> +	tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len);
> +	return 0;
> +}
> +

I have such a function already queued up, but I can't push it out at the
moment because of too many conflicts across all my DRM work.  I'm waiting
for after 4.3-rc1 before publishing anything from or accepting anything
else into DRM branches.

>  static void
>  tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
>  {
> @@ -670,19 +691,24 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
>  	}
>  }
>  
> -static void
> +static int
>  tda998x_configure_audio(struct tda998x_priv *priv,
> -		struct drm_display_mode *mode, struct tda998x_encoder_params *p)
> +			int mode_clock,
> +			int ena_ap,
> +			int dai_format,
> +			int sample_width,
> +			int sample_rate,
> +			const u8 *status)

I don't think this is an improvement.

> +static int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
> +{
> +	struct tda998x_priv *priv = dev_get_drvdata(dev);
> +	struct drm_mode_config *config = &priv->encoder->dev->mode_config;
> +	struct drm_connector *connector;
> +	int ret = -ENODEV;
> +
> +	mutex_lock(&config.mutex);
> +	list_for_each_entry(connector, &config->connector_list, head) {
> +		if (priv->encoder == connector->encoder) {
> +			memcpy(buf, connector->eld, 
> +			       min(sizeof(connector->eld), len));
> +			ret = 0;
> +		}
> +	}
> +	mutex_unlock(&config.mutex);

Obviously untested.  Should be config->mutex.

But in any case, when I kill the DRM slave encoder code in here, this
becomes unnecessary.
Jyri Sarha Aug. 17, 2015, 7:25 a.m. UTC | #2
On 08/14/15 13:06, Russell King - ARM Linux wrote:
> On Fri, Aug 14, 2015 at 12:30:44PM +0300, Jyri Sarha wrote:
>> +static int tda998x_write_aif(struct tda998x_priv *priv,
>> +			     struct hdmi_audio_infoframe *cea)
>> +{
>> +	uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)];
>> +	int len;
>> +
>> +	len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf));
>> +	if (len < 0) {
>> +		dev_err(&priv->hdmi->dev,
>> +			"Failed to pack audio infoframe: %d\n", len);
>> +		return len;
>> +	}
>> +
>> +	/* Write the audio information packet */
>> +	tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len);
>> +	return 0;
>> +}
>> +
>
> I have such a function already queued up, but I can't push it out at the
> moment because of too many conflicts across all my DRM work.  I'm waiting
> for after 4.3-rc1 before publishing anything from or accepting anything
> else into DRM branches.
>

Ok, is the code available some where? Could take a look so I can align 
my code to that.

>>   static void
>>   tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
>>   {
>> @@ -670,19 +691,24 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
>>   	}
>>   }
>>
>> -static void
>> +static int
>>   tda998x_configure_audio(struct tda998x_priv *priv,
>> -		struct drm_display_mode *mode, struct tda998x_encoder_params *p)
>> +			int mode_clock,
>> +			int ena_ap,
>> +			int dai_format,
>> +			int sample_width,
>> +			int sample_rate,
>> +			const u8 *status)
>
> I don't think this is an improvement.
>

Still it makes the function more generic and enables its usage in 
HDMI-codec API implementation. I'll try to make it look tidier.

>> +static int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
>> +{
>> +	struct tda998x_priv *priv = dev_get_drvdata(dev);
>> +	struct drm_mode_config *config = &priv->encoder->dev->mode_config;
>> +	struct drm_connector *connector;
>> +	int ret = -ENODEV;
>> +
>> +	mutex_lock(&config.mutex);
>> +	list_for_each_entry(connector, &config->connector_list, head) {
>> +		if (priv->encoder == connector->encoder) {
>> +			memcpy(buf, connector->eld,
>> +			       min(sizeof(connector->eld), len));
>> +			ret = 0;
>> +		}
>> +	}
>> +	mutex_unlock(&config.mutex);
>
> Obviously untested.  Should be config->mutex.
>

Sorry. Should never do these last minute changes. I must have been 
compiling and testing different code version. I first had this function 
using config->connection_mutex, but then - after reading the eld related 
code - found that mode_config mutex should be used instead.

> But in any case, when I kill the DRM slave encoder code in here, this
> becomes unnecessary.
>

Ok, The information from ELD needs be passed to audio side constraints 
somehow. I'd love to see the code you have in queue.

Best regards,
Jyri

Patch
diff mbox

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 4dc2dc0..8444e18 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>
@@ -49,6 +50,8 @@  struct tda998x_priv {
 	u8 vip_cntrl_2;
 	struct tda998x_encoder_params params;
 
+	struct platform_device *audio_pdev;
+
 	wait_queue_head_t wq_edid;
 	volatile int wq_edid_wait;
 	struct drm_encoder *encoder;
@@ -435,7 +438,7 @@  out:
 }
 
 static void
-reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt)
+reg_write_range(struct tda998x_priv *priv, uint16_t reg, const u8 *p, int cnt)
 {
 	struct i2c_client *client = priv->hdmi;
 	uint8_t buf[cnt+1];
@@ -619,7 +622,7 @@  tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr,
 }
 
 static void
-tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
+tda998x_write_raw_aif(struct tda998x_priv *priv, u8 *audio_frame)
 {
 	u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1];
 
@@ -627,10 +630,10 @@  tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
 	buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO;
 	buf[HB(1)] = 0x01;
 	buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE;
-	buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */
-	buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */
-	buf[PB(4)] = p->audio_frame[4];
-	buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */
+	buf[PB(1)] = audio_frame[1] & 0x07; /* CC */
+	buf[PB(2)] = audio_frame[2] & 0x1c; /* SF */
+	buf[PB(4)] = audio_frame[4];
+	buf[PB(5)] = audio_frame[5] & 0xf8; /* DM_INH + LSV */
 
 	buf[PB(0)] = tda998x_cksum(buf, sizeof(buf));
 
@@ -638,6 +641,24 @@  tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
 			 sizeof(buf));
 }
 
+static int tda998x_write_aif(struct tda998x_priv *priv,
+			     struct hdmi_audio_infoframe *cea)
+{
+	uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)];
+	int len;
+
+	len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf));
+	if (len < 0) {
+		dev_err(&priv->hdmi->dev,
+			"Failed to pack audio infoframe: %d\n", len);
+		return len;
+	}
+
+	/* Write the audio information packet */
+	tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len);
+	return 0;
+}
+
 static void
 tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
 {
@@ -670,19 +691,24 @@  static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
 	}
 }
 
-static void
+static int
 tda998x_configure_audio(struct tda998x_priv *priv,
-		struct drm_display_mode *mode, struct tda998x_encoder_params *p)
+			int mode_clock,
+			int ena_ap,
+			int dai_format,
+			int sample_width,
+			int sample_rate,
+			const u8 *status)
 {
 	uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv;
 	uint32_t n;
 
 	/* Enable audio ports */
-	reg_write(priv, REG_ENA_AP, p->audio_cfg);
-	reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg);
+	reg_write(priv, REG_ENA_AP, ena_ap);
+	reg_write(priv, REG_ENA_ACLK, dai_format == AFMT_SPDIF ? 0 : 1);
 
 	/* Set audio input source */
-	switch (p->audio_format) {
+	switch (dai_format) {
 	case AFMT_SPDIF:
 		reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF);
 		clksel_aip = AIP_CLKSEL_AIP_SPDIF;
@@ -694,12 +720,25 @@  tda998x_configure_audio(struct tda998x_priv *priv,
 		reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S);
 		clksel_aip = AIP_CLKSEL_AIP_I2S;
 		clksel_fs = AIP_CLKSEL_FS_ACLK;
-		cts_n = CTS_N_M(3) | CTS_N_K(3);
+		switch (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:
-		BUG();
-		return;
+		dev_err(&priv->hdmi->dev, "Unsupported I2S format\n");
+		return -EINVAL;
 	}
 
 	reg_write(priv, REG_AIP_CLKSEL, clksel_aip);
@@ -715,11 +754,11 @@  tda998x_configure_audio(struct tda998x_priv *priv,
 	 * assume 100MHz requires larger divider.
 	 */
 	adiv = AUDIO_DIV_SERCLK_8;
-	if (mode->clock > 100000)
+	if (mode_clock > 100000)
 		adiv++;			/* AUDIO_DIV_SERCLK_16 */
 
 	/* S/PDIF asks for a larger divider */
-	if (p->audio_format == AFMT_SPDIF)
+	if (dai_format == AFMT_SPDIF)
 		adiv++;			/* AUDIO_DIV_SERCLK_16 or _32 */
 
 	reg_write(priv, REG_AUDIO_DIV, adiv);
@@ -728,7 +767,7 @@  tda998x_configure_audio(struct tda998x_priv *priv,
 	 * This is the approximate value of N, which happens to be
 	 * the recommended values for non-coherent clocks.
 	 */
-	n = 128 * p->audio_sample_rate / 1000;
+	n = 128 * sample_rate / 1000;
 
 	/* Write the CTS and N values */
 	buf[0] = 0x44;
@@ -747,19 +786,13 @@  tda998x_configure_audio(struct tda998x_priv *priv,
 	reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
 
 	/* Write the channel status */
-	buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT;
-	buf[1] = 0x00;
-	buf[2] = IEC958_AES3_CON_FS_NOTID;
-	buf[3] = IEC958_AES4_CON_ORIGFS_NOTID |
-			IEC958_AES4_CON_MAX_WORDLEN_24;
-	reg_write_range(priv, REG_CH_STAT_B(0), buf, 4);
+	reg_write_range(priv, REG_CH_STAT_B(0), status, 4);
 
 	tda998x_audio_mute(priv, true);
 	msleep(20);
 	tda998x_audio_mute(priv, false);
 
-	/* Write the audio information packet */
-	tda998x_write_aif(priv, p);
+	return 0;
 }
 
 /* DRM encoder functions */
@@ -1033,9 +1066,24 @@  tda998x_encoder_mode_set(struct tda998x_priv *priv,
 
 		tda998x_write_avi(priv, adjusted_mode);
 
-		if (priv->params.audio_cfg)
-			tda998x_configure_audio(priv, adjusted_mode,
-						&priv->params);
+		if (priv->params.audio_cfg) {
+			static const u8 status[] = {
+				IEC958_AES0_CON_NOT_COPYRIGHT,
+				0x00,
+				IEC958_AES3_CON_FS_NOTID,
+				IEC958_AES4_CON_ORIGFS_NOTID,
+				IEC958_AES4_CON_MAX_WORDLEN_24
+			};
+
+			tda998x_configure_audio(priv, adjusted_mode->clock,
+						priv->params.audio_cfg,
+						priv->params.audio_clk_cfg,
+						24,
+						priv->params.audio_sample_rate,
+						status);
+
+			tda998x_write_raw_aif(priv, priv->params.audio_frame);
+		}
 	}
 }
 
@@ -1127,6 +1175,8 @@  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);
+
 	kfree(edid);
 
 	return n;
@@ -1163,6 +1213,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 */
@@ -1237,6 +1290,134 @@  static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = {
 	.set_property = tda998x_encoder_set_property,
 };
 
+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);
+	int ena_ap = -1;
+	int dai_format, i, ret;
+
+	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];
+		dai_format = AFMT_I2S;
+		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];
+		dai_format = AFMT_SPDIF;
+		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;
+	}
+
+	ret = tda998x_configure_audio(priv,
+				      priv->encoder->crtc->hwmode.clock,
+				      ena_ap,
+				      dai_format,
+				      params->sample_width,
+				      params->sample_rate,
+				      params->iec.status);
+
+	if (!ret)
+		ret = tda998x_write_aif(priv, &params->cea);
+
+	return ret;
+}
+
+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 int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
+{
+	struct tda998x_priv *priv = dev_get_drvdata(dev);
+	struct drm_mode_config *config = &priv->encoder->dev->mode_config;
+	struct drm_connector *connector;
+	int ret = -ENODEV;
+
+	mutex_lock(&config.mutex);
+	list_for_each_entry(connector, &config->connector_list, head) {
+		if (priv->encoder == connector->encoder) {
+			memcpy(buf, connector->eld, 
+			       min(sizeof(connector->eld), len));
+			ret = 0;
+		}
+	}
+	mutex_unlock(&config.mutex);
+
+	return ret;
+}
+
+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,
@@ -1425,6 +1606,8 @@  static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
 		}
 	}
 
+	tda998x_audio_codec_init(priv, &client->dev);
+
 	return 0;
 
 fail:
@@ -1455,6 +1638,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;