diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 14723ee5cb05..54181b052fe2 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -81,6 +82,9 @@ struct dw_hdmi_qp { struct regmap *regm; int main_irq; + /* Guards curr_conn between atomic hooks and HPD notify readers */ + struct mutex mutex; + struct drm_connector *curr_conn; unsigned long tmds_char_rate; bool no_hpd; }; @@ -564,6 +568,10 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); } + drm_connector_get(connector); + scoped_guard(mutex, &hdmi->mutex) + hdmi->curr_conn = connector; + hdmi->phy.ops->init(hdmi, hdmi->phy.data); dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); @@ -578,6 +586,13 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, struct dw_hdmi_qp *hdmi = bridge->driver_private; struct drm_connector *connector; + scoped_guard(mutex, &hdmi->mutex) { + if (hdmi->curr_conn) { + drm_connector_put(hdmi->curr_conn); + hdmi->curr_conn = NULL; + } + } + hdmi->tmds_char_rate = 0; connector = drm_atomic_get_old_connector_for_encoder(state, bridge->encoder); @@ -1099,6 +1114,10 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, if (IS_ERR(hdmi)) return ERR_CAST(hdmi); + ret = devm_mutex_init(dev, &hdmi->mutex); + if (ret) + return ERR_PTR(ret); + hdmi->dev = dev; regs = devm_platform_ioremap_resource(pdev, 0); @@ -1194,6 +1213,46 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, } EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind); +/** + * dw_hdmi_qp_hpd_notify() - Notify a hot-plug detection event + * @hdmi: pointer to the DW HDMI QP controller + * + * Platform drivers should call this from their threaded IRQ handler or work + * function to notify the bridge of a connection status change. + * The bridge's .read_hpd() phy_ops callback is used to read the current + * connection status. + */ +void dw_hdmi_qp_hpd_notify(struct dw_hdmi_qp *hdmi) +{ + enum drm_connector_status status; + struct drm_connector *conn; + + scoped_guard(mutex, &hdmi->mutex) { + conn = hdmi->curr_conn; + if (conn) + drm_connector_get(conn); + } + + status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + dev_dbg(hdmi->dev, "%s status=%d\n", __func__, status); + + /* + * When the display pipeline has been already active, switch to + * drm_connector_helper_hpd_irq_event() to ensure .detect_ctx() + * gets invoked, i.e. via drm_helper_probe_detect(), because + * drm_bridge_hpd_notify() defers to a delayed hotplug path in + * this case. + */ + if (conn && status == connector_status_connected) + drm_connector_helper_hpd_irq_event(conn); + else + drm_bridge_hpd_notify(&hdmi->bridge, status); + + if (conn) + drm_connector_put(conn); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_hpd_notify); + void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi) { disable_irq(hdmi->main_irq); diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index b80fceffc315..b4fb1c578a5b 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -36,6 +36,7 @@ struct dw_hdmi_qp_plat_data { struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, struct drm_encoder *encoder, const struct dw_hdmi_qp_plat_data *plat_data); +void dw_hdmi_qp_hpd_notify(struct dw_hdmi_qp *hdmi); void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi); void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi); #endif /* __DW_HDMI_QP__ */