diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi.h b/drivers/gpu/drm/omapdrm/dss/hdmi.h index c4a4e07f0b99..a79d4590611f 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi.h +++ b/drivers/gpu/drm/omapdrm/dss/hdmi.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "omapdss.h" #include "dss.h" @@ -368,6 +369,8 @@ struct omap_hdmi { struct drm_bridge bridge; struct platform_device *audio_pdev; + struct mutex audio_lock; + void (*audio_abort_cb)(struct device *dev); int wp_idlemode; @@ -380,6 +383,9 @@ struct omap_hdmi { bool display_enabled; }; +void hdmi_audio_hpd_notify(struct omap_hdmi *hdmi, + enum drm_connector_status status); + #define drm_bridge_to_hdmi(b) container_of(b, struct omap_hdmi, bridge) #endif diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c index 61cfd003569c..bd6c4d2dcdc8 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c @@ -433,8 +433,15 @@ static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, { struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); - if (status == connector_status_disconnected) + hdmi_audio_hpd_notify(hdmi, status); + + if (status == connector_status_disconnected) { + if (hdmi_runtime_get(hdmi)) + return; + hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID); + hdmi_runtime_put(hdmi); + } } static const struct drm_edid *hdmi4_bridge_edid_read(struct drm_bridge *bridge, @@ -626,12 +633,17 @@ static int hdmi_audio_register(struct omap_hdmi *hdmi) .ops = &hdmi_audio_ops, }; + guard(mutex)(&hdmi->audio_lock); hdmi->audio_pdev = platform_device_register_data( &hdmi->pdev->dev, "omap-hdmi-audio", PLATFORM_DEVID_AUTO, &pdata, sizeof(pdata)); - if (IS_ERR(hdmi->audio_pdev)) - return PTR_ERR(hdmi->audio_pdev); + if (IS_ERR(hdmi->audio_pdev)) { + int err = PTR_ERR(hdmi->audio_pdev); + + hdmi->audio_pdev = NULL; + return err; + } return 0; } @@ -688,8 +700,14 @@ static void hdmi4_unbind(struct device *dev, struct device *master, void *data) dss_debugfs_remove_file(hdmi->debugfs); - if (hdmi->audio_pdev) - platform_device_unregister(hdmi->audio_pdev); + scoped_guard(mutex, &hdmi->audio_lock) { + if (hdmi->audio_pdev) { + struct platform_device *pdev = hdmi->audio_pdev; + + hdmi->audio_pdev = NULL; + platform_device_unregister(pdev); + } + } hdmi4_cec_uninit(&hdmi->core); hdmi_pll_uninit(&hdmi->pll); @@ -770,6 +788,7 @@ static int hdmi4_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, hdmi); mutex_init(&hdmi->lock); + mutex_init(&hdmi->audio_lock); spin_lock_init(&hdmi->audio_playing_lock); r = hdmi4_probe_of(hdmi); diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c index 1ee3bbe20583..c542ba6ce7d3 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c @@ -425,6 +425,15 @@ static void hdmi5_bridge_disable(struct drm_bridge *bridge, mutex_unlock(&hdmi->lock); } +static void hdmi5_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); + + hdmi_audio_hpd_notify(hdmi, status); +} + static const struct drm_edid *hdmi5_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -475,6 +484,7 @@ static const struct drm_bridge_funcs hdmi5_bridge_funcs = { .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_enable = hdmi5_bridge_enable, .atomic_disable = hdmi5_bridge_disable, + .hpd_notify = hdmi5_bridge_hpd_notify, .edid_read = hdmi5_bridge_edid_read, }; @@ -601,12 +611,18 @@ static int hdmi_audio_register(struct omap_hdmi *hdmi) .ops = &hdmi_audio_ops, }; - hdmi->audio_pdev = platform_device_register_data( - &hdmi->pdev->dev, "omap-hdmi-audio", PLATFORM_DEVID_AUTO, - &pdata, sizeof(pdata)); + scoped_guard(mutex, &hdmi->audio_lock) { + hdmi->audio_pdev = platform_device_register_data( + &hdmi->pdev->dev, "omap-hdmi-audio", + PLATFORM_DEVID_AUTO, &pdata, sizeof(pdata)); - if (IS_ERR(hdmi->audio_pdev)) - return PTR_ERR(hdmi->audio_pdev); + if (IS_ERR(hdmi->audio_pdev)) { + int err = PTR_ERR(hdmi->audio_pdev); + + hdmi->audio_pdev = NULL; + return err; + } + } hdmi_runtime_get(hdmi); hdmi->wp_idlemode = @@ -654,8 +670,14 @@ static void hdmi5_unbind(struct device *dev, struct device *master, void *data) dss_debugfs_remove_file(hdmi->debugfs); - if (hdmi->audio_pdev) - platform_device_unregister(hdmi->audio_pdev); + scoped_guard(mutex, &hdmi->audio_lock) { + if (hdmi->audio_pdev) { + struct platform_device *pdev = hdmi->audio_pdev; + + hdmi->audio_pdev = NULL; + platform_device_unregister(pdev); + } + } hdmi_pll_uninit(&hdmi->pll); } @@ -735,6 +757,7 @@ static int hdmi5_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, hdmi); mutex_init(&hdmi->lock); + mutex_init(&hdmi->audio_lock); spin_lock_init(&hdmi->audio_playing_lock); r = hdmi5_probe_of(hdmi); diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi_common.c b/drivers/gpu/drm/omapdrm/dss/hdmi_common.c index 3ecde23ac604..e9adc5775913 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi_common.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi_common.c @@ -147,3 +147,17 @@ int hdmi_compute_acr(u32 pclk, u32 sample_freq, u32 *n, u32 *cts) return 0; } + +void hdmi_audio_hpd_notify(struct omap_hdmi *hdmi, + enum drm_connector_status status) +{ + guard(mutex)(&hdmi->audio_lock); + + if (hdmi->audio_pdev) { + struct device *dev = &hdmi->audio_pdev->dev; + struct omap_hdmi_audio_pdata *ha = dev_get_platdata(dev); + + if (ha->audio_hpd) + ha->audio_hpd(dev, status == connector_status_connected); + } +} diff --git a/include/sound/omap-hdmi-audio.h b/include/sound/omap-hdmi-audio.h index e5f82044a404..8dca92360ee0 100644 --- a/include/sound/omap-hdmi-audio.h +++ b/include/sound/omap-hdmi-audio.h @@ -34,6 +34,7 @@ struct omap_hdmi_audio_pdata { phys_addr_t audio_dma_addr; const struct omap_hdmi_audio_ops *ops; + void (*audio_hpd)(struct device *dev, bool connected); }; #endif /* __OMAP_HDMI_AUDIO_H__ */ diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c index e60f5b483fc5..90ce12a81246 100644 --- a/sound/soc/ti/omap-hdmi.c +++ b/sound/soc/ti/omap-hdmi.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "sdma-pcm.h" @@ -35,6 +36,8 @@ struct hdmi_audio_data { struct mutex current_stream_lock; struct snd_pcm_substream *current_stream; + struct snd_soc_jack jack; + atomic_t jack_state; }; static @@ -262,6 +265,34 @@ static void hdmi_dai_shutdown(struct snd_pcm_substream *substream, ad->current_stream = NULL; } +static void hdmi_audio_hpd(struct device *dev, bool connected) +{ + struct hdmi_audio_data *ad = dev_get_drvdata(dev); + + if (atomic_xchg(&ad->jack_state, connected) == connected) + return; + + snd_soc_jack_report(&ad->jack, + connected ? SND_JACK_AVOUT : 0, SND_JACK_AVOUT); + dev_dbg(dev, "HDMI %s\n", connected ? "CONNECTED" : "DISCONNECTED"); +} + +static int hdmi_audio_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct hdmi_audio_data *ad = snd_soc_card_get_drvdata(rtd->card); + int ret; + + ret = snd_soc_card_jack_new( + card, "HDMI", SND_JACK_AVOUT, &ad->jack); + if (ret < 0) { + dev_err(card->dev, "Cannot create HDMI jack: %i\n", ret); + return ret; + } + + return 0; +} + static const struct snd_soc_dai_ops hdmi_dai_ops = { .startup = hdmi_dai_startup, .hw_params = hdmi_dai_hw_params, @@ -371,28 +402,44 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) card->dai_link->stream_name = card->name; card->dai_link->cpus->dai_name = dev_name(ad->dssdev); card->dai_link->platforms->name = dev_name(ad->dssdev); + card->dai_link->init = hdmi_audio_dai_init; card->num_links = 1; card->dev = dev; + atomic_set(&ad->jack_state, -1); + ad->card = card; + + snd_soc_card_set_drvdata(card, ad); ret = devm_snd_soc_register_card(dev, card); if (ret) { dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); return ret; } - ad->card = card; - snd_soc_card_set_drvdata(card, ad); - dev_set_drvdata(dev, ad); + ha->audio_hpd = hdmi_audio_hpd; + return 0; } +static void omap_hdmi_audio_remove(struct platform_device *pdev) +{ + struct omap_hdmi_audio_pdata *ha = pdev->dev.platform_data; + + /* + * hdmi4_unbind() holds audio lock across platform_device_unregister(), + * so audio_hpd() cannot run concurrently with this callback. + */ + ha->audio_hpd = NULL; +} + static struct platform_driver hdmi_audio_driver = { .driver = { .name = DRV_NAME, }, .probe = omap_hdmi_audio_probe, + .remove = omap_hdmi_audio_remove, }; module_platform_driver(hdmi_audio_driver);