[PATCH 03/13] drm/msm/dp: Track PHY power ownership

From: Xilin Wu

Date: Wed Sep 30 2026 - 08:50:43 EST


Mainlink restart and PHY test paths can power down the PHY before a later
cleanup attempts to release it again. The PHY core counts each power-off
call, so cleanup without an owned reference can underflow that count and
prevent a subsequent enable from calling the hardware power-on operation.

Record successful PHY power acquisition in the controller and route all
power-off paths through an ownership check. Keep the reference recorded
when power-off fails and avoid acquiring a second reference while it is
still held. Also skip releasing link clocks that are already disabled.

This establishes resource ownership for repeated cleanup. Error
propagation is left to the subsequent stream enable rollback change.

Assisted-by: LLM
Signed-off-by: Xilin Wu <sophon@xxxxxxxxx>
---
drivers/gpu/drm/msm/dp/dp_ctrl.c | 35 +++++++++++++++++++++++++++++------
1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index 59070f399e2d..f005421630c6 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -136,6 +136,7 @@ struct msm_dp_ctrl_private {

u32 hw_revision;

+ bool phy_powered;
bool core_clks_on;
bool link_clks_on;
bool stream_clks_on;
@@ -1813,6 +1814,9 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl)

ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);

+ if (!ctrl->link_clks_on)
+ return;
+
clk_bulk_disable_unprepare(ctrl->num_link_clks, ctrl->link_clks);

ctrl->link_clks_on = false;
@@ -1824,6 +1828,23 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl)
str_on_off(ctrl->core_clks_on));
}

+static int msm_dp_ctrl_phy_power_off(struct msm_dp_ctrl_private *ctrl)
+{
+ int ret;
+
+ if (!ctrl->phy_powered)
+ return 0;
+
+ ret = phy_power_off(ctrl->phy);
+ if (ret) {
+ DRM_ERROR("Failed to power off PHY: %d\n", ret);
+ return ret;
+ }
+
+ ctrl->phy_powered = false;
+ return 0;
+}
+
static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl,
struct msm_dp_panel *panel)
{
@@ -1836,7 +1857,11 @@ static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl,
ctrl->phy_opts.dp.ssc = drm_dp_max_downspread(dpcd);

phy_configure(phy, &ctrl->phy_opts);
- phy_power_on(phy);
+ if (!ctrl->phy_powered) {
+ ret = phy_power_on(phy);
+ if (!ret)
+ ctrl->phy_powered = true;
+ }

dev_pm_opp_set_rate(ctrl->dev, ctrl->link->link_params.rate * 1000);
ret = msm_dp_ctrl_link_clk_enable(&ctrl->msm_dp_ctrl);
@@ -1996,7 +2021,7 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl,
*/
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);

- phy_power_off(phy);
+ msm_dp_ctrl_phy_power_off(ctrl);
/* hw recommended delay before re-enabling clocks */
msleep(20);

@@ -2022,7 +2047,7 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl,

msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);

- phy_power_off(phy);
+ msm_dp_ctrl_phy_power_off(ctrl);

/* aux channel down, reinit phy */
phy_exit(phy);
@@ -2643,10 +2668,8 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl,
struct msm_dp_panel *panel)
{
struct msm_dp_ctrl_private *ctrl;
- struct phy *phy;

ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
- phy = ctrl->phy;

msm_dp_panel_disable_vsc_sdp(panel);

@@ -2656,7 +2679,7 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl,

msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);

- phy_power_off(phy);
+ msm_dp_ctrl_phy_power_off(ctrl);
}

irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl,

--
2.55.0