All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Sebastian Reichel" <sebastian.reichel@collabora.com>
Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v12 14/23] drm/bridge: synopsys: dw-dp: Add Runtime PM support
Date: Sat, 14 Nov 2026 05:34:04 +0000	[thread overview]
Message-ID: <sashiko-outbox-214181@kernel.org> (raw)
In-Reply-To: <20261114-synopsys-dw-dp-improvements-v12-14-d42d3dc5bdd4@collabora.com>

Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [High] drm/bridge: dw-dp: missing runtime PM acquire in
  dw_dp_bridge_edid_read()
- [High] drm/bridge: dw-dp: spurious HPD_HOT_PLUG IRQ in
  dw_dp_runtime_resume()
- [High] drm/bridge: dw-dp: missing HPD debounce wait in
  dw_dp_bridge_oob_notify()
- [Medium] drm/bridge: dw-dp: unbalanced IRQ enable and clock leak on
  teardown
--

commit 26edd6f6880ea763bab6673f7b28388080118df0
Author: Sebastian Reichel <sebastian.reichel@collabora.com>

drm/bridge: synopsys: dw-dp: Add Runtime PM support

This commit adds runtime PM support to the Synopsys DesignWare DisplayPort
bridge driver and splits dw_dp_alloc() out of dw_dp_probe() so platform
drivers can wire up runtime PM callbacks before probing completes.

> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index b4eed4d0a5205..4d61ca8f0174c 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c

[ ... ]

> @@ -1755,6 +1775,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;
>  

[Severity: High]
Does dw_dp_bridge_edid_read() also need PM_RUNTIME_ACQUIRE_AUTOSUSPEND()
before calling phy_power_on(dp->phy)?

drivers/gpu/drm/bridge/synopsys/dw-dp.c:dw_dp_bridge_edid_read() {
	ret = phy_power_on(dp->phy);
	if (ret)
		return NULL;

	edid = drm_edid_read_ddc(connector, &dp->aux.ddc);

	phy_power_off(dp->phy);
}

In USB-C mode, if dp->dev is runtime-suspended when userspace reads the EDID
(for example, if autosuspend expires between .detect() and .edid_read(), or
when connector status is forced), phy_power_on(dp->phy) runs while the DP
controller power domain is off.  On Rockchip RK3588/RK3576,
rk_udphy_dp_phy_power_on() -> rk_udphy_dp_lane_select() writes to vo-grf,
which lives in the DP controller's power domain; accessing it while powered
off triggers an ARM64 SError crash, and even without a fault, powering on the
domain later inside dw_dp_aux_transfer() resets vo-grf and loses the lane and
AUX configuration set by phy_power_on().

[ ... ]

> @@ -1949,12 +1987,20 @@ static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge,
>  {
>  	bool hpd_high = status != connector_status_disconnected;
>  	struct dw_dp *dp = bridge_to_dp(bridge);
> +	int ret;
> +
> +	dp->usbc_hpd = hpd_high;
>  
>  	if (!dp->plat_data.hpd_sw_cfg) {
>  		dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n");
>  		return;
>  	}
>  
> +	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
> +	ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> +	if (ret)
> +		return;
> +
>  	dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd_high));
>  	dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high);
>  }

[Severity: High]
When dp->dev is already runtime-active (such as when runtime PM is not
enabled, or before autosuspend expires), PM_RUNTIME_ACQUIRE_AUTOSUSPEND()
does not run dw_dp_runtime_resume(), so dw_dp_bridge_oob_notify() returns
immediately after asserting hpd_sw_cfg() without waiting 100ms for the
controller to debounce HPD.

Its caller drm_bridge_connector_oob_hotplug_event() immediately runs
dw_dp_bridge_detect() -> dw_dp_hpd_detect(), which reads HPD_STATE before the
100ms hardware debounce completes and falsely reports
connector_status_disconnected.  Also, when dp->dev was suspended,
dw_dp_runtime_resume() sleeps 110ms unconditionally even when HPD is low and
calls hpd_sw_cfg() a second time.

[ ... ]

> @@ -2208,39 +2283,67 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
>  	dp->rstc = devm_reset_control_get(dev, NULL);
>  	if (IS_ERR(dp->rstc)) {
>  		dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n");
> -		return ERR_CAST(dp->rstc);
> +		return PTR_ERR(dp->rstc);
>  	}
>  
>  	dp->irq = platform_get_irq(pdev, 0);
>  	if (dp->irq < 0)
> -		return ERR_PTR(dp->irq);
> +		return dp->irq;
>  
>  	ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
>  					IRQF_ONESHOT | IRQF_NO_AUTOEN, dev_name(dev), dp);
>  	if (ret)
> -		return ERR_PTR(ret);
> +		return ret;
> +
> +	/*
> +	 * Disable IRQ a second time; this ensures the interrupt is only
> +	 * enabled when the bridge is attached AND runtime PM is enabled.
> +	 * Also register a devm action to restore the correct balance during
> +	 * device removal.
> +	 */
> +	disable_irq(dp->irq);
> +
> +	ret = devm_add_action_or_reset(dev, dw_dp_enable_irq, dp);
> +	if (ret)
> +		return ret;
>  
>  	dp->next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
>  	if (IS_ERR(dp->next_bridge)) {
>  		dev_err_probe(dev, PTR_ERR(dp->next_bridge), "failed to get follow-up bridge\n");
> -		return ERR_CAST(dp->next_bridge);
> +		return PTR_ERR(dp->next_bridge);
>  	}
>  
>  	ret = devm_add_action_or_reset(dev, dw_dp_put_next_bridge, dp);
>  	if (ret)
> -		return ERR_PTR(ret);
> +		return ret;
>  
> -	dw_dp_init_hw(dp);
> +	if (dp->plat_data.autosuspend_delay > 0) {
> +		pm_runtime_use_autosuspend(dev);
> +		pm_runtime_set_autosuspend_delay(dev, dp->plat_data.autosuspend_delay);
> +		ret = devm_pm_runtime_enable(dev);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!pm_runtime_enabled(dev)) {
> +		ret = dw_dp_runtime_resume(dp);
> +		if (ret)
> +			return ret;
> +
> +		ret = devm_add_action_or_reset(dev, dw_dp_manual_suspend, dp);
> +		if (ret)
> +			return ret;
> +	}

[Severity: Medium]
Can device removal with runtime PM enabled skip dw_dp_runtime_suspend(),
leaking clocks and leaving dp->irq enabled during teardown?

When pm_runtime_enabled(dev) is true, devm_pm_runtime_enable() only disables
runtime PM on teardown without calling dw_dp_runtime_suspend() if the device
is still active (for example, when runtime suspend is prevented via sysfs
power/control or an active usage count).  Because dw_dp_manual_suspend() is
only registered when !pm_runtime_enabled(dev), teardown never calls
dw_dp_runtime_suspend(), which leaks the prepared/enabled aux_clk and apb_clk
and leaves dp->irq at disable depth 1 instead of 2 when dw_dp_enable_irq()
runs, unmasking the hardware interrupt line after phy_exit() and
drm_bridge_remove().

>  	ret = phy_init(dp->phy);
>  	if (ret) {
>  		dev_err_probe(dev, ret, "phy init failed\n");
> -		return ERR_PTR(ret);
> +		return ret;
>  	}
>  
>  	ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp);
>  	if (ret)
> -		return ERR_PTR(ret);
> +		return ret;
>  
>  	bridge = &dp->bridge;
>  	bridge->of_node = dev->of_node;
> @@ -2248,13 +2351,53 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
>  	bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
>  	bridge->ycbcr_420_allowed = true;
>  
> -	ret = devm_drm_bridge_add(dev, bridge);
> +	return devm_drm_bridge_add(dev, bridge);
> +}
> +EXPORT_SYMBOL_GPL(dw_dp_probe);
> +
> +int dw_dp_runtime_suspend(struct dw_dp *dp)
> +{
> +	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 ERR_PTR(ret);
> +		return ret;
>  
> -	return dp;
> +	ret = clk_prepare_enable(dp->aux_clk);
> +	if (ret) {
> +		clk_disable_unprepare(dp->apb_clk);
> +		return ret;
> +	}
> +
> +	if (dp->plat_data.hpd_sw_sel)
> +		dp->plat_data.hpd_sw_sel(dp->plat_data.data, dp->usbc_mode);
> +	if (dp->plat_data.hpd_sw_cfg)
> +		dp->plat_data.hpd_sw_cfg(dp->plat_data.data, dp->usbc_hpd);
> +
> +	dw_dp_init_hw(dp);
> +
> +	enable_irq(dp->irq);
> +
> +	/*
> +	 * HPD_HOT_PLUG bit is asserted only after the sink holds HPD
> +	 * high for at least 100ms.
> +	 */
> +	msleep(110);
> +
> +	return 0;
>  }
> -EXPORT_SYMBOL_GPL(dw_dp_probe);
> +EXPORT_SYMBOL_GPL(dw_dp_runtime_resume);

[Severity: High]
Does enabling dp->irq before msleep(110) without clearing HPD_HOT_PLUG fire a
spurious hotplug interrupt on every runtime resume while HPD is high?

Unlike dw_dp_reset(), which keeps dp->irq disabled while waiting for
HPD_HOT_PLUG and clears HPD_HOT_PLUG before calling enable_irq(dp->irq),
dw_dp_runtime_resume() enables HPD interrupts in dw_dp_init_hw(), unmasks
dp->irq, and sleeps 110ms, causing the hardware to assert HPD_HOT_PLUG and
schedule dp->hpd_work on every wakeup while a sink is connected.

When dw_dp_bridge_atomic_enable() resumes dp->dev during a non-blocking
atomic commit, that spurious hpd_work runs concurrently with modeset enable:

CPU0 (commit_work)                CPU1 (hpd_work)
dw_dp_bridge_atomic_enable()
  pm_runtime_get_active()
    dw_dp_runtime_resume()
      ...                         dw_dp_irq() -> schedules hpd_work
  dw_dp_link_enable()
                                  dw_dp_hpd_work()
                                    drm_helper_hpd_irq_event()
                                      dw_dp_bridge_detect()
                                        dw_dp_hpd_detect_link()
                                          dw_dp_link_parse()
                                            dw_dp_link_reset()
                                              link->rate = 0
                                              link->lanes = 0
                                          phy_power_off(dp->phy)
  dw_dp_video_enable()
    link_bandwidth = (rate / 1000) * lanes
    peak_stream_bandwidth * 64 / link_bandwidth  <-- divide by zero

In addition, if dw_dp_hpd_work() itself wakes dp->dev for a short HPD IRQ
(long_hpd == false), the spurious HPD_HOT_PLUG during resume overwrites
dp->hotplug.long_hpd = true before dw_dp_hpd_work() acquires dp->irq_lock,
dropping the link-retraining request.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261114-synopsys-dw-dp-improvements-v12-0-d42d3dc5bdd4@collabora.com?part=14

  reply	other threads:[~2026-11-14  5:34 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-14  5:00 [PATCH v12 00/23] Synopsys DisplayPort Controller improvements for Rockchip platforms Sebastian Reichel
2026-11-14  5:00 ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 01/23] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 02/23] drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:35   ` sashiko-bot
2026-11-14  5:00 ` [PATCH v12 03/23] drm/bridge: synopsys: dw-dp: Fix error handling for DP link enablement Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 04/23] drm/bridge: synopsys: dw-dp: Document missing reset line deassert Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 05/23] drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 06/23] drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 07/23] drm/bridge: synopsys: dw-dp: Fix support for short I2C reads Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 08/23] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 09/23] drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 10/23] drm/bridge: synopsys: dw-dp: Add follow-up bridge support Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:34   ` sashiko-bot
2026-11-14  5:00 ` [PATCH v12 11/23] drm/bridge: Add out-of-band HPD notify handler Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 12/23] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:30   ` sashiko-bot
2026-11-14  5:00 ` [PATCH v12 13/23] drm/rockchip: dw_dp: Implement out-of-band HPD handling Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 14/23] drm/bridge: synopsys: dw-dp: Add Runtime PM support Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:34   ` sashiko-bot [this message]
2026-11-14  5:00 ` [PATCH v12 15/23] drm/rockchip: dw_dp: Add runtime " Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:29   ` sashiko-bot
2026-11-14  5:00 ` [PATCH v12 16/23] drm/rockchip: dw_dp: Add system " Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 17/23] drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 18/23] drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 19/23] drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 20/23] drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 21/23] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 22/23] drm/bridge: synopsys: dw-dp: Add audio support Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:00 ` [PATCH v12 23/23] drm/bridge: synopsys: dw-dp: Retrain on hotplug with pre-enabled link Sebastian Reichel
2026-11-14  5:00   ` Sebastian Reichel
2026-11-14  5:25   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-214181@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=sebastian.reichel@collabora.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.