Add HDMI audio support for v2 Signed-off-by: Guillaume Ranquet <granquet@xxxxxxxxxxxx> --- drivers/gpu/drm/mediatek/mtk_hdmi_common.c | 1 + drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 198 +++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_v2.h | 4 +- 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_common.c b/drivers/gpu/drm/mediatek/mtk_hdmi_common.c index 97a00102a970..9ed86834ca85 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_common.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_common.c @@ -346,6 +346,7 @@ static const struct mtk_hdmi_conf mtk_hdmi_conf_v2 = { .mtk_hdmi_output_init = mtk_hdmi_output_init_v2, .mtk_hdmi_clk_disable = mtk_hdmi_clk_disable_v2, .mtk_hdmi_clk_enable = mtk_hdmi_clk_enable_v2, + .mtk_hdmi_set_codec_pdata = mtk_hdmi_set_codec_pdata_v2, .mtk_hdmi_clock_names = mtk_hdmi_clk_names_v2, .num_clocks = MTK_HDMIV2_CLK_COUNT, }; diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c index 98b1d50ecd72..0e41dfb36db4 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c @@ -150,6 +150,24 @@ static void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi, bool black) regmap_update_bits(hdmi->regs, TOP_VMUTE_CFG1, REG_VMUTE_EN, val); } +static void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi) +{ + u32 aip, val; + + regmap_read(hdmi->regs, AIP_CTRL, &aip); + + val = FIELD_PREP(AUD_MUTE_FIFO_EN, 1); + if (aip & DSD_EN) + val |= FIELD_PREP(DSD_MUTE_DATA, 1); + + regmap_update_bits(hdmi->regs, AIP_TXCTRL, val, val); +} + +static void mtk_hdmi_hw_aud_unmute(struct mtk_hdmi *hdmi) +{ + regmap_update_bits(hdmi->regs, AIP_TXCTRL, AUD_MUTE_FIFO_EN, AUD_MUTE_DIS); +} + static void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi) { regmap_update_bits(hdmi->regs, HDMITX_CONFIG, HDMITX_SW_RSTB, 0); @@ -766,6 +784,7 @@ static void mtk_hdmi_audio_reset(struct mtk_hdmi *hdmi, bool rst) static void mtk_hdmi_aud_output_config(struct mtk_hdmi *hdmi, struct drm_display_mode *display_mode) { + mtk_hdmi_hw_aud_mute(hdmi); mtk_hdmi_aud_enable_packet(hdmi, false); mtk_hdmi_audio_reset(hdmi, true); mtk_hdmi_aip_ctrl_init(hdmi); @@ -778,6 +797,7 @@ static void mtk_hdmi_aud_output_config(struct mtk_hdmi *hdmi, usleep_range(25, 50); mtk_hdmi_aud_on_off_hw_ncts(hdmi, true); mtk_hdmi_aud_enable_packet(hdmi, true); + mtk_hdmi_hw_aud_unmute(hdmi); } void mtk_hdmi_output_init_v2(struct mtk_hdmi *hdmi) @@ -794,6 +814,16 @@ void mtk_hdmi_output_init_v2(struct mtk_hdmi *hdmi) hdmi->hpd = HDMI_PLUG_OUT; } +static void mtk_hdmi_audio_set_param(struct mtk_hdmi *hdmi, + struct hdmi_audio_param *param) +{ + if (!hdmi->audio_enable) + return; + + memcpy(&hdmi->aud_param, param, sizeof(*param)); + mtk_hdmi_aud_output_config(hdmi, &hdmi->mode); +} + static void mtk_hdmi_change_video_resolution(struct mtk_hdmi *hdmi) { mtk_hdmi_hw_reset(hdmi); @@ -812,6 +842,7 @@ static void mtk_hdmi_change_video_resolution(struct mtk_hdmi *hdmi) usleep_range(5, 10); mtk_hdmi_hw_vid_black(hdmi, true); + mtk_hdmi_hw_aud_mute(hdmi); mtk_hdmi_hw_send_av_unmute(hdmi); regmap_update_bits(hdmi->regs, TOP_CFG01, NULL_PKT_VSYNC_HIGH_EN | NULL_PKT_EN, NULL_PKT_VSYNC_HIGH_EN); @@ -1022,6 +1053,7 @@ static void mtk_hdmi_bridge_disable(struct drm_bridge *bridge, mtk_hdmi_hw_send_av_mute(hdmi); usleep_range(50000, 50050); mtk_hdmi_hw_vid_black(hdmi, true); + mtk_hdmi_hw_aud_mute(hdmi); mtk_hdmi_disable_hdcp_encrypt(hdmi); usleep_range(50000, 50050); @@ -1030,6 +1062,14 @@ static void mtk_hdmi_bridge_disable(struct drm_bridge *bridge, hdmi->enabled = false; } +static void mtk_hdmi_handle_plugged_change(struct mtk_hdmi *hdmi, bool plugged) +{ + mutex_lock(&hdmi->update_plugged_status_lock); + if (hdmi->plugged_cb && hdmi->codec_dev) + hdmi->plugged_cb(hdmi->codec_dev, plugged); + mutex_unlock(&hdmi->update_plugged_status_lock); +} + static void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge, struct drm_bridge_state *old_state) { @@ -1041,6 +1081,9 @@ static void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge, phy_power_off(hdmi->phy); hdmi->powered = false; + + /* signal the disconnect event to audio codec */ + mtk_hdmi_handle_plugged_change(hdmi, false); } static void mtk_hdmi_bridge_pre_enable(struct drm_bridge *bridge, @@ -1077,6 +1120,10 @@ static void mtk_hdmi_bridge_enable(struct drm_bridge *bridge, mtk_hdmi_hw_avi_infoframe(hdmi, buffer_avi, sizeof(buffer_avi)); mtk_hdmi_hw_vid_black(hdmi, false); + mtk_hdmi_hw_aud_unmute(hdmi); + + /* signal the connect event to audio codec */ + mtk_hdmi_handle_plugged_change(hdmi, true); hdmi->enabled = true; } @@ -1103,3 +1150,154 @@ const struct drm_bridge_funcs mtk_v2_hdmi_bridge_funcs = { .get_edid = mtk_hdmi_bridge_get_edid, .detect = mtk_hdmi_bridge_detect, }; + +static void mtk_hdmi_set_plugged_cb(struct mtk_hdmi *hdmi, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + bool plugged; + + mutex_lock(&hdmi->update_plugged_status_lock); + hdmi->plugged_cb = fn; + hdmi->codec_dev = codec_dev; + plugged = (hdmi->hpd == HDMI_PLUG_IN_AND_SINK_POWER_ON); + mutex_unlock(&hdmi->update_plugged_status_lock); + + mtk_hdmi_handle_plugged_change(hdmi, plugged); +} + +/* + * HDMI audio codec callbacks + */ +static int mtk_hdmi_audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + if (!hdmi) + return -ENODEV; + + mtk_hdmi_set_plugged_cb(hdmi, fn, codec_dev); + return 0; +} + +static int mtk_hdmi_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + struct hdmi_audio_param hdmi_params; + unsigned int chan = params->cea.channels; + + if (!hdmi->bridge.encoder) + return -ENODEV; + + switch (chan) { + case 2: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0; + break; + case 4: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_4_0; + break; + case 6: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_5_1; + break; + case 8: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_7_1; + break; + default: + return -EINVAL; + } + + switch (params->sample_rate) { + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + break; + default: + return -EINVAL; + } + + switch (daifmt->fmt) { + case HDMI_I2S: + hdmi_params.aud_codec = HDMI_AUDIO_CODING_TYPE_PCM; + hdmi_params.aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16; + hdmi_params.aud_input_type = HDMI_AUD_INPUT_I2S; + hdmi_params.aud_i2s_fmt = HDMI_I2S_MODE_I2S_24BIT; + hdmi_params.aud_mclk = HDMI_AUD_MCLK_128FS; + break; + default: + return -EINVAL; + } + + memcpy(&hdmi_params.codec_params, params, + sizeof(hdmi_params.codec_params)); + + mtk_hdmi_audio_set_param(hdmi, &hdmi_params); + + return 0; +} + +static int mtk_hdmi_audio_startup(struct device *dev, void *data) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + mtk_hdmi_aud_enable_packet(hdmi, true); + hdmi->audio_enable = true; + + return 0; +} + +static void mtk_hdmi_audio_shutdown(struct device *dev, void *data) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + mtk_hdmi_aud_enable_packet(hdmi, false); + hdmi->audio_enable = false; +} + +static int mtk_hdmi_audio_mute(struct device *dev, void *data, bool enable, + int direction) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + if (enable) + mtk_hdmi_hw_aud_mute(hdmi); + else + mtk_hdmi_hw_aud_unmute(hdmi); + + return 0; +} + +static int mtk_hdmi_audio_get_eld(struct device *dev, void *data, uint8_t *buf, + size_t len) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->enabled) + memcpy(buf, hdmi->curr_conn->eld, min(sizeof(hdmi->curr_conn->eld), len)); + else + memset(buf, 0, len); + return 0; +} + +static const struct hdmi_codec_ops mtk_hdmi_audio_codec_ops = { + .hw_params = mtk_hdmi_audio_hw_params, + .audio_startup = mtk_hdmi_audio_startup, + .audio_shutdown = mtk_hdmi_audio_shutdown, + .mute_stream = mtk_hdmi_audio_mute, + .get_eld = mtk_hdmi_audio_get_eld, + .hook_plugged_cb = mtk_hdmi_audio_hook_plugged_cb, +}; + +void mtk_hdmi_set_codec_pdata_v2(struct hdmi_codec_pdata *codec_data) +{ + codec_data->ops = &mtk_hdmi_audio_codec_ops; + codec_data->max_i2s_channels = 2; + codec_data->i2s = 1; +} diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.h b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.h index 15d06d82eb8e..6b195a9c6cc8 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.h +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.h @@ -7,8 +7,9 @@ #ifndef _MTK_HDMI_V2_CTRL_H #define _MTK_HDMI_V2_CTRL_H -#include <linux/hdmi.h> #include <drm/drm_bridge.h> +#include <linux/hdmi.h> +#include <sound/hdmi-codec.h> struct mtk_hdmi; @@ -17,6 +18,7 @@ extern const struct drm_bridge_funcs mtk_v2_hdmi_bridge_funcs; void mtk_hdmi_output_init_v2(struct mtk_hdmi *hdmi); int mtk_hdmi_clk_enable_v2(struct mtk_hdmi *hdmi); void mtk_hdmi_clk_disable_v2(struct mtk_hdmi *hdmi); +void mtk_hdmi_set_codec_pdata_v2(struct hdmi_codec_pdata *codec_data); enum mtk_hdmi_clk_id_v2 { MTK_HDMIV2_HDCP_SEL, -- 2.40.0