Add sound support to the RK3066 HDMI driver.
The HDMI TX audio source is connected to I2S_8CH.

Signed-off-by: Zheng Yang <zhengy...@rock-chips.com>
Signed-off-by: Johan Jonker <jbx6...@gmail.com>
---

Changed V10:
  s/audio_enable/audio_enabled
  hook to encoder->funcs->late_register
  move audio functions up to avoid adding a declaration

Changed V9:
  Use late_register and early_unregister hooks to
  (un)register the "hdmi-audio-codec" driver.
  restyle

Changed V8:
  return -EPROBE_DEFER as early as possible
  move rk3066_hdmi_audio_codec_init() function after rk3066_hdmi_register()
  restyle

Changed V7:
  rebase
---
 drivers/gpu/drm/rockchip/Kconfig       |   2 +
 drivers/gpu/drm/rockchip/rk3066_hdmi.c | 330 +++++++++++++++++++++++--
 2 files changed, 306 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index 1bf3e2829cd0..a32ee558408c 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -102,6 +102,8 @@ config ROCKCHIP_RGB
 config ROCKCHIP_RK3066_HDMI
        bool "Rockchip specific extensions for RK3066 HDMI"
        depends on DRM_ROCKCHIP
+       select SND_SOC_HDMI_CODEC if SND_SOC
+       select SND_SOC_ROCKCHIP_I2S if SND_SOC
        help
          This selects support for Rockchip SoC specific extensions
          for the RK3066 HDMI driver. If you want to enable
diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c 
b/drivers/gpu/drm/rockchip/rk3066_hdmi.c
index 784de990da1b..e3b8faf89ae2 100644
--- a/drivers/gpu/drm/rockchip/rk3066_hdmi.c
+++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c
@@ -15,12 +15,20 @@
 #include <linux/platform_device.h>
 #include <linux/regmap.h>

+#include <sound/hdmi-codec.h>
+
 #include "rk3066_hdmi.h"

 #include "rockchip_drm_drv.h"

 #define DEFAULT_PLLA_RATE 30000000

+struct rk3066_hdmi_audio_info {
+       int channels;
+       int sample_rate;
+       int sample_width;
+};
+
 struct hdmi_data_info {
        int vic; /* The CEA Video ID (VIC) of the current drm display mode. */
        unsigned int enc_out_format;
@@ -40,7 +48,6 @@ struct rk3066_hdmi_i2c {

 struct rk3066_hdmi {
        struct device *dev;
-       struct drm_device *drm_dev;
        struct regmap *grf_regmap;
        int irq;
        struct clk *hclk;
@@ -54,6 +61,10 @@ struct rk3066_hdmi {

        unsigned int tmdsclk;

+       struct platform_device *audio_pdev;
+       struct rk3066_hdmi_audio_info audio;
+       bool audio_enabled;
+
        struct hdmi_data_info hdmi_data;
 };

@@ -214,6 +225,241 @@ static int rk3066_hdmi_config_avi(struct rk3066_hdmi 
*hdmi,
                                        HDMI_INFOFRAME_AVI, 0, 0, 0);
 }

+static int rk3066_hdmi_config_aai(struct rk3066_hdmi *hdmi)
+{
+       union hdmi_infoframe frame;
+       int rc;
+
+       rc = hdmi_audio_infoframe_init(&frame.audio);
+
+       frame.audio.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+       frame.audio.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+       frame.audio.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+       frame.audio.channels = hdmi->audio.channels;
+
+       return rk3066_hdmi_upload_frame(hdmi, rc, &frame,
+                                       HDMI_INFOFRAME_AAI, 0, 0, 0);
+}
+
+static int rk3066_hdmi_audio_config(struct rk3066_hdmi *hdmi)
+{
+       u32 rate, channel, word_length, N, CTS;
+       struct rk3066_hdmi_audio_info *audio = &hdmi->audio;
+       u64 tmp;
+
+       if (audio->channels < 3)
+               channel = HDMI_AUDIO_I2S_CHANNEL_1_2;
+       else if (audio->channels < 5)
+               channel = HDMI_AUDIO_I2S_CHANNEL_3_4;
+       else if (audio->channels < 7)
+               channel = HDMI_AUDIO_I2S_CHANNEL_5_6;
+       else
+               channel = HDMI_AUDIO_I2S_CHANNEL_7_8;
+
+       switch (audio->sample_rate) {
+       case 32000:
+               rate = HDMI_AUDIO_SAMPLE_FRE_32000;
+               N = N_32K;
+               break;
+       case 44100:
+               rate = HDMI_AUDIO_SAMPLE_FRE_44100;
+               N = N_441K;
+               break;
+       case 48000:
+               rate = HDMI_AUDIO_SAMPLE_FRE_48000;
+               N = N_48K;
+               break;
+       case 88200:
+               rate = HDMI_AUDIO_SAMPLE_FRE_88200;
+               N = N_882K;
+               break;
+       case 96000:
+               rate = HDMI_AUDIO_SAMPLE_FRE_96000;
+               N = N_96K;
+               break;
+       case 176400:
+               rate = HDMI_AUDIO_SAMPLE_FRE_176400;
+               N = N_1764K;
+               break;
+       case 192000:
+               rate = HDMI_AUDIO_SAMPLE_FRE_192000;
+               N = N_192K;
+               break;
+       default:
+               DRM_DEV_ERROR(hdmi->dev, "no support for sample rate %d\n",
+                             audio->sample_rate);
+               return -ENOENT;
+       }
+
+       switch (audio->sample_width) {
+       case 16:
+               word_length = 0x02;
+               break;
+       case 20:
+               word_length = 0x0a;
+               break;
+       case 24:
+               word_length = 0x0b;
+               break;
+       default:
+               DRM_DEV_ERROR(hdmi->dev, "no support for word length %d\n",
+                             audio->sample_width);
+               return -ENOENT;
+       }
+
+       tmp = (u64)hdmi->tmdsclk * N;
+       do_div(tmp, 128 * audio->sample_rate);
+       CTS = tmp;
+
+       /* Set_audio source I2S. */
+       hdmi_writeb(hdmi, HDMI_AUDIO_CTRL1, 0x00);
+       hdmi_writeb(hdmi, HDMI_AUDIO_CTRL2, 0x40);
+       hdmi_writeb(hdmi, HDMI_I2S_AUDIO_CTRL,
+                   HDMI_AUDIO_I2S_FORMAT_STANDARD | channel);
+       hdmi_writeb(hdmi, HDMI_I2S_SWAP, 0x00);
+       hdmi_modb(hdmi, HDMI_AV_CTRL1, HDMI_AUDIO_SAMPLE_FRE_MASK, rate);
+       hdmi_writeb(hdmi, HDMI_AUDIO_SRC_NUM_AND_LENGTH, word_length);
+
+       /* Set N value. */
+       hdmi_modb(hdmi, HDMI_LR_SWAP_N3,
+                 HDMI_AUDIO_N_19_16_MASK, (N >> 16) & 0x0F);
+       hdmi_writeb(hdmi, HDMI_N2, (N >> 8) & 0xFF);
+       hdmi_writeb(hdmi, HDMI_N1, N & 0xFF);
+
+       /* Set CTS value. */
+       hdmi_writeb(hdmi, HDMI_CTS_EXT1, CTS & 0xff);
+       hdmi_writeb(hdmi, HDMI_CTS_EXT2, (CTS >> 8) & 0xff);
+       hdmi_writeb(hdmi, HDMI_CTS_EXT3, (CTS >> 16) & 0xff);
+
+       if (audio->channels > 2)
+               hdmi_modb(hdmi, HDMI_LR_SWAP_N3,
+                         HDMI_AUDIO_LR_SWAP_MASK,
+                         HDMI_AUDIO_LR_SWAP_SUBPACKET1);
+       rate = (~(rate >> 4)) & 0x0f;
+       hdmi_writeb(hdmi, HDMI_AUDIO_STA_BIT_CTRL1, rate);
+       hdmi_writeb(hdmi, HDMI_AUDIO_STA_BIT_CTRL2, 0);
+
+       return rk3066_hdmi_config_aai(hdmi);
+}
+
+static int rk3066_hdmi_audio_hw_params(struct device *dev, void *d,
+                                      struct hdmi_codec_daifmt *daifmt,
+                                      struct hdmi_codec_params *params)
+{
+       struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+       struct drm_display_info *display = &hdmi->connector.display_info;
+
+       if (!display->has_audio) {
+               DRM_DEV_ERROR(hdmi->dev, "no audio support\n");
+               return -ENODEV;
+       }
+
+       if (!hdmi->encoder.encoder.crtc)
+               return -ENODEV;
+
+       switch (daifmt->fmt) {
+       case HDMI_I2S:
+               break;
+       default:
+               DRM_DEV_ERROR(dev, "invalid format %d\n", daifmt->fmt);
+               return -EINVAL;
+       }
+
+       hdmi->audio.channels = params->channels;
+       hdmi->audio.sample_rate = params->sample_rate;
+       hdmi->audio.sample_width = params->sample_width;
+
+       return rk3066_hdmi_audio_config(hdmi);
+}
+
+static void rk3066_hdmi_audio_shutdown(struct device *dev, void *d)
+{
+       /* Do nothing. */
+}
+
+static int rk3066_hdmi_audio_mute_stream(struct device *dev, void *d, bool 
mute, int direction)
+{
+       struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+       struct drm_display_info *display = &hdmi->connector.display_info;
+
+       if (!display->has_audio) {
+               DRM_DEV_ERROR(hdmi->dev, "no audio support\n");
+               return -ENODEV;
+       }
+
+       hdmi->audio_enabled = !mute;
+
+       if (mute)
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+                         HDMI_AUDIO_DISABLE, HDMI_AUDIO_DISABLE);
+       else
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_AUDIO_DISABLE, 0);
+
+       /*
+        * Under power mode E we need to reset the audio capture logic to
+        * make the audio setting update.
+        */
+       if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_E) {
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+                         HDMI_AUDIO_CP_LOGIC_RESET_MASK,
+                         HDMI_AUDIO_CP_LOGIC_RESET);
+               usleep_range(900, 1000);
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+                         HDMI_AUDIO_CP_LOGIC_RESET_MASK, 0);
+       }
+
+       return 0;
+}
+
+static int rk3066_hdmi_audio_get_eld(struct device *dev, void *d, u8 *buf, 
size_t len)
+{
+       struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+       struct drm_mode_config *config = 
&hdmi->encoder.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 (&hdmi->encoder.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 rk3066_hdmi_audio_codec_ops = {
+       .hw_params       = rk3066_hdmi_audio_hw_params,
+       .audio_shutdown  = rk3066_hdmi_audio_shutdown,
+       .mute_stream     = rk3066_hdmi_audio_mute_stream,
+       .get_eld         = rk3066_hdmi_audio_get_eld,
+       .no_capture_mute = 1,
+};
+
+static int rk3066_hdmi_audio_codec_init(struct rk3066_hdmi *hdmi)
+{
+       struct hdmi_codec_pdata rk3066_hdmi_codec_data = {
+               .i2s = 1,
+               .ops = &rk3066_hdmi_audio_codec_ops,
+               .max_i2s_channels = 8,
+       };
+
+       hdmi->audio.channels = 2;
+       hdmi->audio.sample_rate = 48000;
+       hdmi->audio.sample_width = 16;
+       hdmi->audio_enabled = false;
+       hdmi->audio_pdev = platform_device_register_data(hdmi->dev,
+                                                        HDMI_CODEC_DRV_NAME,
+                                                        PLATFORM_DEVID_NONE,
+                                                        
&rk3066_hdmi_codec_data,
+                                                        
sizeof(rk3066_hdmi_codec_data));
+
+       return PTR_ERR_OR_ZERO(hdmi->audio_pdev);
+}
+
 static int rk3066_hdmi_config_video_timing(struct rk3066_hdmi *hdmi,
                                           struct drm_display_mode *mode)
 {
@@ -364,6 +610,7 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
                hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK,
                          HDMI_VIDEO_MODE_HDMI);
                rk3066_hdmi_config_avi(hdmi, mode);
+               rk3066_hdmi_audio_config(hdmi);
        } else {
                hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, 0);
        }
@@ -380,9 +627,20 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
         */
        rk3066_hdmi_i2c_init(hdmi);

-       /* Unmute video output. */
+       /* Unmute video and audio output. */
        hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
                  HDMI_VIDEO_AUDIO_DISABLE_MASK, HDMI_AUDIO_DISABLE);
+       if (hdmi->audio_enabled) {
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_AUDIO_DISABLE, 0);
+               /* Reset audio capture logic. */
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+                         HDMI_AUDIO_CP_LOGIC_RESET_MASK,
+                         HDMI_AUDIO_CP_LOGIC_RESET);
+               usleep_range(900, 1000);
+               hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+                         HDMI_AUDIO_CP_LOGIC_RESET_MASK, 0);
+       }
+
        return 0;
 }

@@ -431,6 +689,7 @@ static void rk3066_hdmi_encoder_disable(struct drm_encoder 
*encoder,
                          HDMI_AUDIO_CP_LOGIC_RESET);
                usleep_range(500, 510);
        }
+
        rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_A);
 }

@@ -447,6 +706,26 @@ rk3066_hdmi_encoder_atomic_check(struct drm_encoder 
*encoder,
        return 0;
 }

+static int rk3066_hdmi_encoder_late_register(struct drm_encoder *encoder)
+{
+       struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder);
+
+       return rk3066_hdmi_audio_codec_init(hdmi);
+}
+
+static void rk3066_hdmi_encoder_early_unregister(struct drm_encoder *encoder)
+{
+       struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder);
+
+       platform_device_unregister(hdmi->audio_pdev);
+}
+
+static const struct drm_encoder_funcs rk3066_hdmi_encoder_funcs = {
+       .destroy          = drm_encoder_cleanup,
+       .late_register    = rk3066_hdmi_encoder_late_register,
+       .early_unregister = rk3066_hdmi_encoder_early_unregister,
+};
+
 static const
 struct drm_encoder_helper_funcs rk3066_hdmi_encoder_helper_funcs = {
        .atomic_check   = rk3066_hdmi_encoder_atomic_check,
@@ -519,41 +798,28 @@ static void rk3066_hdmi_connector_destroy(struct 
drm_connector *connector)
 }

 static const struct drm_connector_funcs rk3066_hdmi_connector_funcs = {
-       .fill_modes = rk3066_hdmi_probe_single_connector_modes,
-       .detect = rk3066_hdmi_connector_detect,
-       .destroy = rk3066_hdmi_connector_destroy,
-       .reset = drm_atomic_helper_connector_reset,
+       .fill_modes             = rk3066_hdmi_probe_single_connector_modes,
+       .detect                 = rk3066_hdmi_connector_detect,
+       .destroy                = rk3066_hdmi_connector_destroy,
+       .reset                  = drm_atomic_helper_connector_reset,
        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+       .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
 };

 static const
 struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = {
-       .get_modes = rk3066_hdmi_connector_get_modes,
-       .mode_valid = rk3066_hdmi_connector_mode_valid,
+       .get_modes    = rk3066_hdmi_connector_get_modes,
+       .mode_valid   = rk3066_hdmi_connector_mode_valid,
        .best_encoder = rk3066_hdmi_connector_best_encoder,
 };

-static int
-rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
+static int rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi 
*hdmi)
 {
        struct drm_encoder *encoder = &hdmi->encoder.encoder;
-       struct device *dev = hdmi->dev;
-
-       encoder->possible_crtcs =
-               drm_of_find_possible_crtcs(drm, dev->of_node);
-
-       /*
-        * If we failed to find the CRTC(s) which this encoder is
-        * supposed to be connected to, it's because the CRTC has
-        * not been registered yet.  Defer probing, and hope that
-        * the required CRTC is added later.
-        */
-       if (encoder->possible_crtcs == 0)
-               return -EPROBE_DEFER;

        drm_encoder_helper_add(encoder, &rk3066_hdmi_encoder_helper_funcs);
-       drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+       drm_encoder_init(drm, encoder, &rk3066_hdmi_encoder_funcs,
+                        DRM_MODE_ENCODER_TMDS, NULL);

        hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;

@@ -740,6 +1006,7 @@ static int rk3066_hdmi_bind(struct device *dev, struct 
device *master,
 {
        struct platform_device *pdev = to_platform_device(dev);
        struct drm_device *drm = data;
+       struct drm_encoder *encoder;
        struct rk3066_hdmi *hdmi;
        int irq;
        int ret;
@@ -748,8 +1015,19 @@ static int rk3066_hdmi_bind(struct device *dev, struct 
device *master,
        if (!hdmi)
                return -ENOMEM;

+       encoder = &hdmi->encoder.encoder;
+       encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+
+       /*
+        * If we failed to find the CRTC(s) which this encoder is
+        * supposed to be connected to, it's because the CRTC has
+        * not been registered yet. Defer probing and hope that
+        * the required CRTC is added later.
+        */
+       if (encoder->possible_crtcs == 0)
+               return -EPROBE_DEFER;
+
        hdmi->dev = dev;
-       hdmi->drm_dev = drm;
        hdmi->regs = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(hdmi->regs))
                return PTR_ERR(hdmi->regs);
--
2.39.2

Reply via email to