[PATCH v7 12/18] drm/bridge: synopsys: dw-dp: Add Runtime PM support
From: Sebastian Reichel
Date: Tue Jul 28 2026 - 13:50:10 EST
Add runtime PM stubs to the Synopsys DesignWare DisplayPort bridge
driver. Support is not enabled automatically and must be hooked up
in the vendor specific glue code.
Signed-off-by: Sebastian Reichel <sebastian.reichel@xxxxxxxxxxxxx>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 126 ++++++++++++++++++++++++++++++--
include/drm/bridge/dw_dp.h | 3 +
2 files changed, 121 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index d4f75b7f85d0..8b650f904575 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -328,6 +328,9 @@ struct dw_dp {
struct dw_dp_link link;
struct dw_dp_plat_data plat_data;
u8 pixel_mode;
+ bool usbc_mode;
+ bool pm_active;
+ bool drm_bound;
struct drm_bridge *next_bridge;
@@ -1467,6 +1470,11 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
if (WARN_ON(msg->size > 16))
return -E2BIG;
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
reinit_completion(&dp->complete);
switch (msg->request & ~DP_AUX_I2C_MOT) {
@@ -1664,6 +1672,13 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_connector_state *conn_state;
int ret;
+ ret = pm_runtime_get_active(dp->dev, RPM_TRANSPARENT);
+ if (ret) {
+ dev_err(dp->dev, "runtime PM failure\n");
+ return;
+ }
+ dp->pm_active = true;
+
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
if (!connector) {
dev_err(dp->dev, "failed to get connector\n");
@@ -1714,10 +1729,15 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
{
struct dw_dp *dp = bridge_to_dp(bridge);
+ if (!dp->pm_active)
+ return;
+ dp->pm_active = false;
+
dw_dp_video_disable(dp);
dw_dp_link_disable(dp);
bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
dw_dp_reset(dp);
+ pm_runtime_put_autosuspend(dp->dev);
}
static bool dw_dp_hpd_detect_link(struct dw_dp *dp, struct drm_connector *connector)
@@ -1738,6 +1758,10 @@ static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge,
{
struct dw_dp *dp = bridge_to_dp(bridge);
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+ if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+ return connector_status_disconnected;
+
if (!dw_dp_hpd_detect(dp))
return connector_status_disconnected;
@@ -1920,6 +1944,11 @@ static void dw_dp_hpd_work(struct work_struct *work)
bool long_hpd;
int ret;
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
+
mutex_lock(&dp->irq_lock);
long_hpd = dp->hotplug.long_hpd;
mutex_unlock(&dp->irq_lock);
@@ -2059,25 +2088,52 @@ 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");
+ dp->usbc_mode = dw_dp_is_routed_to_usb_c(encoder);
- if (dp->plat_data.hpd_sw_sel)
- dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1);
+ ret = pm_runtime_get_active(dev, RPM_TRANSPARENT);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to get runtime PM\n");
+ goto put_next_bridge;
}
- dw_dp_init_hw(dp);
+ /* resume once if runtime PM is disabled */
+ if (!pm_runtime_enabled(dev)) {
+ ret = dw_dp_runtime_resume(dp);
+ if (ret)
+ goto put_runtime_pm;
+ }
+
+ if (dp->plat_data.hpd_sw_sel)
+ dp->plat_data.hpd_sw_sel(dp->plat_data.data, dp->usbc_mode);
ret = phy_init(dp->phy);
if (ret) {
dev_err_probe(dev, ret, "phy init failed\n");
- goto put_next_bridge;
+ goto put_manual_pm;
}
+ dp->drm_bound = true;
enable_irq(dp->irq);
+ /*
+ * USB-C has out-of-band hotplug detection, so device may enter
+ * runtime suspend. Native mode needs to be resumed for working
+ * hotplug detection.
+ */
+ if (dp->usbc_mode) {
+ dev_dbg(dev, "USB-C mode\n");
+ pm_runtime_put_autosuspend(dp->dev);
+ }
+
return 0;
+put_manual_pm:
+ if (!pm_runtime_enabled(dev))
+ dw_dp_runtime_suspend(dp);
+
+put_runtime_pm:
+ pm_runtime_put_sync(dp->dev);
+
put_next_bridge:
drm_bridge_put(dp->next_bridge);
@@ -2090,9 +2146,22 @@ EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
+ if (dp->usbc_mode)
+ pm_runtime_get_sync(dp->dev);
+
+ dp->drm_bound = false;
disable_irq(dp->irq);
+
cancel_work_sync(&dp->hpd_work);
phy_exit(dp->phy);
+
+ /* Last PM runtime suspend must be done with drm_bound = false */
+ pm_runtime_put_sync(dp->dev);
+
+ /* suspend once if runtime PM is disabled */
+ if (!pm_runtime_enabled(dp->dev))
+ dw_dp_runtime_suspend(dp);
+
drm_bridge_put(dp->next_bridge);
drm_dp_aux_unregister(&dp->aux);
}
@@ -2151,13 +2220,13 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
return ERR_CAST(dp->phy);
}
- dp->apb_clk = devm_clk_get_enabled(dev, "apb");
+ dp->apb_clk = devm_clk_get(dev, "apb");
if (IS_ERR(dp->apb_clk)) {
dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n");
return ERR_CAST(dp->apb_clk);
}
- dp->aux_clk = devm_clk_get_enabled(dev, "aux");
+ dp->aux_clk = devm_clk_get(dev, "aux");
if (IS_ERR(dp->aux_clk)) {
dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n");
return ERR_CAST(dp->aux_clk);
@@ -2202,6 +2271,47 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
}
EXPORT_SYMBOL_GPL(dw_dp_probe);
+int dw_dp_runtime_suspend(struct dw_dp *dp)
+{
+ if (dp->drm_bound)
+ disable_irq(dp->irq);
+
+ clk_disable_unprepare(dp->aux_clk);
+ clk_disable_unprepare(dp->apb_clk);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_dp_runtime_suspend);
+
+int dw_dp_runtime_resume(struct dw_dp *dp)
+{
+ int ret;
+
+ ret = clk_prepare_enable(dp->apb_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dp->aux_clk);
+ if (ret) {
+ clk_disable_unprepare(dp->apb_clk);
+ return ret;
+ }
+
+ dw_dp_init_hw(dp);
+
+ /*
+ * HPD_HOT_PLUG bit is asserted only after the sink holds HPD
+ * high for at least 100ms.
+ */
+ msleep(110);
+
+ if (dp->drm_bound)
+ enable_irq(dp->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_dp_runtime_resume);
+
MODULE_AUTHOR("Andy Yan <andyshrk@xxxxxxx>");
MODULE_DESCRIPTION("DW DP Core Library");
MODULE_LICENSE("GPL");
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 79b2cdf0df99..f20d2c2c40ca 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -29,4 +29,7 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder);
void dw_dp_unbind(struct dw_dp *dp);
struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data);
+
+int dw_dp_runtime_suspend(struct dw_dp *dp);
+int dw_dp_runtime_resume(struct dw_dp *dp);
#endif /* __DW_DP__ */
--
2.53.0