drm: bridge: dw_hdmi: Audio: Add General Parallel Audio (GPA) driver
authorSandor Yu <Sandor.yu@nxp.com>
Fri, 15 Apr 2022 02:42:50 +0000 (10:42 +0800)
committerRobert Foss <robert.foss@linaro.org>
Tue, 19 Apr 2022 16:23:48 +0000 (18:23 +0200)
General Parallel Audio (GPA) interface is one of the supported
audio interface for synopsys HDMI module, which has verified for
i.MX8MPlus platform.
This is initial version for GPA.

Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Signed-off-by: Robert Foss <robert.foss@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/f21ba3e8c4d9d028ac74c6f3c588ddbffe739399.1649989179.git.Sandor.yu@nxp.com
drivers/gpu/drm/bridge/synopsys/Kconfig
drivers/gpu/drm/bridge/synopsys/Makefile
drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c [new file with mode: 0644]
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
include/drm/bridge/dw_hdmi.h

index 21a1be3ced0f38ef8127a3a83f2981a4f99c118f..a4a31b669b65a16c367b5aeafad5336b26662310 100644 (file)
@@ -25,6 +25,16 @@ config DRM_DW_HDMI_I2S_AUDIO
          Support the I2S Audio interface which is part of the Synopsys
          Designware HDMI block.
 
+config DRM_DW_HDMI_GP_AUDIO
+       tristate "Synopsys Designware GP Audio interface"
+       depends on DRM_DW_HDMI && SND
+       select SND_PCM
+       select SND_PCM_ELD
+       select SND_PCM_IEC958
+       help
+         Support the GP Audio interface which is part of the Synopsys
+         Designware HDMI block.
+
 config DRM_DW_HDMI_CEC
        tristate "Synopsis Designware CEC interface"
        depends on DRM_DW_HDMI
index 91d746ad5de12bb8055cb57b74d2a4700fabb8db..ce715562e9e522e8036d0efc6943f5c0799c2af6 100644 (file)
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o
 obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
 obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
 
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c
new file mode 100644 (file)
index 0000000..11ea1c8
--- /dev/null
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * dw-hdmi-gp-audio.c
+ *
+ * Copyright 2020-2022 NXP
+ */
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_connector.h>
+
+#include <sound/hdmi-codec.h>
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_drm_eld.h>
+#include <sound/pcm_iec958.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "dw-hdmi-audio.h"
+
+#define DRIVER_NAME "dw-hdmi-gp-audio"
+#define DRV_NAME    "hdmi-gp-audio"
+
+struct snd_dw_hdmi {
+       struct dw_hdmi_audio_data data;
+       struct platform_device  *audio_pdev;
+       unsigned int pos;
+};
+
+struct dw_hdmi_channel_conf {
+       u8 conf1;
+       u8 ca;
+};
+
+/*
+ * The default mapping of ALSA channels to HDMI channels and speaker
+ * allocation bits.  Note that we can't do channel remapping here -
+ * channels must be in the same order.
+ *
+ * Mappings for alsa-lib pcm/surround*.conf files:
+ *
+ *             Front   Sur4.0  Sur4.1  Sur5.0  Sur5.1  Sur7.1
+ * Channels    2       4       6       6       6       8
+ *
+ * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel:
+ *
+ *                             Number of ALSA channels
+ * ALSA Channel        2       3       4       5       6       7       8
+ * 0           FL:0    =       =       =       =       =       =
+ * 1           FR:1    =       =       =       =       =       =
+ * 2                   FC:3    RL:4    LFE:2   =       =       =
+ * 3                           RR:5    RL:4    FC:3    =       =
+ * 4                                   RR:5    RL:4    =       =
+ * 5                                           RR:5    =       =
+ * 6                                                   RC:6    =
+ * 7                                                   RLC/FRC RLC/FRC
+ */
+static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = {
+       { 0x03, 0x00 }, /* FL,FR */
+       { 0x0b, 0x02 }, /* FL,FR,FC */
+       { 0x33, 0x08 }, /* FL,FR,RL,RR */
+       { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */
+       { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */
+       { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */
+       { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */
+};
+
+static int audio_hw_params(struct device *dev,  void *data,
+                          struct hdmi_codec_daifmt *daifmt,
+                          struct hdmi_codec_params *params)
+{
+       struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+       int ret = 0;
+       u8 ca;
+
+       dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate);
+
+       ca = default_hdmi_channel_config[params->channels - 2].ca;
+
+       dw_hdmi_set_channel_count(dw->data.hdmi, params->channels);
+       dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);
+
+       dw_hdmi_set_sample_non_pcm(dw->data.hdmi,
+                                  params->iec.status[0] & IEC958_AES0_NONAUDIO);
+       dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width);
+
+       return ret;
+}
+
+static void audio_shutdown(struct device *dev, void *data)
+{
+}
+
+static int audio_mute_stream(struct device *dev, void *data,
+                            bool enable, int direction)
+{
+       struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+       int ret = 0;
+
+       if (!enable)
+               dw_hdmi_audio_enable(dw->data.hdmi);
+       else
+               dw_hdmi_audio_disable(dw->data.hdmi);
+
+       return ret;
+}
+
+static int audio_get_eld(struct device *dev, void *data,
+                        u8 *buf, size_t len)
+{
+       struct dw_hdmi_audio_data *audio = data;
+       u8 *eld;
+
+       eld = audio->get_eld(audio->hdmi);
+       if (eld)
+               memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
+       else
+               /* Pass en empty ELD if connector not available */
+               memset(buf, 0, len);
+
+       return 0;
+}
+
+static int audio_hook_plugged_cb(struct device *dev, void *data,
+                                hdmi_codec_plugged_cb fn,
+                                struct device *codec_dev)
+{
+       struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+
+       return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev);
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+       .hw_params = audio_hw_params,
+       .audio_shutdown = audio_shutdown,
+       .mute_stream = audio_mute_stream,
+       .get_eld = audio_get_eld,
+       .hook_plugged_cb = audio_hook_plugged_cb,
+};
+
+static int snd_dw_hdmi_probe(struct platform_device *pdev)
+{
+       struct dw_hdmi_audio_data *data = pdev->dev.platform_data;
+       struct snd_dw_hdmi *dw;
+
+       const struct hdmi_codec_pdata codec_data = {
+               .i2s = 1,
+               .spdif = 0,
+               .ops = &audio_codec_ops,
+               .max_i2s_channels = 8,
+               .data = data,
+       };
+
+       dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL);
+       if (!dw)
+               return -ENOMEM;
+
+       dw->data = *data;
+
+       platform_set_drvdata(pdev, dw);
+
+       dw->audio_pdev = platform_device_register_data(&pdev->dev,
+                                                      HDMI_CODEC_DRV_NAME, 1,
+                                                      &codec_data,
+                                                      sizeof(codec_data));
+
+       return PTR_ERR_OR_ZERO(dw->audio_pdev);
+}
+
+static int snd_dw_hdmi_remove(struct platform_device *pdev)
+{
+       struct snd_dw_hdmi *dw = platform_get_drvdata(pdev);
+
+       platform_device_unregister(dw->audio_pdev);
+
+       return 0;
+}
+
+static struct platform_driver snd_dw_hdmi_driver = {
+       .probe  = snd_dw_hdmi_probe,
+       .remove = snd_dw_hdmi_remove,
+       .driver = {
+               .name = DRIVER_NAME,
+       },
+};
+
+module_platform_driver(snd_dw_hdmi_driver);
+
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
index 3500b4b9829761471aa56724ca1f3e0600f8e938..f3f82a04c25283e236adf008fb811d23a2087bbd 100644 (file)
@@ -191,7 +191,10 @@ struct dw_hdmi {
 
        spinlock_t audio_lock;
        struct mutex audio_mutex;
+       unsigned int sample_non_pcm;
+       unsigned int sample_width;
        unsigned int sample_rate;
+       unsigned int channels;
        unsigned int audio_cts;
        unsigned int audio_n;
        bool audio_enable;
@@ -589,6 +592,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
                        n = 4096;
                else if (pixel_clk == 74176000 || pixel_clk == 148352000)
                        n = 11648;
+               else if (pixel_clk == 297000000)
+                       n = 3072;
                else
                        n = 4096;
                n *= mult;
@@ -601,6 +606,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
                        n = 17836;
                else if (pixel_clk == 148352000)
                        n = 8918;
+               else if (pixel_clk == 297000000)
+                       n = 4704;
                else
                        n = 6272;
                n *= mult;
@@ -615,6 +622,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
                        n = 11648;
                else if (pixel_clk == 148352000)
                        n = 5824;
+               else if (pixel_clk == 297000000)
+                       n = 5120;
                else
                        n = 6144;
                n *= mult;
@@ -659,8 +668,8 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
 
        config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
 
-       /* Only compute CTS when using internal AHB audio */
-       if (config3 & HDMI_CONFIG3_AHBAUDDMA) {
+       /* Compute CTS when using internal AHB audio or General Parallel audio*/
+       if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) {
                /*
                 * Compute the CTS value from the N value.  Note that CTS and N
                 * can be up to 20 bits in total, so we need 64-bit math.  Also
@@ -702,6 +711,22 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi)
        mutex_unlock(&hdmi->audio_mutex);
 }
 
+void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width)
+{
+       mutex_lock(&hdmi->audio_mutex);
+       hdmi->sample_width = width;
+       mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_width);
+
+void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm)
+{
+       mutex_lock(&hdmi->audio_mutex);
+       hdmi->sample_non_pcm = non_pcm;
+       mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm);
+
 void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate)
 {
        mutex_lock(&hdmi->audio_mutex);
@@ -717,6 +742,7 @@ void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt)
        u8 layout;
 
        mutex_lock(&hdmi->audio_mutex);
+       hdmi->channels = cnt;
 
        /*
         * For >2 channel PCM audio, we need to select layout 1
@@ -765,6 +791,89 @@ static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi)
        return hdmi->curr_conn->eld;
 }
 
+static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi)
+{
+       const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
+       int sample_freq = 0x2, org_sample_freq = 0xD;
+       int ch_mask = BIT(hdmi->channels) - 1;
+
+       switch (hdmi->sample_rate) {
+       case 32000:
+               sample_freq = 0x03;
+               org_sample_freq = 0x0C;
+               break;
+       case 44100:
+               sample_freq = 0x00;
+               org_sample_freq = 0x0F;
+               break;
+       case 48000:
+               sample_freq = 0x02;
+               org_sample_freq = 0x0D;
+               break;
+       case 88200:
+               sample_freq = 0x08;
+               org_sample_freq = 0x07;
+               break;
+       case 96000:
+               sample_freq = 0x0A;
+               org_sample_freq = 0x05;
+               break;
+       case 176400:
+               sample_freq = 0x0C;
+               org_sample_freq = 0x03;
+               break;
+       case 192000:
+               sample_freq = 0x0E;
+               org_sample_freq = 0x01;
+               break;
+       default:
+               break;
+       }
+
+       hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
+       hdmi_enable_audio_clk(hdmi, true);
+
+       hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNLS0);
+       hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNLS2);
+       hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS3);
+       hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS4);
+       hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS5);
+       hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS6);
+       hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNLS7);
+       hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNLS8);
+
+       hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1);
+       hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2);
+       hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0);
+
+       hdmi_modb(hdmi,  0x3, 0x3, HDMI_FC_DATAUTO3);
+
+       /* hbr */
+       if (hdmi->sample_rate == 192000 && hdmi->channels == 8 &&
+           hdmi->sample_width == 32 && hdmi->sample_non_pcm)
+               hdmi_modb(hdmi, 0x01, 0x01, HDMI_GP_CONF2);
+
+       if (pdata->enable_audio)
+               pdata->enable_audio(hdmi,
+                                           hdmi->channels,
+                                           hdmi->sample_width,
+                                           hdmi->sample_rate,
+                                           hdmi->sample_non_pcm);
+}
+
+static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi)
+{
+       const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
+
+       hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0);
+
+       hdmi_modb(hdmi,  0, 0x3, HDMI_FC_DATAUTO3);
+       if (pdata->disable_audio)
+               pdata->disable_audio(hdmi);
+
+       hdmi_enable_audio_clk(hdmi, false);
+}
+
 static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi)
 {
        hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
@@ -3258,6 +3367,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
        hdmi->plat_data = plat_data;
        hdmi->dev = dev;
        hdmi->sample_rate = 48000;
+       hdmi->channels = 2;
        hdmi->disabled = true;
        hdmi->rxsense = true;
        hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
@@ -3481,6 +3591,24 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
                pdevinfo.size_data = sizeof(audio);
                pdevinfo.dma_mask = DMA_BIT_MASK(32);
                hdmi->audio = platform_device_register_full(&pdevinfo);
+       } else if (iores && config3 & HDMI_CONFIG3_GPAUD) {
+               struct dw_hdmi_audio_data audio;
+
+               audio.phys = iores->start;
+               audio.base = hdmi->regs;
+               audio.irq = irq;
+               audio.hdmi = hdmi;
+               audio.get_eld = hdmi_audio_get_eld;
+
+               hdmi->enable_audio = dw_hdmi_gp_audio_enable;
+               hdmi->disable_audio = dw_hdmi_gp_audio_disable;
+
+               pdevinfo.name = "dw-hdmi-gp-audio";
+               pdevinfo.id = PLATFORM_DEVID_NONE;
+               pdevinfo.data = &audio;
+               pdevinfo.size_data = sizeof(audio);
+               pdevinfo.dma_mask = DMA_BIT_MASK(32);
+               hdmi->audio = platform_device_register_full(&pdevinfo);
        }
 
        if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) {
index 18df3e11955308636f45df88a57cb4d0a56fc973..af43a0414b78f402dceb82ee3fdac8638823feab 100644 (file)
 #define HDMI_FC_SPDDEVICEINF                    0x1062
 #define HDMI_FC_AUDSCONF                        0x1063
 #define HDMI_FC_AUDSSTAT                        0x1064
-#define HDMI_FC_AUDSCHNLS7                      0x106e
-#define HDMI_FC_AUDSCHNLS8                      0x106f
+#define HDMI_FC_AUDSV                           0x1065
+#define HDMI_FC_AUDSU                           0x1066
+#define HDMI_FC_AUDSCHNLS0                       0x1067
+#define HDMI_FC_AUDSCHNLS1                       0x1068
+#define HDMI_FC_AUDSCHNLS2                       0x1069
+#define HDMI_FC_AUDSCHNLS3                       0x106A
+#define HDMI_FC_AUDSCHNLS4                       0x106B
+#define HDMI_FC_AUDSCHNLS5                       0x106C
+#define HDMI_FC_AUDSCHNLS6                       0x106D
+#define HDMI_FC_AUDSCHNLS7                       0x106E
+#define HDMI_FC_AUDSCHNLS8                       0x106F
 #define HDMI_FC_DATACH0FILL                     0x1070
 #define HDMI_FC_DATACH1FILL                     0x1071
 #define HDMI_FC_DATACH2FILL                     0x1072
index 70082f80a8c82c70f1a5fa45ccaf7c19f478726a..f668e75fbabe6191e7fb33dd032df6321b5b8679 100644 (file)
@@ -143,6 +143,11 @@ struct dw_hdmi_plat_data {
                                           const struct drm_display_info *info,
                                           const struct drm_display_mode *mode);
 
+       /* Platform-specific audio enable/disable (optional) */
+       void (*enable_audio)(struct dw_hdmi *hdmi, int channel,
+                            int width, int rate, int non_pcm);
+       void (*disable_audio)(struct dw_hdmi *hdmi);
+
        /* Vendor PHY support */
        const struct dw_hdmi_phy_ops *phy_ops;
        const char *phy_name;
@@ -173,6 +178,8 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
 
 int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
                           struct device *codec_dev);
+void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm);
+void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width);
 void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate);
 void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt);
 void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, u8 *channel_status);