From: Jiaxin Yu <[email protected]> Add audio support for it6505 by bridging to the hdmi-codec, registering an "hdmi-audio-codec" platform device from probe. The it6505's audio setup/shutdown helpers were merged earlier as unused code; wire them up via hdmi_codec_ops so the DAI actually appears, which unblocks the mt8186-mt6366 sound card that references it6505 as the I2S3 codec.
Some DP-to-HDMI dongles get into a bad state if InfoFrame is sent without audio data, so it6505's audio is only enabled once the stream is unmuted. Signed-off-by: Jiaxin Yu <[email protected]> Link: https://lore.kernel.org/all/[email protected]/ Signed-off-by: Daniel Golle <[email protected]> --- djg: respin of Jiaxin Yu's v3, rebased onto current mainline. Changes from v3: hdmi_codec_ops lost .trigger, so drive enable/disable from .mute_stream; drop the now-redundant __maybe_unused; use it6505->dev instead of &client->dev, addressing AngeloGioacchino Del Regno's v3 review. Also fix issues in v3: initialise delayed_audio before registering the codec device; keep and unregister the platform_device and cancel delayed_audio on remove to avoid a leak and a use-after-free; cancel the delayed work on audio shutdown; and actually disable audio (not just cancel the pending enable) when the stream is muted. Note: it6505_enable_audio()/it6505_disable_audio() can still run concurrently from the delayed work, the .mute_stream / .audio_shutdown callbacks and the audio-FIFO-error IRQ; as in v3 these are left unserialised -- input welcome on whether a lock is warranted. v2: * store the hdmi-codec platform_device and unregister it on i2c remove, fixing a resource leak and use-after-free on unbind * initialise the delayed audio work before registering the codec device instead of after * synchronously cancel the delayed audio work on audio shutdown and on driver remove (cancel_delayed_work_sync) * rework the mute path to cancel pending work synchronously and disable audio immediately when muting, removing a race with the delayed enable work drivers/gpu/drm/bridge/ite-it6505.c | 101 +++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c index 8ecb43611dba..d84441926e8e 100644 --- a/drivers/gpu/drm/bridge/ite-it6505.c +++ b/drivers/gpu/drm/bridge/ite-it6505.c @@ -476,6 +476,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; @@ -2320,19 +2321,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); } @@ -2960,7 +2954,7 @@ static void it6505_remove_notifier_module(struct it6505 *it6505) } } -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); @@ -2974,9 +2968,9 @@ 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; int i = 0; @@ -3031,18 +3025,52 @@ static int __maybe_unused it6505_audio_setup_hw_params(struct it6505 *it6505, 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); + cancel_delayed_work_sync(&it6505->delayed_audio); if (it6505->powered) it6505_disable_audio(it6505); } -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_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 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); + + /* + * Some DP-to-HDMI dongles get into a bad state if the InfoFrame is + * sent without audio data, so only enable it6505's audio once the + * stream is unmuted (i.e. actually playing). + */ + if (enable) { + cancel_delayed_work_sync(&it6505->delayed_audio); + if (it6505->powered) + it6505_disable_audio(it6505); + } else { + 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; @@ -3053,6 +3081,39 @@ static int __maybe_unused it6505_audio_hook_plugged_cb(struct device *dev, 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; + + 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; +} + static inline struct it6505 *bridge_to_it6505(struct drm_bridge *bridge) { return container_of(bridge, struct it6505, bridge); @@ -3612,6 +3673,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); @@ -3644,6 +3711,8 @@ static void it6505_i2c_remove(struct i2c_client *client) { struct it6505 *it6505 = i2c_get_clientdata(client); + platform_device_unregister(it6505->audio_pdev); + cancel_delayed_work_sync(&it6505->delayed_audio); drm_bridge_remove(&it6505->bridge); drm_dp_aux_unregister(&it6505->aux); it6505_debugfs_remove(it6505); -- 2.55.0
