drm/tegra: hdmi: Register audio CODEC on Tegra20
authorDmitry Osipenko <digetx@gmail.com>
Sat, 4 Dec 2021 14:37:18 +0000 (17:37 +0300)
committerThierry Reding <treding@nvidia.com>
Thu, 16 Dec 2021 13:07:07 +0000 (14:07 +0100)
Tegra20 SoC supports only S/PDIF source for HDMI audio. Register ASoC HDMI
S/PDIF CODEC for Tegra20, it will be linked with the S/PDIF CPU DAI.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/tegra/Kconfig
drivers/gpu/drm/tegra/hdmi.c

index 1650a448eabd6843a46072c636bc42fd7a02f265..8cf5aeb9db6c6b612c18e692a19042704a932a1e 100644 (file)
@@ -12,6 +12,9 @@ config DRM_TEGRA
        select INTERCONNECT
        select IOMMU_IOVA
        select CEC_CORE if CEC_NOTIFIER
+       select SND_SIMPLE_CARD if SND_SOC_TEGRA20_SPDIF
+       select SND_SOC_HDMI_CODEC if SND_SOC_TEGRA20_SPDIF
+       select SND_AUDIO_GRAPH_CARD if SND_SOC_TEGRA20_SPDIF
        help
          Choose this option if you have an NVIDIA Tegra SoC.
 
index 704271511d2bc82ffd47e09da3f658d9863cd6bb..ce917d46b23a60d4e38d5d943dc6fc52976fcefc 100644 (file)
@@ -15,6 +15,8 @@
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 
+#include <sound/hdmi-codec.h>
+
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_debugfs.h>
@@ -78,6 +80,9 @@ struct tegra_hdmi {
        bool dvi;
 
        struct drm_info_list *debugfs_files;
+
+       struct platform_device *audio_pdev;
+       struct mutex audio_lock;
 };
 
 static inline struct tegra_hdmi *
@@ -360,6 +365,18 @@ static const struct tmds_config tegra124_tmds_config[] = {
        },
 };
 
+static void tegra_hdmi_audio_lock(struct tegra_hdmi *hdmi)
+{
+       mutex_lock(&hdmi->audio_lock);
+       disable_irq(hdmi->irq);
+}
+
+static void tegra_hdmi_audio_unlock(struct tegra_hdmi *hdmi)
+{
+       enable_irq(hdmi->irq);
+       mutex_unlock(&hdmi->audio_lock);
+}
+
 static int
 tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pix_clock,
                            struct tegra_hdmi_audio_config *config)
@@ -829,6 +846,23 @@ static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi,
                                  HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT);
 }
 
+static int tegra_hdmi_reconfigure_audio(struct tegra_hdmi *hdmi)
+{
+       int err;
+
+       err = tegra_hdmi_setup_audio(hdmi);
+       if (err < 0) {
+               tegra_hdmi_disable_audio_infoframe(hdmi);
+               tegra_hdmi_disable_audio(hdmi);
+       } else {
+               tegra_hdmi_setup_audio_infoframe(hdmi);
+               tegra_hdmi_enable_audio_infoframe(hdmi);
+               tegra_hdmi_enable_audio(hdmi);
+       }
+
+       return err;
+}
+
 static bool tegra_output_is_hdmi(struct tegra_output *output)
 {
        struct edid *edid;
@@ -1135,6 +1169,8 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
        u32 value;
        int err;
 
+       tegra_hdmi_audio_lock(hdmi);
+
        /*
         * The following accesses registers of the display controller, so make
         * sure it's only executed when the output is attached to one.
@@ -1159,6 +1195,10 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
        tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE);
        tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK);
 
+       hdmi->pixel_clock = 0;
+
+       tegra_hdmi_audio_unlock(hdmi);
+
        err = host1x_client_suspend(&hdmi->client);
        if (err < 0)
                dev_err(hdmi->dev, "failed to suspend: %d\n", err);
@@ -1182,6 +1222,8 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
                return;
        }
 
+       tegra_hdmi_audio_lock(hdmi);
+
        /*
         * Enable and unmask the HDA codec SCRATCH0 register interrupt. This
         * is used for interoperability between the HDA codec driver and the
@@ -1387,6 +1429,8 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
        }
 
        /* TODO: add HDCP support */
+
+       tegra_hdmi_audio_unlock(hdmi);
 }
 
 static int
@@ -1416,6 +1460,91 @@ static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = {
        .atomic_check = tegra_hdmi_encoder_atomic_check,
 };
 
+static int tegra_hdmi_hw_params(struct device *dev, void *data,
+                               struct hdmi_codec_daifmt *fmt,
+                               struct hdmi_codec_params *hparms)
+{
+       struct tegra_hdmi *hdmi = data;
+       int ret = 0;
+
+       tegra_hdmi_audio_lock(hdmi);
+
+       hdmi->format.sample_rate = hparms->sample_rate;
+       hdmi->format.channels = hparms->channels;
+
+       if (hdmi->pixel_clock && !hdmi->dvi)
+               ret = tegra_hdmi_reconfigure_audio(hdmi);
+
+       tegra_hdmi_audio_unlock(hdmi);
+
+       return ret;
+}
+
+static int tegra_hdmi_audio_startup(struct device *dev, void *data)
+{
+       struct tegra_hdmi *hdmi = data;
+       int ret;
+
+       ret = host1x_client_resume(&hdmi->client);
+       if (ret < 0)
+               dev_err(hdmi->dev, "failed to resume: %d\n", ret);
+
+       return ret;
+}
+
+static void tegra_hdmi_audio_shutdown(struct device *dev, void *data)
+{
+       struct tegra_hdmi *hdmi = data;
+       int ret;
+
+       tegra_hdmi_audio_lock(hdmi);
+
+       hdmi->format.sample_rate = 0;
+       hdmi->format.channels = 0;
+
+       tegra_hdmi_audio_unlock(hdmi);
+
+       ret = host1x_client_suspend(&hdmi->client);
+       if (ret < 0)
+               dev_err(hdmi->dev, "failed to suspend: %d\n", ret);
+}
+
+static const struct hdmi_codec_ops tegra_hdmi_codec_ops = {
+       .hw_params = tegra_hdmi_hw_params,
+       .audio_startup = tegra_hdmi_audio_startup,
+       .audio_shutdown = tegra_hdmi_audio_shutdown,
+};
+
+static int tegra_hdmi_codec_register(struct tegra_hdmi *hdmi)
+{
+       struct hdmi_codec_pdata codec_data = {};
+
+       if (hdmi->config->has_hda)
+               return 0;
+
+       codec_data.ops = &tegra_hdmi_codec_ops;
+       codec_data.data = hdmi;
+       codec_data.spdif = 1;
+
+       hdmi->audio_pdev = platform_device_register_data(hdmi->dev,
+                                                        HDMI_CODEC_DRV_NAME,
+                                                        PLATFORM_DEVID_AUTO,
+                                                        &codec_data,
+                                                        sizeof(codec_data));
+       if (IS_ERR(hdmi->audio_pdev))
+               return PTR_ERR(hdmi->audio_pdev);
+
+       hdmi->format.channels = 2;
+
+       return 0;
+}
+
+static void tegra_hdmi_codec_unregister(struct tegra_hdmi *hdmi)
+{
+       if (hdmi->audio_pdev)
+               platform_device_unregister(hdmi->audio_pdev);
+}
+
 static int tegra_hdmi_init(struct host1x_client *client)
 {
        struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
@@ -1468,8 +1597,16 @@ static int tegra_hdmi_init(struct host1x_client *client)
                goto disable_pll;
        }
 
+       err = tegra_hdmi_codec_register(hdmi);
+       if (err < 0) {
+               dev_err(hdmi->dev, "failed to register audio codec: %d\n", err);
+               goto disable_vdd;
+       }
+
        return 0;
 
+disable_vdd:
+       regulator_disable(hdmi->vdd);
 disable_pll:
        regulator_disable(hdmi->pll);
 disable_hdmi:
@@ -1484,6 +1621,8 @@ static int tegra_hdmi_exit(struct host1x_client *client)
 {
        struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
 
+       tegra_hdmi_codec_unregister(hdmi);
+
        tegra_output_exit(&hdmi->output);
 
        regulator_disable(hdmi->vdd);
@@ -1608,7 +1747,6 @@ static irqreturn_t tegra_hdmi_irq(int irq, void *data)
 {
        struct tegra_hdmi *hdmi = data;
        u32 value;
-       int err;
 
        value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_INT_STATUS);
        tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_INT_STATUS);
@@ -1623,16 +1761,7 @@ static irqreturn_t tegra_hdmi_irq(int irq, void *data)
                        format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
 
                        tegra_hda_parse_format(format, &hdmi->format);
-
-                       err = tegra_hdmi_setup_audio(hdmi);
-                       if (err < 0) {
-                               tegra_hdmi_disable_audio_infoframe(hdmi);
-                               tegra_hdmi_disable_audio(hdmi);
-                       } else {
-                               tegra_hdmi_setup_audio_infoframe(hdmi);
-                               tegra_hdmi_enable_audio_infoframe(hdmi);
-                               tegra_hdmi_enable_audio(hdmi);
-                       }
+                       tegra_hdmi_reconfigure_audio(hdmi);
                } else {
                        tegra_hdmi_disable_audio_infoframe(hdmi);
                        tegra_hdmi_disable_audio(hdmi);
@@ -1660,6 +1789,8 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
        hdmi->stereo = false;
        hdmi->dvi = false;
 
+       mutex_init(&hdmi->audio_lock);
+
        hdmi->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(hdmi->clk)) {
                dev_err(&pdev->dev, "failed to get clock\n");