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 <jsa...@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(&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;
 
-- 
1.9.1

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

Reply via email to