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
next prev parent reply other threads:[~2026-11-14 5:34 UTC|newest]
Thread overview: 31+ 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 ` [PATCH v12 01/23] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach 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: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 ` [PATCH v12 04/23] drm/bridge: synopsys: dw-dp: Document missing reset line deassert 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 ` [PATCH v12 06/23] drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition 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 ` [PATCH v12 08/23] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid 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 ` [PATCH v12 10/23] drm/bridge: synopsys: dw-dp: Add follow-up bridge support 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 ` [PATCH v12 12/23] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD 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 ` [PATCH v12 14/23] drm/bridge: synopsys: dw-dp: Add Runtime PM support 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: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 ` [PATCH v12 17/23] drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access 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 ` [PATCH v12 19/23] drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable 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 ` [PATCH v12 21/23] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells 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:47 ` sashiko-bot
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: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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox