diff mbox series

[v7] drm/rockchip: rk3066_hdmi: add sound support

Message ID 5c651b3f-fe30-4874-98ed-044f7c62dd97@gmail.com (mailing list archive)
State New
Headers show
Series [v7] drm/rockchip: rk3066_hdmi: add sound support | expand

Commit Message

Johan Jonker June 28, 2024, 9:23 a.m. UTC
Add sound support to the RK3066 HDMI driver.
The HDMI TX audio source is connected to I2S_8CH.

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

Changed V7:
  rebase
---
 drivers/gpu/drm/rockchip/Kconfig       |   2 +
 drivers/gpu/drm/rockchip/rk3066_hdmi.c | 274 ++++++++++++++++++++++++-
 2 files changed, 275 insertions(+), 1 deletion(-)

--
2.39.2

Comments

Andy Yan June 28, 2024, 1:08 p.m. UTC | #1
Hi Johan,

At 2024-06-28 17:23:39, "Johan Jonker" <jbx6244@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@rock-chips.com>
>Signed-off-by: Johan Jonker <jbx6244@gmail.com>
>---
>
>Changed V7:
>  rebase
>---
> drivers/gpu/drm/rockchip/Kconfig       |   2 +
> drivers/gpu/drm/rockchip/rk3066_hdmi.c | 274 ++++++++++++++++++++++++-
> 2 files changed, 275 insertions(+), 1 deletion(-)
>
>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..d3128b787629 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 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;
>@@ -54,9 +62,16 @@ struct rk3066_hdmi {
>
> 	unsigned int tmdsclk;
>
>+	struct platform_device *audio_pdev;
>+	stru

......

>+
>+	return ret;
>+}
>+
>+static const struct hdmi_codec_ops 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 device *dev)
>+{
>+	struct hdmi_codec_pdata codec_data = {
>+		.i2s = 1,
>+		.ops = &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(dev,
>+					      HDMI_CODEC_DRV_NAME,
>+					      PLATFORM_DEVID_NONE,
>+					      &codec_data,
>+					      sizeof(codec_data));
>+
>+	return PTR_ERR_OR_ZERO(hdmi->audio_pdev);
>+}
>+
> static int
> rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
> {
>@@ -566,6 +834,8 @@ rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
>
> 	drm_connector_attach_encoder(&hdmi->connector, encoder);
>
>+	rk3066_hdmi_audio_codec_init(hdmi, dev);


According to Documentation/driver-api/driver-model/driver.rst,

It is best not to register at the bind callback:

.. warning::
      -EPROBE_DEFER must not be returned if probe() has already created
      child devices, even if those child devices are removed again
      in a cleanup path. If -EPROBE_DEFER is returned after a child
      device has been registered, it may result in an infinite loop of
      .probe() calls to the same driver.

For example:
vop_probe --》component_add--》rk3066_hdmi_bind--》rk3066_hdmi_audio_codec_init--》hdmi_codec_probe--》rockchip_rgb_init(DEFER when panel not ready)

This  may result in an infinite loop of probe


>+
> 	return 0;
> }
>
>@@ -813,6 +1083,7 @@ static int rk3066_hdmi_bind(struct device *dev, struct device *master,
> 	return 0;
>
> err_cleanup_hdmi:
>+	platform_device_unregister(hdmi->audio_pdev);
> 	hdmi->connector.funcs->destroy(&hdmi->connector);
> 	hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
> err_disable_i2c:
>@@ -828,6 +1099,7 @@ static void rk3066_hdmi_unbind(struct device *dev, struct device *master,
> {
> 	struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
>
>+	platform_device_unregister(hdmi->audio_pdev);
> 	hdmi->connector.funcs->destroy(&hdmi->connector);
> 	hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
>
>--
>2.39.2
>
Johan Jonker June 29, 2024, 10:18 a.m. UTC | #2
Hi Andy, thanks.

On 6/28/24 15:08, Andy Yan wrote:
> 
> Hi Johan,
> 
> At 2024-06-28 17:23:39, "Johan Jonker" <jbx6244@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@rock-chips.com>
>> Signed-off-by: Johan Jonker <jbx6244@gmail.com>
>> ---
>>
>> Changed V7:
>>  rebase
>> ---
>> drivers/gpu/drm/rockchip/Kconfig       |   2 +
>> drivers/gpu/drm/rockchip/rk3066_hdmi.c | 274 ++++++++++++++++++++++++-
>> 2 files changed, 275 insertions(+), 1 deletion(-)
>>
>> 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..d3128b787629 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 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;
>> @@ -54,9 +62,16 @@ struct rk3066_hdmi {
>>
>> 	unsigned int tmdsclk;
>>
>> +	struct platform_device *audio_pdev;
>> +	stru
> 
> ......
> 
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct hdmi_codec_ops 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 device *dev)
>> +{
>> +	struct hdmi_codec_pdata codec_data = {
>> +		.i2s = 1,
>> +		.ops = &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(dev,
>> +					      HDMI_CODEC_DRV_NAME,
>> +					      PLATFORM_DEVID_NONE,
>> +					      &codec_data,
>> +					      sizeof(codec_data));
>> +
>> +	return PTR_ERR_OR_ZERO(hdmi->audio_pdev);
>> +}
>> +
>> static int
>> rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
>> {
>> @@ -566,6 +834,8 @@ rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
>>
>> 	drm_connector_attach_encoder(&hdmi->connector, encoder);
>>
>> +	rk3066_hdmi_audio_codec_init(hdmi, dev);
> 
> 
> According to Documentation/driver-api/driver-model/driver.rst,
> 

> It is best not to register at the bind callback:

Question for the DRM experts:
What would be the correct location/level for the rk3066_hdmi_audio_codec_init() function?
Is that at the rk3066_hdmi_encoder_enable() function?
Are there other functions/examples for sound in the DRM toolbox?

Johan
> 
> .. warning::
>       -EPROBE_DEFER must not be returned if probe() has already created
>       child devices, even if those child devices are removed again
>       in a cleanup path. If -EPROBE_DEFER is returned after a child
>       device has been registered, it may result in an infinite loop of
>       .probe() calls to the same driver.
> 
> For example:
> vop_probe --》component_add--》rk3066_hdmi_bind--》rk3066_hdmi_audio_codec_init--》hdmi_codec_probe--》rockchip_rgb_init(DEFER when panel not ready)
> 
> This  may result in an infinite loop of probe
> 
> 
>> +
>> 	return 0;
>> }
>>
>> @@ -813,6 +1083,7 @@ static int rk3066_hdmi_bind(struct device *dev, struct device *master,
>> 	return 0;
>>
>> err_cleanup_hdmi:
>> +	platform_device_unregister(hdmi->audio_pdev);
>> 	hdmi->connector.funcs->destroy(&hdmi->connector);
>> 	hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
>> err_disable_i2c:
>> @@ -828,6 +1099,7 @@ static void rk3066_hdmi_unbind(struct device *dev, struct device *master,
>> {
>> 	struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
>>
>> +	platform_device_unregister(hdmi->audio_pdev);
>> 	hdmi->connector.funcs->destroy(&hdmi->connector);
>> 	hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
>>
>> --
>> 2.39.2
>>
diff mbox series

Patch

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..d3128b787629 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 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;
@@ -54,9 +62,16 @@  struct rk3066_hdmi {

 	unsigned int tmdsclk;

+	struct platform_device *audio_pdev;
+	struct audio_info audio;
+	bool audio_enable;
+
 	struct hdmi_data_info hdmi_data;
 };

+static int
+rk3066_hdmi_config_audio(struct rk3066_hdmi *hdmi, struct audio_info *audio);
+
 static struct rk3066_hdmi *encoder_to_rk3066_hdmi(struct drm_encoder *encoder)
 {
 	struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
@@ -214,6 +229,23 @@  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,
+				  struct audio_info *audio)
+{
+	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 +396,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_config_audio(hdmi, &hdmi->audio);
 	} else {
 		hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, 0);
 	}
@@ -380,9 +413,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;
 }

@@ -534,6 +578,230 @@  struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = {
 	.best_encoder = rk3066_hdmi_connector_best_encoder,
 };

+static int
+rk3066_hdmi_config_audio(struct rk3066_hdmi *hdmi, struct audio_info *audio)
+{
+	u32 rate, channel, word_length, N, CTS;
+	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, audio);
+}
+
+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_config_audio(hdmi, &hdmi->audio);
+}
+
+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);
+
+	/*
+	 * 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 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 device *dev)
+{
+	struct hdmi_codec_pdata codec_data = {
+		.i2s = 1,
+		.ops = &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(dev,
+					      HDMI_CODEC_DRV_NAME,
+					      PLATFORM_DEVID_NONE,
+					      &codec_data,
+					      sizeof(codec_data));
+
+	return PTR_ERR_OR_ZERO(hdmi->audio_pdev);
+}
+
 static int
 rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
 {
@@ -566,6 +834,8 @@  rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)

 	drm_connector_attach_encoder(&hdmi->connector, encoder);

+	rk3066_hdmi_audio_codec_init(hdmi, dev);
+
 	return 0;
 }

@@ -813,6 +1083,7 @@  static int rk3066_hdmi_bind(struct device *dev, struct device *master,
 	return 0;

 err_cleanup_hdmi:
+	platform_device_unregister(hdmi->audio_pdev);
 	hdmi->connector.funcs->destroy(&hdmi->connector);
 	hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
 err_disable_i2c:
@@ -828,6 +1099,7 @@  static void rk3066_hdmi_unbind(struct device *dev, struct device *master,
 {
 	struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);

+	platform_device_unregister(hdmi->audio_pdev);
 	hdmi->connector.funcs->destroy(&hdmi->connector);
 	hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);