Message ID | feaad47f-c85b-4fd9-a63f-cdda1d621b70@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v9] drm/rockchip: rk3066_hdmi: add sound support | expand |
Hi Johan, Thanks for your patch。 At 2024-07-03 22:20:27, "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 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-rockchip@lists.infradead.org >http://lists.infradead.org/mailman/listinfo/linux-rockchip
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; + 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); + 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, }; 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);