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

Jyri Sarha
2015-08-14T09:30:44Z

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-l0cyMroinI0@public.gmane.org>
---
drivers/gpu/drm/i2c/Kconfig | 1 +
drivers/gpu/drm/i2c/tda998x_drv.c | 241 +++++++++++++++++++++++++++++++++-----
2 files changed, 214 insertions(+), 28 deletions(-)

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;

--
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html