[PATCH v4 05/10] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD

From: Sebastian Reichel

Date: Tue Jul 21 2026 - 14:25:34 EST


Add support for USB-C DP AltMode out-of-band hotplug handling. The
handling itself is implemented in the platform specific driver as the
registers to force HPD state are not part of the Designware DisplayPort
IP itself. Instead the platform integration might provide the necessary
functionality to mux the HPD signal.

Signed-off-by: Sebastian Reichel <sebastian.reichel@xxxxxxxxxxxxx>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 38 +++++++++++++++++++++++++++++++++
include/drm/bridge/dw_dp.h | 3 +++
2 files changed, 41 insertions(+)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 0e8ecd73a000..8eb2699dc7ab 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1819,6 +1819,19 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b
return &state->base;
}

+static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ enum drm_connector_status status)
+{
+ bool hpd_high = status != connector_status_disconnected;
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ if (dp->plat_data.hpd_sw_cfg)
+ dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high);
+ else
+ dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n");
+}
+
static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
.atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -1831,6 +1844,7 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
.atomic_disable = dw_dp_bridge_atomic_disable,
.detect = dw_dp_bridge_detect,
.edid_read = dw_dp_bridge_edid_read,
+ .oob_notify = dw_dp_bridge_oob_notify,
};

static int dw_dp_link_retrain(struct dw_dp *dp)
@@ -1960,6 +1974,19 @@ static const struct regmap_config dw_dp_regmap_config = {
.rd_table = &dw_dp_readable_table,
};

+static bool dw_dp_is_routed_to_usb_c(struct drm_encoder *encoder)
+{
+ struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
+ struct fwnode_handle *fwnode;
+
+ last_bridge = drm_bridge_chain_get_last_bridge(encoder);
+ if (!last_bridge)
+ return false;
+
+ fwnode = of_fwnode_handle(last_bridge->of_node);
+ return fwnode_device_is_compatible(fwnode, "usb-c-connector");
+}
+
int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
{
struct device *dev = dp->dev;
@@ -2004,6 +2031,13 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
goto put_next_bridge;
}

+ if (dw_dp_is_routed_to_usb_c(encoder)) {
+ dev_dbg(dev, "USB-C mode\n");
+
+ if (dp->plat_data.hpd_sw_sel)
+ dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1);
+ }
+
dw_dp_init_hw(dp);

ret = phy_init(dp->phy);
@@ -2052,6 +2086,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_

dp->dev = dev;
dp->pixel_mode = plat_data->pixel_mode;
+
+ dp->plat_data.hpd_sw_sel = plat_data->hpd_sw_sel;
+ dp->plat_data.hpd_sw_cfg = plat_data->hpd_sw_cfg;
+ dp->plat_data.data = plat_data->data;
dp->plat_data.max_link_rate = plat_data->max_link_rate;

mutex_init(&dp->irq_lock);
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index a82412a9e769..79b2cdf0df99 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -20,6 +20,9 @@ enum {
struct dw_dp_plat_data {
u32 max_link_rate;
u8 pixel_mode;
+ void *data;
+ void (*hpd_sw_sel)(void *data, bool hpd);
+ void (*hpd_sw_cfg)(void *data, bool hpd);
};

int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder);

--
2.53.0