[PATCH v6 12/13] drm/bridge: it6505: Add audio support
Daniel Golle <[email protected]>
| Newsgroups | org.infradead.lists.linux-mediatek,org.freedesktop.lists.dri-devel,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <849db02af75f9f8bab9e211395435fda12d27c3e.1784947240.git.daniel@makrotopia.org> |
From: Jiaxin Yu <[email protected]> Add audio support for it6505 by bridging to the hdmi-codec: register an "hdmi-audio-codec" platform device from probe and wire up the previously unused audio helpers via hdmi_codec_ops. This unblocks the mt8186-mt6366 sound card which references it6505 as the I2S3 codec. Audio starts out muted and is only enabled once the stream is unmuted, since some DP-to-HDMI dongles get into a bad state if the InfoFrame is sent without audio data. Enable/disable can race between the FIFO-error IRQ, the delayed enable work, the hdmi-codec ops and the HPD-low path, so the register sequences, the mute state and the cached stream parameters are serialised with a new audio_lock mutex. plugged_cb/codec_dev updates take mode_lock, the lock it6505_detect() holds when calling back into the codec. Signed-off-by: Jiaxin Yu <[email protected]> Link: https://lore.kernel.org/all/[email protected]/ Signed-off-by: Daniel Golle <[email protected]> --- v6: no changes v5: update plugged_cb/codec_dev under mode_lock v4: * start out muted * update mute state and cached parameters under audio_lock; the FIFO-error IRQ probes and enables audio in one locked section * unregister the codec device only after quiesce on remove v3: serialise enable/disable with audio_lock; track mute state so the FIFO-error IRQ cannot undo a mute v2: * drive enable/disable from .mute_stream (hdmi_codec_ops lost .trigger), use it6505->dev * keep and unregister the codec platform_device on remove, cancel delayed_audio on shutdown and remove, disable audio when muting v1: respin of Jiaxin Yu's v3 on current -next drivers/gpu/drm/bridge/ite-it6505.c | 163 +++++++++++++++++++++++----- 1 file changed, 134 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c index aeaa7b5ecf5a..c560f1258f6d 100644 --- a/drivers/gpu/drm/bridge/ite-it6505.c +++ b/drivers/gpu/drm/bridge/ite-it6505.c @@ -407,6 +407,7 @@ struct it6505_audio_data { u8 i2s_data_delay; u8 i2s_ws_channel; u8 i2s_data_sequence; + bool mute; }; struct it6505_audio_sample_rate_map { @@ -439,6 +440,7 @@ struct it6505 { struct mutex extcon_lock; struct mutex mode_lock; /* used to bridge_detect */ struct mutex aux_lock; /* used to aux data transfers */ + struct mutex audio_lock; /* serializes audio enable/disable */ struct regmap *regmap; struct drm_display_mode source_output_mode; struct drm_display_mode video_info; @@ -477,6 +479,7 @@ struct it6505 { bool enable_enhanced_frame; hdmi_codec_plugged_cb plugged_cb; struct device *codec_dev; + struct platform_device *audio_pdev; struct delayed_work delayed_audio; struct it6505_audio_data audio; struct dentry *debugfs; @@ -1599,7 +1602,7 @@ static void it6505_enable_audio_infoframe(struct it6505 *it6505) EN_AUD_CTRL_PKT); } -static void it6505_disable_audio(struct it6505 *it6505) +static void __it6505_disable_audio(struct it6505 *it6505) { it6505_set_bits(it6505, REG_DATA_MUTE_CTRL, EN_AUD_MUTE, EN_AUD_MUTE); it6505_set_bits(it6505, REG_AUDIO_SRC_CTRL, M_AUDIO_I2S_EN, 0x00); @@ -1607,13 +1610,21 @@ static void it6505_disable_audio(struct it6505 *it6505) it6505_set_bits(it6505, REG_RESET_CTRL, AUDIO_RESET, AUDIO_RESET); } -static void it6505_enable_audio(struct it6505 *it6505) +static void it6505_disable_audio(struct it6505 *it6505) +{ + mutex_lock(&it6505->audio_lock); + __it6505_disable_audio(it6505); + mutex_unlock(&it6505->audio_lock); +} + +static void __it6505_enable_audio(struct it6505 *it6505) { struct device *dev = it6505->dev; int regbe; DRM_DEV_DEBUG_DRIVER(dev, "start"); - it6505_disable_audio(it6505); + + __it6505_disable_audio(it6505); it6505_setup_audio_channel_status(it6505); it6505_setup_audio_format(it6505); @@ -1637,6 +1648,14 @@ static void it6505_enable_audio(struct it6505 *it6505) it6505_set_bits(it6505, REG_DATA_MUTE_CTRL, EN_AUD_MUTE, 0x00); } +static void it6505_enable_audio(struct it6505 *it6505) +{ + mutex_lock(&it6505->audio_lock); + if (!it6505->audio.mute) + __it6505_enable_audio(it6505); + mutex_unlock(&it6505->audio_lock); +} + static bool it6505_use_step_train_check(struct it6505 *it6505) { if (it6505->link.revision >= 0x12) @@ -2331,19 +2350,12 @@ static void it6505_stop_link_train(struct it6505 *it6505) static void it6505_link_train_ok(struct it6505 *it6505) { - struct device *dev = it6505->dev; - it6505->link_state = LINK_OK; /* disalbe mute enable avi info frame */ it6505_set_bits(it6505, REG_DATA_MUTE_CTRL, EN_VID_MUTE, 0x00); it6505_set_bits(it6505, REG_INFOFRAME_CTRL, EN_VID_CTRL_PKT, EN_VID_CTRL_PKT); - if (it6505_audio_input(it6505)) { - DRM_DEV_DEBUG_DRIVER(dev, "Enable audio!"); - it6505_enable_audio(it6505); - } - if (it6505->hdcp_desired) it6505_start_hdcp(it6505); } @@ -2635,8 +2647,10 @@ static void it6505_irq_audio_fifo_error(struct it6505 *it6505) DRM_DEV_DEBUG_DRIVER(dev, "audio fifo error Interrupt"); - if (it6505_audio_input(it6505)) - it6505_enable_audio(it6505); + mutex_lock(&it6505->audio_lock); + if (!it6505->audio.mute && it6505_audio_input(it6505)) + __it6505_enable_audio(it6505); + mutex_unlock(&it6505->audio_lock); } static void it6505_irq_link_train_fail(struct it6505 *it6505) @@ -2985,7 +2999,7 @@ static void it6505_remove_notifier_module(struct it6505 *it6505) flush_work(&it6505->extcon_wq); } -static void __maybe_unused it6505_delayed_audio(struct work_struct *work) +static void it6505_delayed_audio(struct work_struct *work) { struct it6505 *it6505 = container_of(work, struct it6505, delayed_audio.work); @@ -2999,11 +3013,12 @@ static void __maybe_unused it6505_delayed_audio(struct work_struct *work) it6505_enable_audio(it6505); } -static int __maybe_unused it6505_audio_setup_hw_params(struct it6505 *it6505, - struct hdmi_codec_params - *params) +static int it6505_audio_setup_hw_params(struct it6505 *it6505, + struct hdmi_codec_params + *params) { struct device *dev = it6505->dev; + u8 word_length; int i = 0; DRM_DEV_DEBUG_DRIVER(dev, "%s %d Hz, %d bit, %d channels\n", __func__, @@ -3019,8 +3034,6 @@ static int __maybe_unused it6505_audio_setup_hw_params(struct it6505 *it6505, return -EINVAL; } - it6505->audio.channel_count = params->cea.channels; - while (i < ARRAY_SIZE(audio_sample_rate_map) && params->sample_rate != audio_sample_rate_map[i].sample_rate_value) { @@ -3031,21 +3044,20 @@ static int __maybe_unused it6505_audio_setup_hw_params(struct it6505 *it6505, params->sample_rate); return -EINVAL; } - it6505->audio.sample_rate = audio_sample_rate_map[i].rate; switch (params->sample_width) { case 16: - it6505->audio.word_length = WORD_LENGTH_16BIT; + word_length = WORD_LENGTH_16BIT; break; case 18: - it6505->audio.word_length = WORD_LENGTH_18BIT; + word_length = WORD_LENGTH_18BIT; break; case 20: - it6505->audio.word_length = WORD_LENGTH_20BIT; + word_length = WORD_LENGTH_20BIT; break; case 24: case 32: - it6505->audio.word_length = WORD_LENGTH_24BIT; + word_length = WORD_LENGTH_24BIT; break; default: DRM_DEV_DEBUG_DRIVER(dev, "wordlength: %d bit not support", @@ -3053,27 +3065,111 @@ static int __maybe_unused it6505_audio_setup_hw_params(struct it6505 *it6505, return -EINVAL; } + mutex_lock(&it6505->audio_lock); + it6505->audio.channel_count = params->cea.channels; + it6505->audio.sample_rate = audio_sample_rate_map[i].rate; + it6505->audio.word_length = word_length; + mutex_unlock(&it6505->audio_lock); + return 0; } -static void __maybe_unused it6505_audio_shutdown(struct device *dev, void *data) +static void it6505_audio_shutdown(struct device *dev, void *data) { struct it6505 *it6505 = dev_get_drvdata(dev); + mutex_lock(&it6505->audio_lock); + it6505->audio.mute = true; if (it6505->powered) - it6505_disable_audio(it6505); + __it6505_disable_audio(it6505); + mutex_unlock(&it6505->audio_lock); + cancel_delayed_work_sync(&it6505->delayed_audio); +} + +static int it6505_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct it6505 *it6505 = dev_get_drvdata(dev); + + return it6505_audio_setup_hw_params(it6505, params); } -static int __maybe_unused it6505_audio_hook_plugged_cb(struct device *dev, - void *data, - hdmi_codec_plugged_cb fn, - struct device *codec_dev) +static int it6505_audio_mute(struct device *dev, void *data, + bool enable, int direction) +{ + struct it6505 *it6505 = dev_get_drvdata(dev); + + DRM_DEV_DEBUG_DRIVER(dev, "mute: %d", enable); + + /* + * Delay enabling audio until the stream is unmuted; InfoFrames + * without audio data upset some DP-to-HDMI dongles. + */ + if (enable) { + mutex_lock(&it6505->audio_lock); + it6505->audio.mute = true; + if (it6505->powered) + __it6505_disable_audio(it6505); + mutex_unlock(&it6505->audio_lock); + cancel_delayed_work_sync(&it6505->delayed_audio); + } else { + mutex_lock(&it6505->audio_lock); + it6505->audio.mute = false; + mutex_unlock(&it6505->audio_lock); + queue_delayed_work(system_wq, &it6505->delayed_audio, + msecs_to_jiffies(180)); + } + + return 0; +} + +static int it6505_audio_hook_plugged_cb(struct device *dev, + void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) { struct it6505 *it6505 = data; + mutex_lock(&it6505->mode_lock); it6505->plugged_cb = fn; it6505->codec_dev = codec_dev; it6505_plugged_status_to_codec(it6505); + mutex_unlock(&it6505->mode_lock); + + return 0; +} + +static const struct hdmi_codec_ops it6505_audio_codec_ops = { + .hw_params = it6505_audio_hw_params, + .mute_stream = it6505_audio_mute, + .audio_shutdown = it6505_audio_shutdown, + .hook_plugged_cb = it6505_audio_hook_plugged_cb, +}; + +static int it6505_register_audio_driver(struct device *dev) +{ + struct it6505 *it6505 = dev_get_drvdata(dev); + struct hdmi_codec_pdata codec_data = { + .ops = &it6505_audio_codec_ops, + .max_i2s_channels = 8, + .i2s = 1, + .no_capture_mute = 1, + .data = it6505, + }; + struct platform_device *pdev; + + it6505->audio.mute = true; + INIT_DELAYED_WORK(&it6505->delayed_audio, it6505_delayed_audio); + + pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, &codec_data, + sizeof(codec_data)); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + it6505->audio_pdev = pdev; + DRM_DEV_DEBUG_DRIVER(dev, "bound to %s", HDMI_CODEC_DRV_NAME); return 0; } @@ -3589,6 +3685,7 @@ static int it6505_i2c_probe(struct i2c_client *client) mutex_init(&it6505->extcon_lock); mutex_init(&it6505->mode_lock); mutex_init(&it6505->aux_lock); + mutex_init(&it6505->audio_lock); it6505->bridge.of_node = client->dev.of_node; it6505->connector_status = connector_status_disconnected; @@ -3639,6 +3736,12 @@ static int it6505_i2c_probe(struct i2c_client *client) return err; } + err = it6505_register_audio_driver(dev); + if (err < 0) { + dev_err(dev, "Failed to register audio driver: %d", err); + return err; + } + INIT_WORK(&it6505->link_works, it6505_link_training_work); INIT_WORK(&it6505->hdcp_wait_ksv_list, it6505_hdcp_wait_ksv_list); INIT_DELAYED_WORK(&it6505->hdcp_work, it6505_hdcp_work); @@ -3680,6 +3783,8 @@ static void it6505_i2c_remove(struct i2c_client *client) cancel_work_sync(&it6505->hdcp_wait_ksv_list); cancel_delayed_work_sync(&it6505->hdcp_work); cancel_work_sync(&it6505->extcon_wq); + platform_device_unregister(it6505->audio_pdev); + cancel_delayed_work_sync(&it6505->delayed_audio); if (it6505->extcon_state) pm_runtime_put_sync(&client->dev); pm_runtime_disable(&client->dev); -- 2.55.0