drm: zte: support hdmi audio through spdif
authorShawn Guo <shawn.guo@linaro.org>
Thu, 1 Dec 2016 09:20:31 +0000 (17:20 +0800)
committerShawn Guo <shawn.guo@linaro.org>
Sat, 28 Jan 2017 02:17:39 +0000 (10:17 +0800)
It enables HDMI audio support through SPDIF interface based on generic
hdmi-audio-codec driver.  The HDMI hardware supports more audio
interfaces than SPDIF, like I2S, which may be added later.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
drivers/gpu/drm/zte/Kconfig
drivers/gpu/drm/zte/zx_hdmi.c
drivers/gpu/drm/zte/zx_hdmi_regs.h
drivers/gpu/drm/zte/zx_vou.c
drivers/gpu/drm/zte/zx_vou.h
drivers/gpu/drm/zte/zx_vou_regs.h

index 5f9b4e7f2a36ace36687beac0fe6cb80201a3def..5b36421ef3e5590ac99e10d993d64e53f954d55a 100644 (file)
@@ -4,6 +4,7 @@ config DRM_ZTE
        select DRM_KMS_CMA_HELPER
        select DRM_KMS_FB_HELPER
        select DRM_KMS_HELPER
+       select SND_SOC_HDMI_CODEC if SND_SOC
        select VIDEOMODE_HELPERS
        help
          Choose this option to enable DRM on ZTE ZX SoCs.
index 6bf6c364811ea40c3f29e67ace80886cf4f2f11c..c2012184607355c7043651a8bf039da4d2cce853 100644 (file)
@@ -25,6 +25,8 @@
 #include <drm/drm_of.h>
 #include <drm/drmP.h>
 
+#include <sound/hdmi-codec.h>
+
 #include "zx_hdmi_regs.h"
 #include "zx_vou.h"
 
@@ -49,6 +51,7 @@ struct zx_hdmi {
        bool sink_is_hdmi;
        bool sink_has_audio;
        const struct vou_inf *inf;
+       struct platform_device *audio_pdev;
 };
 
 #define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x)
@@ -366,6 +369,142 @@ static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id)
        return IRQ_NONE;
 }
 
+static int zx_hdmi_audio_startup(struct device *dev, void *data)
+{
+       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+       struct drm_encoder *encoder = &hdmi->encoder;
+
+       vou_inf_hdmi_audio_sel(encoder->crtc, VOU_HDMI_AUD_SPDIF);
+
+       return 0;
+}
+
+static void zx_hdmi_audio_shutdown(struct device *dev, void *data)
+{
+       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+
+       /* Disable audio input */
+       hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, 0);
+}
+
+static inline int zx_hdmi_audio_get_n(unsigned int fs)
+{
+       unsigned int n;
+
+       if (fs && (fs % 44100) == 0)
+               n = 6272 * (fs / 44100);
+       else
+               n = fs * 128 / 1000;
+
+       return n;
+}
+
+static int zx_hdmi_audio_hw_params(struct device *dev,
+                                  void *data,
+                                  struct hdmi_codec_daifmt *daifmt,
+                                  struct hdmi_codec_params *params)
+{
+       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+       struct hdmi_audio_infoframe *cea = &params->cea;
+       union hdmi_infoframe frame;
+       int n;
+
+       /* We only support spdif for now */
+       if (daifmt->fmt != HDMI_SPDIF) {
+               DRM_DEV_ERROR(hdmi->dev, "invalid daifmt %d\n", daifmt->fmt);
+               return -EINVAL;
+       }
+
+       switch (params->sample_width) {
+       case 16:
+               hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK,
+                                SPDIF_SAMPLE_SIZE_16BIT);
+               break;
+       case 20:
+               hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK,
+                                SPDIF_SAMPLE_SIZE_20BIT);
+               break;
+       case 24:
+               hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK,
+                                SPDIF_SAMPLE_SIZE_24BIT);
+               break;
+       default:
+               DRM_DEV_ERROR(hdmi->dev, "invalid sample width %d\n",
+                             params->sample_width);
+               return -EINVAL;
+       }
+
+       /* CTS is calculated by hardware, and we only need to take care of N */
+       n = zx_hdmi_audio_get_n(params->sample_rate);
+       hdmi_writeb(hdmi, N_SVAL1, n & 0xff);
+       hdmi_writeb(hdmi, N_SVAL2, (n >> 8) & 0xff);
+       hdmi_writeb(hdmi, N_SVAL3, (n >> 16) & 0xf);
+
+       /* Enable spdif mode */
+       hdmi_writeb_mask(hdmi, AUD_MODE, SPDIF_EN, SPDIF_EN);
+
+       /* Enable audio input */
+       hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, AUD_IN_EN);
+
+       memcpy(&frame.audio, cea, sizeof(*cea));
+
+       return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AUDIO);
+}
+
+static int zx_hdmi_audio_digital_mute(struct device *dev, void *data,
+                                     bool enable)
+{
+       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+
+       if (enable)
+               hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE,
+                                TPI_AUD_MUTE);
+       else
+               hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE, 0);
+
+       return 0;
+}
+
+static int zx_hdmi_audio_get_eld(struct device *dev, void *data,
+                                uint8_t *buf, size_t len)
+{
+       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+       struct drm_connector *connector = &hdmi->connector;
+
+       memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
+
+       return 0;
+}
+
+static const struct hdmi_codec_ops zx_hdmi_codec_ops = {
+       .audio_startup = zx_hdmi_audio_startup,
+       .hw_params = zx_hdmi_audio_hw_params,
+       .audio_shutdown = zx_hdmi_audio_shutdown,
+       .digital_mute = zx_hdmi_audio_digital_mute,
+       .get_eld = zx_hdmi_audio_get_eld,
+};
+
+static struct hdmi_codec_pdata zx_hdmi_codec_pdata = {
+       .ops = &zx_hdmi_codec_ops,
+       .spdif = 1,
+};
+
+static int zx_hdmi_audio_register(struct zx_hdmi *hdmi)
+{
+       struct platform_device *pdev;
+
+       pdev = platform_device_register_data(hdmi->dev, HDMI_CODEC_DRV_NAME,
+                                            PLATFORM_DEVID_AUTO,
+                                            &zx_hdmi_codec_pdata,
+                                            sizeof(zx_hdmi_codec_pdata));
+       if (IS_ERR(pdev))
+               return PTR_ERR(pdev);
+
+       hdmi->audio_pdev = pdev;
+
+       return 0;
+}
+
 static int zx_hdmi_i2c_read(struct zx_hdmi *hdmi, struct i2c_msg *msg)
 {
        int len = msg->len;
@@ -566,6 +705,12 @@ static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
                return ret;
        }
 
+       ret = zx_hdmi_audio_register(hdmi);
+       if (ret) {
+               DRM_DEV_ERROR(dev, "failed to register audio: %d\n", ret);
+               return ret;
+       }
+
        ret = zx_hdmi_register(drm, hdmi);
        if (ret) {
                DRM_DEV_ERROR(dev, "failed to register hdmi: %d\n", ret);
@@ -590,6 +735,9 @@ static void zx_hdmi_unbind(struct device *dev, struct device *master,
 
        hdmi->connector.funcs->destroy(&hdmi->connector);
        hdmi->encoder.funcs->destroy(&hdmi->encoder);
+
+       if (hdmi->audio_pdev)
+               platform_device_unregister(hdmi->audio_pdev);
 }
 
 static const struct component_ops zx_hdmi_component_ops = {
index de911f66b65888c15685daa0542f5ec1fb62ca3b..c6d5d8211725c209db4958d17c611e275b6f4775 100644 (file)
 #define TPI_INFO_TRANS_RPT             BIT(6)
 #define TPI_DDC_MASTER_EN              0x06f8
 #define HW_DDC_MASTER                  BIT(7)
+#define N_SVAL1                                0xa03
+#define N_SVAL2                                0xa04
+#define N_SVAL3                                0xa05
+#define AUD_EN                         0xa13
+#define AUD_IN_EN                      BIT(0)
+#define AUD_MODE                       0xa14
+#define SPDIF_EN                       BIT(1)
+#define TPI_AUD_CONFIG                 0xa62
+#define SPDIF_SAMPLE_SIZE_SHIFT                6
+#define SPDIF_SAMPLE_SIZE_MASK         (0x3 << SPDIF_SAMPLE_SIZE_SHIFT)
+#define SPDIF_SAMPLE_SIZE_16BIT                (0x1 << SPDIF_SAMPLE_SIZE_SHIFT)
+#define SPDIF_SAMPLE_SIZE_20BIT                (0x2 << SPDIF_SAMPLE_SIZE_SHIFT)
+#define SPDIF_SAMPLE_SIZE_24BIT                (0x3 << SPDIF_SAMPLE_SIZE_SHIFT)
+#define TPI_AUD_MUTE                   BIT(4)
 
 #endif /* __ZX_HDMI_REGS_H__ */
index 73fe15c17c32406280b8a7e24ad136239c19c37e..f89ad7f72fdbd7dd2ff9480ed51ed2769a92b3f6 100644 (file)
@@ -119,6 +119,15 @@ static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc)
        return zcrtc->vou;
 }
 
+void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc,
+                           enum vou_inf_hdmi_audio aud)
+{
+       struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+       struct zx_vou_hw *vou = zcrtc->vou;
+
+       zx_writel_mask(vou->vouctl + VOU_INF_HDMI_CTRL, VOU_HDMI_AUD_MASK, aud);
+}
+
 void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc)
 {
        struct zx_crtc *zcrtc = to_zx_crtc(crtc);
index 349e06cd86f48517bdbe94af3a788acd1d1a5e9c..e571b888a3cad3b02d9026339dfc307f9a3e26e5 100644 (file)
@@ -30,6 +30,14 @@ enum vou_inf_data_sel {
        VOU_RGB_666     = 3,
 };
 
+enum vou_inf_hdmi_audio {
+       VOU_HDMI_AUD_SPDIF      = BIT(0),
+       VOU_HDMI_AUD_I2S        = BIT(1),
+       VOU_HDMI_AUD_DSD        = BIT(2),
+       VOU_HDMI_AUD_HBR        = BIT(3),
+       VOU_HDMI_AUD_PARALLEL   = BIT(4),
+};
+
 struct vou_inf {
        enum vou_inf_id id;
        enum vou_inf_data_sel data_sel;
@@ -37,6 +45,8 @@ struct vou_inf {
        u32 clocks_sel_bits;
 };
 
+void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc,
+                           enum vou_inf_hdmi_audio aud);
 void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc);
 void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc);
 
index f44e7a4ae4417b58ea89b56c9d5ab3a27443c803..15b73cd3a612d1680b5f2caa56cae08a8048099f 100644 (file)
 #define VOU_CLK_GL0_SEL                        BIT(4)
 #define VOU_CLK_REQEN                  0x20
 #define VOU_CLK_EN                     0x24
+#define VOU_INF_HDMI_CTRL              0x30
+#define VOU_HDMI_AUD_MASK              0x1f
 
 /* OTFPPU_CTRL registers */
 #define OTFPPU_RSZ_DATA_SOURCE         0x04