[PATCH v12 05/16] drm/bridge: dw-hdmi-qp: Provide dw_hdmi_qp_hpd_notify() helper
From: Cristian Ciocaltea
Date: Sun Oct 04 2026 - 20:42:19 EST
Export dw_hdmi_qp_hpd_notify() for platform drivers to report hot-plug
detection events. Unlike drm_helper_hpd_irq_event() which polls all
connectors, this helper targets only the affected connector and ensures
.detect_ctx() is invoked on reconnection events to trigger SCDC state
recovery.
Track the connector in use in curr_conn, set and cleared by the atomic
enable/disable hooks. Since the helper runs asynchronously from
IRQ/work context, hold a reference on that connector for as long as the
bridge is enabled, so that the pointer is always safe to dereference.
Otherwise validity would rest on the platform glue quiescing HPD before
the DRM-managed connector is freed during teardown, risking a
use-after-free on a late HPD event.
The helper must take its own temporary reference while running, because
the long-lived reference above only guarantees the pointer is valid at
the time it is read, not that the connector survives a concurrent
atomic_disable dropping that reference while the helper is still using
it. Moreover, reading the pointer and acquiring the temporary reference
must be atomic with respect to atomic_disable, otherwise a window exists
where drm_connector_get() is called on a connector whose refcount
already reached zero, hence guard curr_conn with a mutex for this
purpose. Holding the mutex across the whole helper instead is not an
option, as it takes modeset locks while atomic_disable is called with
them held.
Also note that this helper may sleep, so it must only be called from a
threaded HPD interrupt handler or a work function.
Tested-by: Maud Spierings <maud_spierings@xxxxxxxxxxx>
Tested-by: Diederik de Haas <diederik@xxxxxxxxxxxxxx> # NanoPC-T6 LTS, Rock 5B
Tested-by: Robin Räber <robin.raebi@xxxxxxxxx>
Tested-by: Igor Paunovic <royalnet026@xxxxxxxxx> # RK3588 Orange Pi 5 Plus, dual HDMI
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@xxxxxxxxxxxxx>
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 59 ++++++++++++++++++++++++++++
include/drm/bridge/dw_hdmi_qp.h | 1 +
2 files changed, 60 insertions(+)
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 <drm/drm_edid.h>
#include <drm/drm_modes.h>
#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
#include <media/cec.h>
@@ -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__ */
--
2.55.0