[PATCH v10] drm/rockchip: rk3066_hdmi: add sound support

Andy Yan andyshrk at 163.com
Sun Jul 7 19:36:47 PDT 2024



Hi Johan,
    Thanks for your patch.

At 2024-07-04 21:48:13, "Johan Jonker" <jbx6244 at 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 <zhengyang at rock-chips.com>
>Signed-off-by: Johan Jonker <jbx6244 at gmail.com>

   Reviewed-by: Andy Yan <andy.yan at rock-chips.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


More information about the Linux-rockchip mailing list