Hi Johan,
   Thanks for your patch。

At 2024-07-03 22:20:27, "Johan Jonker" <jbx6...@gmail.com> wrote:
>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 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 | 320 +++++++++++++++++++++++--
> 2 files changed, 300 insertions(+), 22 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..6081e1e062f2 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,9 +61,16 @@ struct rk3066_hdmi {
>
>       unsigned int tmdsclk;
>
>+      struct platform_device *audio_pdev;
>+      struct rk3066_hdmi_audio_info audio;
>+      bool audio_enable;

                s/audio_enable/audio_enabled  ?

>+
>       struct hdmi_data_info hdmi_data;
> };
>
>+static int rk3066_hdmi_audio_config(struct rk3066_hdmi *hdmi);
>+static int rk3066_hdmi_audio_codec_init(struct rk3066_hdmi *hdmi);

can we move these two function ahead abit to avoid adding declaration here ?

>+
> static struct rk3066_hdmi *encoder_to_rk3066_hdmi(struct drm_encoder *encoder)
> {
>       struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
>@@ -214,6 +228,22 @@ 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_config_video_timing(struct rk3066_hdmi *hdmi,
>                                          struct drm_display_mode *mode)
> {
>@@ -364,6 +394,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 +411,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_enable) {
>+              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 +473,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);
> }
>
>@@ -518,39 +561,260 @@ static void rk3066_hdmi_connector_destroy(struct 
>drm_connector *connector)
>       drm_connector_cleanup(connector);
> }
>
>+static int rk3066_hdmi_connector_late_register(struct drm_connector 
>*connector)
>+{
>+      struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector);
>+
>+      return rk3066_hdmi_audio_codec_init(hdmi);
>+}
>+
>+static void rk3066_hdmi_connector_early_unregister(struct drm_connector 
>*connector)
>+{
>+      struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector);
>+
>+      platform_device_unregister(hdmi->audio_pdev);
>+}
>+
> 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,
>+      .late_register          = rk3066_hdmi_connector_late_register,
>+      .early_unregister       = rk3066_hdmi_connector_early_unregister,

IT‘s better to hook to encoder->funcs->late_register,because the community are 
pushing us  to switch to the new bridge driver mode,connector maybe moved
out from the hdmi driver to display controller driver。

 > };
>
> 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_audio_config(struct rk3066_hdmi *hdmi)
> {
>-      struct drm_encoder *encoder = &hdmi->encoder.encoder;
>-      struct device *dev = hdmi->dev;
>+      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;
>
>-      encoder->possible_crtcs =
>-              drm_of_find_possible_crtcs(drm, dev->of_node);
>+      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_enable = !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);
>
>       /*
>-       * 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.
>+       * Under power mode E we need to reset the audio capture logic to
>+       * make the audio setting update.
>        */
>-      if (encoder->possible_crtcs == 0)
>-              return -EPROBE_DEFER;
>+      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_enable = 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_register(struct drm_device *drm, struct rk3066_hdmi 
>*hdmi)
>+{
>+      struct drm_encoder *encoder = &hdmi->encoder.encoder;
>
>       drm_encoder_helper_add(encoder, &rk3066_hdmi_encoder_helper_funcs);
>       drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
>@@ -740,6 +1004,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 +1013,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
>
>
>_______________________________________________
>Linux-rockchip mailing list
>linux-rockc...@lists.infradead.org
>http://lists.infradead.org/mailman/listinfo/linux-rockchip

Reply via email to