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 at ti.com>
---
 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

Reply via email to