* [PATCH v8 00/21] Synopsys DisplayPort Controller improvements for Rockchip platforms
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko, Krzysztof Kozlowski
This patch series updates the Synopsys Designware DisplayPort bridge
together with the only existing user: The Rockchip RK3576/RK3588:
1. Follow-up bridges (PHY, USB-C connector)
This is needed to get USB-C DP AltMode working; I've followed
the Qualcomm driver as reference
2. Runtime PM
The initial driver has been upstreamed without RPM; add it to
avoid wasting power when nothing is plugged
3. Audio
The initial driver has been upstreamed without audio support;
this adds all missing bits for audio with single stream transport
To properly make use of the bridge code the following USBDP PHY series
is also needed:
https://lore.kernel.org/all/20260714-rockchip-usbdp-cleanup-v13-0-6cb3e769d4c5@collabora.com/
This series can be applied without the USBDP side. In that case USB-C
support won't work as the mainline USBDP side does not register as a
DRM bridge. As upstream DT binding for USBDP does not yet support
proper ports, no such board should be upstream.
Boards using the DP controller for native DP connectors (or HDMI
bridges), currently directly link to the connector/hdmi bridge. These
are not affected by the USBDP changes and should keep working in any
case. The difference is, that this series the DRM side is aware of the
link. I don't have any of those boards, so this code path is untested.
I added a dedicated bridge callback for out-of-band hotplug events,
which is separate from the hotplug_notify. I have a feeling, that
there might be a better solution, but haven't found it. Please comment.
Changes in v8:
- Link to v7: https://patch.msgid.link/20260728-synopsys-dw-dp-improvements-v7-0-b7640fa8cf48@collabora.com
- Add new patch moving DP AUX bridge registration into
bridge_attach/detach callbacks, which simplifies cleanup and ensures
it is (un)registered at the right point in time (Sashiko)
- Update Fix incorrect resource lifetimes in bind callback patch to
ensure at the end of the probe, the device is fully functional,
also only register the bridge once all necessary bits are available
as it is in theory reachable at this point (Sashiko)
- Update the patch cancelling HPD worker to simply use
devm_work_autocancel() as a result of this rework
- Add a new patch describing why the reset line state is not explicitly
changed (Sashiko)
- Merge the two patches for the AUX transfer timeout handling to avoid a
temporary race condition and synchronize IRQs after the reset to fix
yet another race condition (Sashiko)
- Add a new patch to properly support short I2C reads (Sashiko)
- Rework Runtime PM handling again, so that the OOB handling is done
before the 100ms sleep. Also ensure it is enabled at the right point
in time after the cleanup changes. (Sashiko)
- Add new patch to drop useless reservation for SDP slot 0, which
fix a race condition (Sashiko)
- Also restore audio mute status when audio functionality is restored
after atomic disable/enable cycle (Sashiko)
- Setup SDP audio frame channel_allocation according to information
received from ALSA (Sashko)
- Sashiko feedback ignored by me and that may or may not appear again
in this version as Sashiko does not seem to properly take the whole
series into account and thus probably ignores the changelog
* A divide-by-zero vulnerability exists in dw_dp_video_enable when
processing a display mode with a clock value of 1; I believe this
clock cannot drive a real mode
* I2C-over-AUX reply status (NACK/DEFER) is silently discarded,
causing deferred I2C transactions to falsely succeed and return
garbage data; this is a false positive. It seems Sashiko got
confused by the bit patterns. Nothing is discarded.
* Potential Use-After-Free of the connector and encoder due to missing
DRM device reference in OOB hotplug event handling; Sashiko
missed that drm_for_each_bridge_in_chain() is now the renamed
drm_for_each_bridge_in_chain_scoped().
* Missing runtime PM acquisition in out-of-band HPD callbacks leads to
SError crashes once runtime PM is enabled; Sashiko failed to
notice that Runtime PM is only introduced in a later patch
* Calling a sleeping function (msleep) from a runtime PM resume
callback that is reachable from an atomic commit path; Sashiko
failed to understand that atomic DRM functions may sleep
* Missing system sleep callbacks prevent the device and its power
domain from suspending during system sleep for native DP
configurations; this needs separate investigation considering
system sleep is broken on recent Rockchip platforms (same error
appears a few times)
* There is a bunch of pre-existing issues that are fixed later
in the series
Changes in v7:
- Link to v6: https://lore.kernel.org/r/20260724-synopsys-dw-dp-improvements-v6-0-041d99a19c4e@collabora.com
- Move drm_bridge_add() to probe and only attach it in the bind
function, as the object is re-used and not fully cleared by
drm_bridge_remove (Sashiko)
- Add a new patch resetting the DP AUX sub-controller on message
timeout to have decent error recovery (Sashiko)
- Fix dp_dp_rockchip_get_vo_grf -> dw_dp_rockchip_get_vo_grf (Sashiko)
- Introduce pm_active in the PM runtime patch, to early exit
on atomic_disable when atomic_enable failed to get runtime
PM enabled (Sashiko)
- Introduce drm_bound in the PM runtime patch, which is (un)set
when the driver is bound to a DRM encoder and used to enable
IRQ enabling in the runtime PM handlers. This in turn protects
the system of potential spurious interrupts. (Sashiko)
- In the audio patch, fix support for hotplug with a running
audio stream. (Sashiko)
- To avoid further blowing up the series, I've decided not to
act on these "pre-existing" issues reported by Sashiko. I
think it makes sense to handle them separately:
* Short HPD and Long HPD events share a single boolean state
variable, causing critical hot plug events to be lost if
interrupts arrive in quick succession.
* DP AUX lifetime complains (registered too early / unregistered
too late); reported multiple times
* The driver fails to support DP AUX short reads, incorrectly
rejecting them with -EBUSY instead of returning the number
of bytes successfully transferred, which breaks I2C-over-AUX.
- Collected Reviewed-by from Krzysztof Kozlowski on the
DT binding patch
Changes in v6:
- Link to v5: https://lore.kernel.org/r/20260724-synopsys-dw-dp-improvements-v5-0-9445c2e87441@collabora.com
- Fix overlong commit message in mutex patch
- Update MEDIA_BUS_FMT_FIXED patch, to use input format as output format
when output format is MEDIA_BUS_FMT_FIXED (Sashiko)
- Drop patch moving dw_dp_bridge_atomic_get_output_bus_fmts (no longer
needed)
- Reword the comment about platforms other than RK3576 and RK3588
shouldn't get the VO GRF from the USBDP to make it more obvious
that no such platform exist at the moment and thus the alternative
path will not be implemented at this point in time (Sashiko)
- Update the runtime PM code to have much better error handling
as well as support for platforms not using runtime PM (Sashiko)
- Update the runtime PM code to keep the device suspended when
probed, but unbound as a nice side effect
- Fix error handling for dw_dp_audio_infoframe_send() in audio
patch (Sashiko)
- Fix typo in sample width check in audio patch (Sashiko)
- Improve cleanup of SDP in audio patch (Sashiko)
Changes in v5:
- Link to v4: https://lore.kernel.org/r/20260721-synopsys-dw-dp-improvements-v4-0-f0f4a4ede712@collabora.com
- Add new patch to cancel pending HPD work on unbind (Sashiko)
- Add new patch to add missing mutex cleanups on module removal (Sashiko)
- Add new patch moving dw_dp_bridge_atomic_get_output_bus_fmts
- Update MEDIA_BUS_FMT_FIXED patch to avoid fixed RGB888 format
and instead use whatever works for the negotiated bandwidth (Sashiko)
- Add more error checking for runtime PM patches (Sashiko)
- Ensure runtime PM is held in interrupt handler (Sashiko)
- Fix usbc_mode logic inversion for runtime PM in unbind (Sashiko)
- Move sleep for HPD_HOT_PLUG from hotplug handler to runtime
resume function; no need to sleep when runtime PM is kept
active and runtime PM resume is a slow operation anyways. (Sashiko)
- Add new patch to protect sdp_reg_bank from concurrent access (Sashiko)
- Add new patch to use regmap_set_bits in dw_dp_send_sdp()
- Update DT binding patch to be all about the selected solution and
mention the ABI break
- Clear SDP in the audio patch on unprepare (Sashiko)
- Add a comment, that only 1,2 or 8 channels are supported (Sashiko)
- Error out on unsupported sample_width (Sashiko)
- Fix missing error check for clock enable (Sashiko)
Changes in PATCHv4:
- Link to v3: https://lore.kernel.org/all/20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com/
- Drop "Simplify driver data setting" (effectively reverted later on)
- Add "Fix incorrect resource lifetimes in bind callback", which
affects all following patches quite a bit (Sashiko)
- Update "Add follow-up bridge support" to mention that fatal errors
are fine (Sashiko)
- Update "Add out-of-band HPD notify handler" to use
drm_for_each_bridge_in_chain() instead of
drm_for_each_bridge_in_chain_scoped() required after rebase to
latest drm-misc-next
- Add 100ms sleep in hpd detect functions after runtime resume in the
"Add Runtime PM support" patch (Andy Yan)
- Keep runtime PM enabled if DP is used without USB-C to avoid missing
HPD signals in "Add Runtime PM support" patch (Andy Yan)
- Update patch description of DT binding patch and drop RFC tag
- Use pm_runtime_resume_and_get() in the audio startup function, which
allows easy error handling (Sashiko)
- Avoid updating audio_interface in the audio prepare function when
an error related early exit happens (Sashiko)
- Add error handling for clock prepare calls in audio prepare function
(Sashiko)
Changes in PATCHv3:
- Link to v2: https://lore.kernel.org/r/20260501-synopsys-dw-dp-improvements-v2-0-d7e7f6bac77f@collabora.com
- Collect Reviewed-by on "Simplify driver data setting" patch (Andy Yan)
- Use of_drm_get_bridge_by_endpoint instead of devm_drm_of_get_bridge (Luca Ceresoli)
- Use FIELD_PREP_WM16 instead of FIELD_PREP_WM16_CONST (Chaoyi Chen)
- Rebase to latest drm-misc-next (Dropped Cristian's patches)
Changes in PATCHv2:
* Link to v1: https://lore.kernel.org/r/20260326-synopsys-dw-dp-improvements-v1-0-501849162290@collabora.com
* rebased to latest drm-misc-next
* prepended the Patches from Cristian's cleanup series, as they also
needed a rebase and should be merged first
https://lore.kernel.org/dri-devel/20260327-drm-rk-fixes-v3-0-fd2e6900c08c@collabora.com/
* fix issue with the audio stream (un)prepare handling in last patch
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
Sebastian Reichel (21):
drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach
drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback
drm/bridge: synopsys: dw-dp: Cancel pending HPD work
drm/bridge: synopsys: dw-dp: Document missing reset line deassert
drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal
drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition
drm/bridge: synopsys: dw-dp: Fix support for short I2C reads
drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid
drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED
drm/bridge: synopsys: dw-dp: Add follow-up bridge support
drm/bridge: Add out-of-band HPD notify handler
drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD
drm/rockchip: dw_dp: Implement out-of-band HPD handling
drm/bridge: synopsys: dw-dp: Add Runtime PM support
drm/rockchip: dw_dp: Add runtime PM support
drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access
drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot
drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable
drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp
dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells
drm/bridge: synopsys: dw-dp: Add audio support
.../bindings/display/rockchip/rockchip,dw-dp.yaml | 9 +-
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 782 ++++++++++++++++++---
drivers/gpu/drm/display/drm_bridge_connector.c | 6 +
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 190 ++++-
include/drm/bridge/dw_dp.h | 13 +-
include/drm/drm_bridge.h | 14 +
6 files changed, 901 insertions(+), 113 deletions(-)
---
base-commit: 35b6cf07851a2b430b0eefbcb7b5a856f9030264
change-id: 20260325-synopsys-dw-dp-improvements-7da2e98df1dd
Best regards,
--
Sebastian Reichel <sebastian.reichel@collabora.com>
^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 00/21] Synopsys DisplayPort Controller improvements for Rockchip platforms
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, Krzysztof Kozlowski, linux-kernel,
dri-devel, Sebastian Reichel, Alexey Charkov, linux-rockchip,
Damon Ding, Sashiko, kernel, linux-arm-kernel
This patch series updates the Synopsys Designware DisplayPort bridge
together with the only existing user: The Rockchip RK3576/RK3588:
1. Follow-up bridges (PHY, USB-C connector)
This is needed to get USB-C DP AltMode working; I've followed
the Qualcomm driver as reference
2. Runtime PM
The initial driver has been upstreamed without RPM; add it to
avoid wasting power when nothing is plugged
3. Audio
The initial driver has been upstreamed without audio support;
this adds all missing bits for audio with single stream transport
To properly make use of the bridge code the following USBDP PHY series
is also needed:
https://lore.kernel.org/all/20260714-rockchip-usbdp-cleanup-v13-0-6cb3e769d4c5@collabora.com/
This series can be applied without the USBDP side. In that case USB-C
support won't work as the mainline USBDP side does not register as a
DRM bridge. As upstream DT binding for USBDP does not yet support
proper ports, no such board should be upstream.
Boards using the DP controller for native DP connectors (or HDMI
bridges), currently directly link to the connector/hdmi bridge. These
are not affected by the USBDP changes and should keep working in any
case. The difference is, that this series the DRM side is aware of the
link. I don't have any of those boards, so this code path is untested.
I added a dedicated bridge callback for out-of-band hotplug events,
which is separate from the hotplug_notify. I have a feeling, that
there might be a better solution, but haven't found it. Please comment.
Changes in v8:
- Link to v7: https://patch.msgid.link/20260728-synopsys-dw-dp-improvements-v7-0-b7640fa8cf48@collabora.com
- Add new patch moving DP AUX bridge registration into
bridge_attach/detach callbacks, which simplifies cleanup and ensures
it is (un)registered at the right point in time (Sashiko)
- Update Fix incorrect resource lifetimes in bind callback patch to
ensure at the end of the probe, the device is fully functional,
also only register the bridge once all necessary bits are available
as it is in theory reachable at this point (Sashiko)
- Update the patch cancelling HPD worker to simply use
devm_work_autocancel() as a result of this rework
- Add a new patch describing why the reset line state is not explicitly
changed (Sashiko)
- Merge the two patches for the AUX transfer timeout handling to avoid a
temporary race condition and synchronize IRQs after the reset to fix
yet another race condition (Sashiko)
- Add a new patch to properly support short I2C reads (Sashiko)
- Rework Runtime PM handling again, so that the OOB handling is done
before the 100ms sleep. Also ensure it is enabled at the right point
in time after the cleanup changes. (Sashiko)
- Add new patch to drop useless reservation for SDP slot 0, which
fix a race condition (Sashiko)
- Also restore audio mute status when audio functionality is restored
after atomic disable/enable cycle (Sashiko)
- Setup SDP audio frame channel_allocation according to information
received from ALSA (Sashko)
- Sashiko feedback ignored by me and that may or may not appear again
in this version as Sashiko does not seem to properly take the whole
series into account and thus probably ignores the changelog
* A divide-by-zero vulnerability exists in dw_dp_video_enable when
processing a display mode with a clock value of 1; I believe this
clock cannot drive a real mode
* I2C-over-AUX reply status (NACK/DEFER) is silently discarded,
causing deferred I2C transactions to falsely succeed and return
garbage data; this is a false positive. It seems Sashiko got
confused by the bit patterns. Nothing is discarded.
* Potential Use-After-Free of the connector and encoder due to missing
DRM device reference in OOB hotplug event handling; Sashiko
missed that drm_for_each_bridge_in_chain() is now the renamed
drm_for_each_bridge_in_chain_scoped().
* Missing runtime PM acquisition in out-of-band HPD callbacks leads to
SError crashes once runtime PM is enabled; Sashiko failed to
notice that Runtime PM is only introduced in a later patch
* Calling a sleeping function (msleep) from a runtime PM resume
callback that is reachable from an atomic commit path; Sashiko
failed to understand that atomic DRM functions may sleep
* Missing system sleep callbacks prevent the device and its power
domain from suspending during system sleep for native DP
configurations; this needs separate investigation considering
system sleep is broken on recent Rockchip platforms (same error
appears a few times)
* There is a bunch of pre-existing issues that are fixed later
in the series
Changes in v7:
- Link to v6: https://lore.kernel.org/r/20260724-synopsys-dw-dp-improvements-v6-0-041d99a19c4e@collabora.com
- Move drm_bridge_add() to probe and only attach it in the bind
function, as the object is re-used and not fully cleared by
drm_bridge_remove (Sashiko)
- Add a new patch resetting the DP AUX sub-controller on message
timeout to have decent error recovery (Sashiko)
- Fix dp_dp_rockchip_get_vo_grf -> dw_dp_rockchip_get_vo_grf (Sashiko)
- Introduce pm_active in the PM runtime patch, to early exit
on atomic_disable when atomic_enable failed to get runtime
PM enabled (Sashiko)
- Introduce drm_bound in the PM runtime patch, which is (un)set
when the driver is bound to a DRM encoder and used to enable
IRQ enabling in the runtime PM handlers. This in turn protects
the system of potential spurious interrupts. (Sashiko)
- In the audio patch, fix support for hotplug with a running
audio stream. (Sashiko)
- To avoid further blowing up the series, I've decided not to
act on these "pre-existing" issues reported by Sashiko. I
think it makes sense to handle them separately:
* Short HPD and Long HPD events share a single boolean state
variable, causing critical hot plug events to be lost if
interrupts arrive in quick succession.
* DP AUX lifetime complains (registered too early / unregistered
too late); reported multiple times
* The driver fails to support DP AUX short reads, incorrectly
rejecting them with -EBUSY instead of returning the number
of bytes successfully transferred, which breaks I2C-over-AUX.
- Collected Reviewed-by from Krzysztof Kozlowski on the
DT binding patch
Changes in v6:
- Link to v5: https://lore.kernel.org/r/20260724-synopsys-dw-dp-improvements-v5-0-9445c2e87441@collabora.com
- Fix overlong commit message in mutex patch
- Update MEDIA_BUS_FMT_FIXED patch, to use input format as output format
when output format is MEDIA_BUS_FMT_FIXED (Sashiko)
- Drop patch moving dw_dp_bridge_atomic_get_output_bus_fmts (no longer
needed)
- Reword the comment about platforms other than RK3576 and RK3588
shouldn't get the VO GRF from the USBDP to make it more obvious
that no such platform exist at the moment and thus the alternative
path will not be implemented at this point in time (Sashiko)
- Update the runtime PM code to have much better error handling
as well as support for platforms not using runtime PM (Sashiko)
- Update the runtime PM code to keep the device suspended when
probed, but unbound as a nice side effect
- Fix error handling for dw_dp_audio_infoframe_send() in audio
patch (Sashiko)
- Fix typo in sample width check in audio patch (Sashiko)
- Improve cleanup of SDP in audio patch (Sashiko)
Changes in v5:
- Link to v4: https://lore.kernel.org/r/20260721-synopsys-dw-dp-improvements-v4-0-f0f4a4ede712@collabora.com
- Add new patch to cancel pending HPD work on unbind (Sashiko)
- Add new patch to add missing mutex cleanups on module removal (Sashiko)
- Add new patch moving dw_dp_bridge_atomic_get_output_bus_fmts
- Update MEDIA_BUS_FMT_FIXED patch to avoid fixed RGB888 format
and instead use whatever works for the negotiated bandwidth (Sashiko)
- Add more error checking for runtime PM patches (Sashiko)
- Ensure runtime PM is held in interrupt handler (Sashiko)
- Fix usbc_mode logic inversion for runtime PM in unbind (Sashiko)
- Move sleep for HPD_HOT_PLUG from hotplug handler to runtime
resume function; no need to sleep when runtime PM is kept
active and runtime PM resume is a slow operation anyways. (Sashiko)
- Add new patch to protect sdp_reg_bank from concurrent access (Sashiko)
- Add new patch to use regmap_set_bits in dw_dp_send_sdp()
- Update DT binding patch to be all about the selected solution and
mention the ABI break
- Clear SDP in the audio patch on unprepare (Sashiko)
- Add a comment, that only 1,2 or 8 channels are supported (Sashiko)
- Error out on unsupported sample_width (Sashiko)
- Fix missing error check for clock enable (Sashiko)
Changes in PATCHv4:
- Link to v3: https://lore.kernel.org/all/20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com/
- Drop "Simplify driver data setting" (effectively reverted later on)
- Add "Fix incorrect resource lifetimes in bind callback", which
affects all following patches quite a bit (Sashiko)
- Update "Add follow-up bridge support" to mention that fatal errors
are fine (Sashiko)
- Update "Add out-of-band HPD notify handler" to use
drm_for_each_bridge_in_chain() instead of
drm_for_each_bridge_in_chain_scoped() required after rebase to
latest drm-misc-next
- Add 100ms sleep in hpd detect functions after runtime resume in the
"Add Runtime PM support" patch (Andy Yan)
- Keep runtime PM enabled if DP is used without USB-C to avoid missing
HPD signals in "Add Runtime PM support" patch (Andy Yan)
- Update patch description of DT binding patch and drop RFC tag
- Use pm_runtime_resume_and_get() in the audio startup function, which
allows easy error handling (Sashiko)
- Avoid updating audio_interface in the audio prepare function when
an error related early exit happens (Sashiko)
- Add error handling for clock prepare calls in audio prepare function
(Sashiko)
Changes in PATCHv3:
- Link to v2: https://lore.kernel.org/r/20260501-synopsys-dw-dp-improvements-v2-0-d7e7f6bac77f@collabora.com
- Collect Reviewed-by on "Simplify driver data setting" patch (Andy Yan)
- Use of_drm_get_bridge_by_endpoint instead of devm_drm_of_get_bridge (Luca Ceresoli)
- Use FIELD_PREP_WM16 instead of FIELD_PREP_WM16_CONST (Chaoyi Chen)
- Rebase to latest drm-misc-next (Dropped Cristian's patches)
Changes in PATCHv2:
* Link to v1: https://lore.kernel.org/r/20260326-synopsys-dw-dp-improvements-v1-0-501849162290@collabora.com
* rebased to latest drm-misc-next
* prepended the Patches from Cristian's cleanup series, as they also
needed a rebase and should be merged first
https://lore.kernel.org/dri-devel/20260327-drm-rk-fixes-v3-0-fd2e6900c08c@collabora.com/
* fix issue with the audio stream (un)prepare handling in last patch
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
Sebastian Reichel (21):
drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach
drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback
drm/bridge: synopsys: dw-dp: Cancel pending HPD work
drm/bridge: synopsys: dw-dp: Document missing reset line deassert
drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal
drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition
drm/bridge: synopsys: dw-dp: Fix support for short I2C reads
drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid
drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED
drm/bridge: synopsys: dw-dp: Add follow-up bridge support
drm/bridge: Add out-of-band HPD notify handler
drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD
drm/rockchip: dw_dp: Implement out-of-band HPD handling
drm/bridge: synopsys: dw-dp: Add Runtime PM support
drm/rockchip: dw_dp: Add runtime PM support
drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access
drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot
drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable
drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp
dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells
drm/bridge: synopsys: dw-dp: Add audio support
.../bindings/display/rockchip/rockchip,dw-dp.yaml | 9 +-
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 782 ++++++++++++++++++---
drivers/gpu/drm/display/drm_bridge_connector.c | 6 +
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 190 ++++-
include/drm/bridge/dw_dp.h | 13 +-
include/drm/drm_bridge.h | 14 +
6 files changed, 901 insertions(+), 113 deletions(-)
---
base-commit: 35b6cf07851a2b430b0eefbcb7b5a856f9030264
change-id: 20260325-synopsys-dw-dp-improvements-7da2e98df1dd
Best regards,
--
Sebastian Reichel <sebastian.reichel@collabora.com>
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 01/21] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
Unregister the DP AUX device at the right spot as documented in the
drm_dp_aux_register() function description. This helps that it is
only accessed when the DRM device is ready and the bridge is powered
and initialized (further fixes are required for that).
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 55 +++++++++++++++++++++------------
1 file changed, 35 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 3445c82e6f50..112a49911309 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1813,7 +1813,36 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b
return &state->base;
}
+static int dw_dp_bridge_attach(struct drm_bridge *bridge,
+ struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+ struct device *dev = dp->dev;
+ int ret;
+
+ dp->aux.dev = dev;
+ dp->aux.drm_dev = encoder->dev;
+ dp->aux.name = dev_name(dev);
+ dp->aux.transfer = dw_dp_aux_transfer;
+
+ ret = drm_dp_aux_register(&dp->aux);
+ if (ret)
+ dev_err(dev, "Aux register failed: %d\n", ret);
+
+ return ret;
+}
+
+static void dw_dp_bridge_detach(struct drm_bridge *bridge)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ drm_dp_aux_unregister(&dp->aux);
+}
+
static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
+ .attach = dw_dp_bridge_attach,
+ .detach = dw_dp_bridge_detach,
.atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
@@ -2044,20 +2073,10 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
if (ret)
return ERR_PTR(ret);
- dp->aux.dev = dev;
- dp->aux.drm_dev = encoder->dev;
- dp->aux.name = dev_name(dev);
- dp->aux.transfer = dw_dp_aux_transfer;
- ret = drm_dp_aux_register(&dp->aux);
- if (ret) {
- dev_err_probe(dev, ret, "Aux register failed\n");
- return ERR_PTR(ret);
- }
-
ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err_probe(dev, ret, "Failed to attach bridge\n");
- goto unregister_aux;
+ return ERR_PTR(ret);
}
dw_dp_init_hw(dp);
@@ -2065,37 +2084,33 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
ret = phy_init(dp->phy);
if (ret) {
dev_err_probe(dev, ret, "phy init failed\n");
- goto unregister_aux;
+ return ERR_PTR(ret);
}
ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp);
if (ret)
- goto unregister_aux;
+ return ERR_PTR(ret);
dp->irq = platform_get_irq(pdev, 0);
if (dp->irq < 0) {
ret = dp->irq;
- goto unregister_aux;
+ return ERR_PTR(ret);
}
ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
IRQF_ONESHOT, dev_name(dev), dp);
if (ret) {
dev_err_probe(dev, ret, "failed to request irq\n");
- goto unregister_aux;
+ return ERR_PTR(ret);
}
return dp;
-
-unregister_aux:
- drm_dp_aux_unregister(&dp->aux);
- return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
- drm_dp_aux_unregister(&dp->aux);
+ /* nothing to do */
}
EXPORT_SYMBOL_GPL(dw_dp_unbind);
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 01/21] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
Unregister the DP AUX device at the right spot as documented in the
drm_dp_aux_register() function description. This helps that it is
only accessed when the DRM device is ready and the bridge is powered
and initialized (further fixes are required for that).
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 55 +++++++++++++++++++++------------
1 file changed, 35 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 3445c82e6f50..112a49911309 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1813,7 +1813,36 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b
return &state->base;
}
+static int dw_dp_bridge_attach(struct drm_bridge *bridge,
+ struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+ struct device *dev = dp->dev;
+ int ret;
+
+ dp->aux.dev = dev;
+ dp->aux.drm_dev = encoder->dev;
+ dp->aux.name = dev_name(dev);
+ dp->aux.transfer = dw_dp_aux_transfer;
+
+ ret = drm_dp_aux_register(&dp->aux);
+ if (ret)
+ dev_err(dev, "Aux register failed: %d\n", ret);
+
+ return ret;
+}
+
+static void dw_dp_bridge_detach(struct drm_bridge *bridge)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ drm_dp_aux_unregister(&dp->aux);
+}
+
static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
+ .attach = dw_dp_bridge_attach,
+ .detach = dw_dp_bridge_detach,
.atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
@@ -2044,20 +2073,10 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
if (ret)
return ERR_PTR(ret);
- dp->aux.dev = dev;
- dp->aux.drm_dev = encoder->dev;
- dp->aux.name = dev_name(dev);
- dp->aux.transfer = dw_dp_aux_transfer;
- ret = drm_dp_aux_register(&dp->aux);
- if (ret) {
- dev_err_probe(dev, ret, "Aux register failed\n");
- return ERR_PTR(ret);
- }
-
ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err_probe(dev, ret, "Failed to attach bridge\n");
- goto unregister_aux;
+ return ERR_PTR(ret);
}
dw_dp_init_hw(dp);
@@ -2065,37 +2084,33 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
ret = phy_init(dp->phy);
if (ret) {
dev_err_probe(dev, ret, "phy init failed\n");
- goto unregister_aux;
+ return ERR_PTR(ret);
}
ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp);
if (ret)
- goto unregister_aux;
+ return ERR_PTR(ret);
dp->irq = platform_get_irq(pdev, 0);
if (dp->irq < 0) {
ret = dp->irq;
- goto unregister_aux;
+ return ERR_PTR(ret);
}
ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
IRQF_ONESHOT, dev_name(dev), dp);
if (ret) {
dev_err_probe(dev, ret, "failed to request irq\n");
- goto unregister_aux;
+ return ERR_PTR(ret);
}
return dp;
-
-unregister_aux:
- drm_dp_aux_unregister(&dp->aux);
- return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
- drm_dp_aux_unregister(&dp->aux);
+ /* nothing to do */
}
EXPORT_SYMBOL_GPL(dw_dp_unbind);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 02/21] drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
Currently the Synopsys DesignWare DP controller driver's bind function
requests lots of resources using device managed functions. These are
free'd on driver removal instead of at unbind time. Fix this discrepancy
by introducing a new probe helper function and moving over the whole
bind function. This results in a fully functional DRM bridge once probe
succeeded. The only thing still happening when the component is bound
is the bridge attachment, which requires the encoder.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 69 +++++++++++++++++--------------
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 53 ++++++++++++++----------
include/drm/bridge/dw_dp.h | 5 ++-
3 files changed, 71 insertions(+), 56 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 112a49911309..7f8249514171 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1983,6 +1983,26 @@ static const struct regmap_config dw_dp_regmap_config = {
.rd_table = &dw_dp_readable_table,
};
+int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
+{
+ struct drm_bridge *bridge = &dp->bridge;
+ struct device *dev = dp->dev;
+ int ret;
+
+ ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to attach bridge\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_dp_bind);
+
+void dw_dp_unbind(struct dw_dp *dp)
+{
+ /* nothing to do */
+}
+EXPORT_SYMBOL_GPL(dw_dp_unbind);
+
static void dw_dp_phy_exit(void *data)
{
struct dw_dp *dp = data;
@@ -1990,13 +2010,12 @@ static void dw_dp_phy_exit(void *data)
phy_exit(dp->phy);
}
-struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
- const struct dw_dp_plat_data *plat_data)
+struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct dw_dp *dp;
+ struct device *dev = &pdev->dev;
struct drm_bridge *bridge;
void __iomem *res;
+ struct dw_dp *dp;
int ret;
dp = devm_drm_bridge_alloc(dev, struct dw_dp, bridge, &dw_dp_bridge_funcs);
@@ -2005,9 +2024,8 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
dp->dev = dev;
dp->pixel_mode = plat_data->pixel_mode;
-
dp->plat_data.max_link_rate = plat_data->max_link_rate;
- bridge = &dp->bridge;
+
mutex_init(&dp->irq_lock);
INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
init_completion(&dp->complete);
@@ -2064,21 +2082,6 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
return ERR_CAST(dp->rstc);
}
- bridge->of_node = dev->of_node;
- bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
- bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
- bridge->ycbcr_420_allowed = true;
-
- ret = devm_drm_bridge_add(dev, bridge);
- if (ret)
- return ERR_PTR(ret);
-
- ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
- if (ret) {
- dev_err_probe(dev, ret, "Failed to attach bridge\n");
- return ERR_PTR(ret);
- }
-
dw_dp_init_hw(dp);
ret = phy_init(dp->phy);
@@ -2091,11 +2094,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
if (ret)
return ERR_PTR(ret);
- dp->irq = platform_get_irq(pdev, 0);
- if (dp->irq < 0) {
- ret = dp->irq;
+ bridge = &dp->bridge;
+ bridge->of_node = dev->of_node;
+ bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+ bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
+ bridge->ycbcr_420_allowed = true;
+
+ ret = devm_drm_bridge_add(dev, bridge);
+ if (ret)
return ERR_PTR(ret);
- }
+
+ dp->irq = platform_get_irq(pdev, 0);
+ if (dp->irq < 0)
+ return ERR_PTR(dp->irq);
ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
IRQF_ONESHOT, dev_name(dev), dp);
@@ -2106,13 +2117,7 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
return dp;
}
-EXPORT_SYMBOL_GPL(dw_dp_bind);
-
-void dw_dp_unbind(struct dw_dp *dp)
-{
- /* nothing to do */
-}
-EXPORT_SYMBOL_GPL(dw_dp_unbind);
+EXPORT_SYMBOL_GPL(dw_dp_probe);
MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
MODULE_DESCRIPTION("DW DP Core Library");
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index b23efb153c9e..005938dc66c9 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -26,7 +26,7 @@
struct rockchip_dw_dp {
struct dw_dp *base;
struct device *dev;
- struct rockchip_encoder encoder;
+ struct rockchip_encoder *encoder;
};
static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
@@ -73,37 +73,28 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
{
- struct platform_device *pdev = to_platform_device(dev);
- const struct dw_dp_plat_data *plat_data;
+ struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
- struct rockchip_dw_dp *dp;
struct drm_encoder *encoder;
struct drm_connector *connector;
int ret;
- dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL);
- if (!dp)
+ dp->encoder = drmm_kzalloc(drm_dev, sizeof(*dp->encoder), GFP_KERNEL);
+ if (!dp->encoder)
return -ENOMEM;
- dp->dev = dev;
- platform_set_drvdata(pdev, dp);
-
- plat_data = of_device_get_match_data(dev);
- if (!plat_data)
- return -ENODEV;
-
- encoder = &dp->encoder.encoder;
+ encoder = &dp->encoder->encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
- rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
+ rockchip_drm_encoder_set_crtc_endpoint_id(dp->encoder, dev->of_node, 0, 0);
ret = drmm_encoder_init(drm_dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret)
return ret;
drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs);
- dp->base = dw_dp_bind(dev, encoder, plat_data);
- if (IS_ERR(dp->base))
- return PTR_ERR(dp->base);
+ ret = dw_dp_bind(dp->base, encoder);
+ if (ret)
+ return ret;
connector = drm_bridge_connector_init(drm_dev, encoder);
if (IS_ERR(connector)) {
@@ -128,12 +119,30 @@ static const struct component_ops dw_dp_rockchip_component_ops = {
.unbind = dw_dp_rockchip_unbind,
};
-static int dw_dp_probe(struct platform_device *pdev)
+static int dw_dp_rockchip_probe(struct platform_device *pdev)
{
+ const struct dw_dp_plat_data *plat_data;
+ struct device *dev = &pdev->dev;
+ struct rockchip_dw_dp *dp;
+
+ plat_data = of_device_get_match_data(dev);
+ if (!plat_data)
+ return -ENODEV;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, dp);
+ dp->dev = dev;
+
+ dp->base = dw_dp_probe(pdev, plat_data);
+ if (IS_ERR(dp->base))
+ return PTR_ERR(dp->base);
+
return component_add(&pdev->dev, &dw_dp_rockchip_component_ops);
}
-static void dw_dp_remove(struct platform_device *pdev)
+static void dw_dp_rockchip_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
@@ -161,8 +170,8 @@ static const struct of_device_id dw_dp_of_match[] = {
MODULE_DEVICE_TABLE(of, dw_dp_of_match);
struct platform_driver dw_dp_driver = {
- .probe = dw_dp_probe,
- .remove = dw_dp_remove,
+ .probe = dw_dp_rockchip_probe,
+ .remove = dw_dp_rockchip_remove,
.driver = {
.name = "dw-dp",
.of_match_table = dw_dp_of_match,
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 22105c3e8e4d..a82412a9e769 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -22,7 +22,8 @@ struct dw_dp_plat_data {
u8 pixel_mode;
};
-struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
- const struct dw_dp_plat_data *plat_data);
+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);
#endif /* __DW_DP__ */
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 02/21] drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
Currently the Synopsys DesignWare DP controller driver's bind function
requests lots of resources using device managed functions. These are
free'd on driver removal instead of at unbind time. Fix this discrepancy
by introducing a new probe helper function and moving over the whole
bind function. This results in a fully functional DRM bridge once probe
succeeded. The only thing still happening when the component is bound
is the bridge attachment, which requires the encoder.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 69 +++++++++++++++++--------------
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 53 ++++++++++++++----------
include/drm/bridge/dw_dp.h | 5 ++-
3 files changed, 71 insertions(+), 56 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 112a49911309..7f8249514171 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1983,6 +1983,26 @@ static const struct regmap_config dw_dp_regmap_config = {
.rd_table = &dw_dp_readable_table,
};
+int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
+{
+ struct drm_bridge *bridge = &dp->bridge;
+ struct device *dev = dp->dev;
+ int ret;
+
+ ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to attach bridge\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_dp_bind);
+
+void dw_dp_unbind(struct dw_dp *dp)
+{
+ /* nothing to do */
+}
+EXPORT_SYMBOL_GPL(dw_dp_unbind);
+
static void dw_dp_phy_exit(void *data)
{
struct dw_dp *dp = data;
@@ -1990,13 +2010,12 @@ static void dw_dp_phy_exit(void *data)
phy_exit(dp->phy);
}
-struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
- const struct dw_dp_plat_data *plat_data)
+struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct dw_dp *dp;
+ struct device *dev = &pdev->dev;
struct drm_bridge *bridge;
void __iomem *res;
+ struct dw_dp *dp;
int ret;
dp = devm_drm_bridge_alloc(dev, struct dw_dp, bridge, &dw_dp_bridge_funcs);
@@ -2005,9 +2024,8 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
dp->dev = dev;
dp->pixel_mode = plat_data->pixel_mode;
-
dp->plat_data.max_link_rate = plat_data->max_link_rate;
- bridge = &dp->bridge;
+
mutex_init(&dp->irq_lock);
INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
init_completion(&dp->complete);
@@ -2064,21 +2082,6 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
return ERR_CAST(dp->rstc);
}
- bridge->of_node = dev->of_node;
- bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
- bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
- bridge->ycbcr_420_allowed = true;
-
- ret = devm_drm_bridge_add(dev, bridge);
- if (ret)
- return ERR_PTR(ret);
-
- ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
- if (ret) {
- dev_err_probe(dev, ret, "Failed to attach bridge\n");
- return ERR_PTR(ret);
- }
-
dw_dp_init_hw(dp);
ret = phy_init(dp->phy);
@@ -2091,11 +2094,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
if (ret)
return ERR_PTR(ret);
- dp->irq = platform_get_irq(pdev, 0);
- if (dp->irq < 0) {
- ret = dp->irq;
+ bridge = &dp->bridge;
+ bridge->of_node = dev->of_node;
+ bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+ bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
+ bridge->ycbcr_420_allowed = true;
+
+ ret = devm_drm_bridge_add(dev, bridge);
+ if (ret)
return ERR_PTR(ret);
- }
+
+ dp->irq = platform_get_irq(pdev, 0);
+ if (dp->irq < 0)
+ return ERR_PTR(dp->irq);
ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
IRQF_ONESHOT, dev_name(dev), dp);
@@ -2106,13 +2117,7 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
return dp;
}
-EXPORT_SYMBOL_GPL(dw_dp_bind);
-
-void dw_dp_unbind(struct dw_dp *dp)
-{
- /* nothing to do */
-}
-EXPORT_SYMBOL_GPL(dw_dp_unbind);
+EXPORT_SYMBOL_GPL(dw_dp_probe);
MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
MODULE_DESCRIPTION("DW DP Core Library");
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index b23efb153c9e..005938dc66c9 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -26,7 +26,7 @@
struct rockchip_dw_dp {
struct dw_dp *base;
struct device *dev;
- struct rockchip_encoder encoder;
+ struct rockchip_encoder *encoder;
};
static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
@@ -73,37 +73,28 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
{
- struct platform_device *pdev = to_platform_device(dev);
- const struct dw_dp_plat_data *plat_data;
+ struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
- struct rockchip_dw_dp *dp;
struct drm_encoder *encoder;
struct drm_connector *connector;
int ret;
- dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL);
- if (!dp)
+ dp->encoder = drmm_kzalloc(drm_dev, sizeof(*dp->encoder), GFP_KERNEL);
+ if (!dp->encoder)
return -ENOMEM;
- dp->dev = dev;
- platform_set_drvdata(pdev, dp);
-
- plat_data = of_device_get_match_data(dev);
- if (!plat_data)
- return -ENODEV;
-
- encoder = &dp->encoder.encoder;
+ encoder = &dp->encoder->encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
- rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
+ rockchip_drm_encoder_set_crtc_endpoint_id(dp->encoder, dev->of_node, 0, 0);
ret = drmm_encoder_init(drm_dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret)
return ret;
drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs);
- dp->base = dw_dp_bind(dev, encoder, plat_data);
- if (IS_ERR(dp->base))
- return PTR_ERR(dp->base);
+ ret = dw_dp_bind(dp->base, encoder);
+ if (ret)
+ return ret;
connector = drm_bridge_connector_init(drm_dev, encoder);
if (IS_ERR(connector)) {
@@ -128,12 +119,30 @@ static const struct component_ops dw_dp_rockchip_component_ops = {
.unbind = dw_dp_rockchip_unbind,
};
-static int dw_dp_probe(struct platform_device *pdev)
+static int dw_dp_rockchip_probe(struct platform_device *pdev)
{
+ const struct dw_dp_plat_data *plat_data;
+ struct device *dev = &pdev->dev;
+ struct rockchip_dw_dp *dp;
+
+ plat_data = of_device_get_match_data(dev);
+ if (!plat_data)
+ return -ENODEV;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, dp);
+ dp->dev = dev;
+
+ dp->base = dw_dp_probe(pdev, plat_data);
+ if (IS_ERR(dp->base))
+ return PTR_ERR(dp->base);
+
return component_add(&pdev->dev, &dw_dp_rockchip_component_ops);
}
-static void dw_dp_remove(struct platform_device *pdev)
+static void dw_dp_rockchip_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
@@ -161,8 +170,8 @@ static const struct of_device_id dw_dp_of_match[] = {
MODULE_DEVICE_TABLE(of, dw_dp_of_match);
struct platform_driver dw_dp_driver = {
- .probe = dw_dp_probe,
- .remove = dw_dp_remove,
+ .probe = dw_dp_rockchip_probe,
+ .remove = dw_dp_rockchip_remove,
.driver = {
.name = "dw-dp",
.of_match_table = dw_dp_of_match,
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 22105c3e8e4d..a82412a9e769 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -22,7 +22,8 @@ struct dw_dp_plat_data {
u8 pixel_mode;
};
-struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
- const struct dw_dp_plat_data *plat_data);
+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);
#endif /* __DW_DP__ */
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 03/21] drm/bridge: synopsys: dw-dp: Cancel pending HPD work
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
There is a race condition during device removal: If the HPD interrupt
started an HPD worker exactly when the device is removed, the worker
thread might access invalid resources. Avoid this by stopping any
pending work immediately after disabling the interrupt.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 7f8249514171..6289ed06e231 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -8,6 +8,7 @@
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/devm-helpers.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/media-bus-format.h>
@@ -2027,7 +2028,6 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
dp->plat_data.max_link_rate = plat_data->max_link_rate;
mutex_init(&dp->irq_lock);
- INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
init_completion(&dp->complete);
res = devm_platform_ioremap_resource(pdev, 0);
@@ -2108,6 +2108,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
if (dp->irq < 0)
return ERR_PTR(dp->irq);
+ ret = devm_work_autocancel(dev, &dp->hpd_work, dw_dp_hpd_work);
+ if (ret)
+ return ERR_PTR(ret);
+
ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
IRQF_ONESHOT, dev_name(dev), dp);
if (ret) {
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 03/21] drm/bridge: synopsys: dw-dp: Cancel pending HPD work
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
There is a race condition during device removal: If the HPD interrupt
started an HPD worker exactly when the device is removed, the worker
thread might access invalid resources. Avoid this by stopping any
pending work immediately after disabling the interrupt.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 7f8249514171..6289ed06e231 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -8,6 +8,7 @@
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/devm-helpers.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/media-bus-format.h>
@@ -2027,7 +2028,6 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
dp->plat_data.max_link_rate = plat_data->max_link_rate;
mutex_init(&dp->irq_lock);
- INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
init_completion(&dp->complete);
res = devm_platform_ioremap_resource(pdev, 0);
@@ -2108,6 +2108,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
if (dp->irq < 0)
return ERR_PTR(dp->irq);
+ ret = devm_work_autocancel(dev, &dp->hpd_work, dw_dp_hpd_work);
+ if (ret)
+ return ERR_PTR(ret);
+
ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
IRQF_ONESHOT, dev_name(dev), dp);
if (ret) {
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 04/21] drm/bridge: synopsys: dw-dp: Document missing reset line deassert
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
If the driver uses devm_reset_control_get_exclusive_deasserted() instead
of devm_reset_control_get() and thus automatically deasserts during
probe, the SoC will hang when the device is unbound.
This does not happen, when runtime PM is being used (not yet supported
in mainline), which suggests the power-domain involved requires this reset
line to be deasserted.
Even with runtime PM there is no gurantee that the power-domain is
disabled as it is shared. Considering the power-domain does not have
the reset dependency described in DT, document the problem but leave
things in the current state until a better solution is found as the
reset line is deasserted by default on all supported platforms.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 6289ed06e231..a8ae13cf91d6 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -2076,6 +2076,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
return ERR_CAST(dp->hdcp_clk);
}
+ /*
+ * This reset line is deasserted by default; asserting it hangs the SoC if the
+ * related power-domain is still active.
+ */
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");
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 04/21] drm/bridge: synopsys: dw-dp: Document missing reset line deassert
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
If the driver uses devm_reset_control_get_exclusive_deasserted() instead
of devm_reset_control_get() and thus automatically deasserts during
probe, the SoC will hang when the device is unbound.
This does not happen, when runtime PM is being used (not yet supported
in mainline), which suggests the power-domain involved requires this reset
line to be deasserted.
Even with runtime PM there is no gurantee that the power-domain is
disabled as it is shared. Considering the power-domain does not have
the reset dependency described in DT, document the problem but leave
things in the current state until a better solution is found as the
reset line is deasserted by default on all supported platforms.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 6289ed06e231..a8ae13cf91d6 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -2076,6 +2076,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
return ERR_CAST(dp->hdcp_clk);
}
+ /*
+ * This reset line is deasserted by default; asserting it hangs the SoC if the
+ * related power-domain is still active.
+ */
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");
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 05/21] drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
The driver is currently missing to fully clean up after itself. Ensure
that the mutex is cleaned up.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index a8ae13cf91d6..f373643a9cc1 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -2027,9 +2027,12 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
dp->pixel_mode = plat_data->pixel_mode;
dp->plat_data.max_link_rate = plat_data->max_link_rate;
- mutex_init(&dp->irq_lock);
init_completion(&dp->complete);
+ ret = devm_mutex_init(dev, &dp->irq_lock);
+ if (ret)
+ return ERR_PTR(ret);
+
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
return ERR_CAST(res);
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 05/21] drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
The driver is currently missing to fully clean up after itself. Ensure
that the mutex is cleaned up.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index a8ae13cf91d6..f373643a9cc1 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -2027,9 +2027,12 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
dp->pixel_mode = plat_data->pixel_mode;
dp->plat_data.max_link_rate = plat_data->max_link_rate;
- mutex_init(&dp->irq_lock);
init_completion(&dp->complete);
+ ret = devm_mutex_init(dev, &dp->irq_lock);
+ if (ret)
+ return ERR_PTR(ret);
+
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
return ERR_CAST(res);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 06/21] drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
The DP AUX transfer method uses a completion triggered by an interrupt,
which can timeout. If the function runs into the timeout and the
interrupt fires afterwards, the following DP aux transfer completion
would trigger immediately without waiting for the interrupt. This in
turn means the next one would also be broken and so on.
Fix this potential issue by re-initializing the completion directly
before sending the AUX command.
As this is racy (the interrupt might arrive between the completion
re-init and the new command being programmed), also reset the AUX
controller on timeouts and synchronize pending interrupts to gurantee
that there are no pending AUX transfers when the dw_dp_aux_transfer()
returns.
Due to lack of a sink, which generates AUX timeouts, this change is
effectively untested.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index f373643a9cc1..57b6a8fc98d0 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1466,6 +1466,8 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
if (WARN_ON(msg->size > 16))
return -E2BIG;
+ reinit_completion(&dp->complete);
+
switch (msg->request & ~DP_AUX_I2C_MOT) {
case DP_AUX_NATIVE_WRITE:
case DP_AUX_I2C_WRITE:
@@ -1492,6 +1494,12 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
status = wait_for_completion_timeout(&dp->complete, timeout);
if (!status) {
dev_err(dp->dev, "timeout waiting for AUX reply\n");
+ regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL,
+ AUX_RESET, FIELD_PREP(AUX_RESET, 1));
+ usleep_range(10, 20);
+ regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL,
+ AUX_RESET, FIELD_PREP(AUX_RESET, 0));
+ synchronize_irq(dp->irq);
return -ETIMEDOUT;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 06/21] drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
The DP AUX transfer method uses a completion triggered by an interrupt,
which can timeout. If the function runs into the timeout and the
interrupt fires afterwards, the following DP aux transfer completion
would trigger immediately without waiting for the interrupt. This in
turn means the next one would also be broken and so on.
Fix this potential issue by re-initializing the completion directly
before sending the AUX command.
As this is racy (the interrupt might arrive between the completion
re-init and the new command being programmed), also reset the AUX
controller on timeouts and synchronize pending interrupts to gurantee
that there are no pending AUX transfers when the dw_dp_aux_transfer()
returns.
Due to lack of a sink, which generates AUX timeouts, this change is
effectively untested.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index f373643a9cc1..57b6a8fc98d0 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1466,6 +1466,8 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
if (WARN_ON(msg->size > 16))
return -E2BIG;
+ reinit_completion(&dp->complete);
+
switch (msg->request & ~DP_AUX_I2C_MOT) {
case DP_AUX_NATIVE_WRITE:
case DP_AUX_I2C_WRITE:
@@ -1492,6 +1494,12 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
status = wait_for_completion_timeout(&dp->complete, timeout);
if (!status) {
dev_err(dp->dev, "timeout waiting for AUX reply\n");
+ regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL,
+ AUX_RESET, FIELD_PREP(AUX_RESET, 1));
+ usleep_range(10, 20);
+ regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL,
+ AUX_RESET, FIELD_PREP(AUX_RESET, 0));
+ synchronize_irq(dp->irq);
return -ETIMEDOUT;
}
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 07/21] drm/bridge: synopsys: dw-dp: Fix support for short I2C reads
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
The transfer functions returns the amount of bytes read for
DP_AUX_I2C_READ. By returning -EBUSY for short reads, the caller has
less information available what is going wrong and possibly simply
resends the read request. On sinks not supporting long reads, this will
simply run into the same issue again.
Instead it makes more sense to return the data from the short read with
the length information, which allows drm_dp_i2c_do_msg() to read data in
smaller chunks and succeed in the end.
Due to lack of a sink, which only supports short reads, this change is
effectively untested.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 57b6a8fc98d0..760e1e0f8234 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1513,7 +1513,7 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
if (msg->request & DP_AUX_I2C_READ) {
size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
- if (count != msg->size)
+ if (!count)
return -EBUSY;
ret = dw_dp_aux_read_data(dp, msg->buffer, count);
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 07/21] drm/bridge: synopsys: dw-dp: Fix support for short I2C reads
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
The transfer functions returns the amount of bytes read for
DP_AUX_I2C_READ. By returning -EBUSY for short reads, the caller has
less information available what is going wrong and possibly simply
resends the read request. On sinks not supporting long reads, this will
simply run into the same issue again.
Instead it makes more sense to return the data from the short read with
the length information, which allows drm_dp_i2c_do_msg() to read data in
smaller chunks and succeed in the end.
Due to lack of a sink, which only supports short reads, this change is
effectively untested.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 57b6a8fc98d0..760e1e0f8234 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1513,7 +1513,7 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
if (msg->request & DP_AUX_I2C_READ) {
size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
- if (count != msg->size)
+ if (!count)
return -EBUSY;
ret = dw_dp_aux_read_data(dp, msg->buffer, count);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 08/21] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Sashiko
If dw_dp_bandwidth_ok() returns false for all formats, *num_output_fmts
might end up becoming 0. In this case functions calling it assume that
nothing needs to be free'd, so free output_fmts within the function to
avoid leaking memory.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 760e1e0f8234..7127928c2650 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1804,6 +1804,11 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
output_fmts[j++] = fmt->bus_format;
}
+ if (j == 0) {
+ kfree(output_fmts);
+ output_fmts = NULL;
+ }
+
*num_output_fmts = j;
return output_fmts;
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 08/21] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
Sashiko, kernel, linux-arm-kernel
If dw_dp_bandwidth_ok() returns false for all formats, *num_output_fmts
might end up becoming 0. In this case functions calling it assume that
nothing needs to be free'd, so free output_fmts within the function to
avoid leaking memory.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 760e1e0f8234..7127928c2650 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1804,6 +1804,11 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
output_fmts[j++] = fmt->bus_format;
}
+ if (j == 0) {
+ kfree(output_fmts);
+ output_fmts = NULL;
+ }
+
*num_output_fmts = j;
return output_fmts;
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 09/21] drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Add support for MEDIA_BUS_FMT_FIXED, which is e.g. requested for USB-C
DP chains as the last bridge in the chain (aux-hpd-bridge) does not
implement atomic_get_output_bus_fmts(), which results in the generic
drm_atomic_bridge_chain_select_bus_fmts() code using MEDIA_BUS_FMT_FIXED
instead. For decent support of this, two areas are changed:
1. In atomic_check, resolving MEDIA_BUS_FMT_FIXED output format by
using the negotiated input format.
2. Implementing a custom .atomic_get_input_bus_fmts hook that, on
MEDIA_BUS_FMT_FIXED, advertises all bandwidth-validated formats
from dw_dp_bridge_atomic_get_output_bus_fmts(). This lets the
upstream encoder negotiate the best mutually supported format.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 34 +++++++++++++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 7127928c2650..42927335f0fd 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1537,6 +1537,7 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_connector_state *conn_state)
{
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ unsigned int out_bus_format = bridge_state->output_bus_cfg.format;
struct dw_dp *dp = bridge_to_dp(bridge);
struct dw_dp_bridge_state *state;
const struct dw_dp_output_format *fmt;
@@ -1547,7 +1548,10 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge,
state = to_dw_dp_bridge_state(bridge_state);
mode = &state->mode;
- fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format);
+ if (out_bus_format == MEDIA_BUS_FMT_FIXED)
+ out_bus_format = bridge_state->input_bus_cfg.format;
+
+ fmt = dw_dp_get_output_format(out_bus_format);
if (!fmt)
return -EINVAL;
@@ -1814,6 +1818,32 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
return output_fmts;
}
+static u32 *
+dw_dp_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts)
+{
+ /*
+ * MEDIA_BUS_FMT_FIXED means the downstream bridge does not constrain
+ * the bus format. In that case, advertise all formats supported by the
+ * DP link so the upstream encoder can negotiate the best match.
+ */
+ if (output_fmt == MEDIA_BUS_FMT_FIXED)
+ return dw_dp_bridge_atomic_get_output_bus_fmts(bridge,
+ bridge_state,
+ crtc_state,
+ conn_state,
+ num_input_fmts);
+
+ return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
+ crtc_state, conn_state,
+ output_fmt,
+ num_input_fmts);
+}
+
static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
{
struct dw_dp_bridge_state *state;
@@ -1860,7 +1890,7 @@ 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,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
- .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
+ .atomic_get_input_bus_fmts = dw_dp_bridge_atomic_get_input_bus_fmts,
.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
.atomic_check = dw_dp_bridge_atomic_check,
.mode_valid = dw_dp_bridge_mode_valid,
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 09/21] drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Add support for MEDIA_BUS_FMT_FIXED, which is e.g. requested for USB-C
DP chains as the last bridge in the chain (aux-hpd-bridge) does not
implement atomic_get_output_bus_fmts(), which results in the generic
drm_atomic_bridge_chain_select_bus_fmts() code using MEDIA_BUS_FMT_FIXED
instead. For decent support of this, two areas are changed:
1. In atomic_check, resolving MEDIA_BUS_FMT_FIXED output format by
using the negotiated input format.
2. Implementing a custom .atomic_get_input_bus_fmts hook that, on
MEDIA_BUS_FMT_FIXED, advertises all bandwidth-validated formats
from dw_dp_bridge_atomic_get_output_bus_fmts(). This lets the
upstream encoder negotiate the best mutually supported format.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 34 +++++++++++++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 7127928c2650..42927335f0fd 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1537,6 +1537,7 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_connector_state *conn_state)
{
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ unsigned int out_bus_format = bridge_state->output_bus_cfg.format;
struct dw_dp *dp = bridge_to_dp(bridge);
struct dw_dp_bridge_state *state;
const struct dw_dp_output_format *fmt;
@@ -1547,7 +1548,10 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge,
state = to_dw_dp_bridge_state(bridge_state);
mode = &state->mode;
- fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format);
+ if (out_bus_format == MEDIA_BUS_FMT_FIXED)
+ out_bus_format = bridge_state->input_bus_cfg.format;
+
+ fmt = dw_dp_get_output_format(out_bus_format);
if (!fmt)
return -EINVAL;
@@ -1814,6 +1818,32 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
return output_fmts;
}
+static u32 *
+dw_dp_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts)
+{
+ /*
+ * MEDIA_BUS_FMT_FIXED means the downstream bridge does not constrain
+ * the bus format. In that case, advertise all formats supported by the
+ * DP link so the upstream encoder can negotiate the best match.
+ */
+ if (output_fmt == MEDIA_BUS_FMT_FIXED)
+ return dw_dp_bridge_atomic_get_output_bus_fmts(bridge,
+ bridge_state,
+ crtc_state,
+ conn_state,
+ num_input_fmts);
+
+ return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
+ crtc_state, conn_state,
+ output_fmt,
+ num_input_fmts);
+}
+
static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
{
struct dw_dp_bridge_state *state;
@@ -1860,7 +1890,7 @@ 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,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
- .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
+ .atomic_get_input_bus_fmts = dw_dp_bridge_atomic_get_input_bus_fmts,
.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
.atomic_check = dw_dp_bridge_atomic_check,
.mode_valid = dw_dp_bridge_mode_valid,
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 10/21] drm/bridge: synopsys: dw-dp: Add follow-up bridge support
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Add support to use USB-C connectors with the DP altmode helper code on
devicetree based platforms. To get this working there must be a DRM
bridge chain from the DisplayPort controller to the USB-C connector.
E.g. on Rockchip RK3576:
root@rk3576 # cat /sys/kernel/debug/dri/0/encoder-0/bridges
bridge[0]: dw_dp_bridge_funcs
refcount: 7
type: [10] DP
OF: /soc/dp@27e40000:rockchip,rk3576-dp
ops: [0x47] detect edid hpd
bridge[1]: drm_aux_bridge_funcs
refcount: 4
type: [0] Unknown
OF: /soc/phy@2b010000:rockchip,rk3576-usbdp-phy
ops: [0x0]
bridge[2]: drm_aux_hpd_bridge_funcs
refcount: 5
type: [10] DP
OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector
ops: [0x4] hpd
It's fine to fatally error out when there is no follow-up bridge
as the Rockchip Designware Displayport controller is the only
user of the bridge helper and has the port marked as required
in its binding.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 42927335f0fd..7d42067df2cf 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -330,6 +330,8 @@ struct dw_dp {
struct dw_dp_plat_data plat_data;
u8 pixel_mode;
+ struct drm_bridge *next_bridge;
+
DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
};
@@ -2037,13 +2039,31 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
if (ret)
return dev_err_probe(dev, ret, "Failed to attach bridge\n");
+ dp->next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
+ if (IS_ERR(dp->next_bridge)) {
+ ret = PTR_ERR(dp->next_bridge);
+ return dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
+ }
+
+ ret = drm_bridge_attach(encoder, dp->next_bridge, bridge,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to attach next bridge\n");
+ goto put_next_bridge;
+ }
+
return 0;
+
+put_next_bridge:
+ drm_bridge_put(dp->next_bridge);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
- /* nothing to do */
+ drm_bridge_put(dp->next_bridge);
}
EXPORT_SYMBOL_GPL(dw_dp_unbind);
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 10/21] drm/bridge: synopsys: dw-dp: Add follow-up bridge support
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Add support to use USB-C connectors with the DP altmode helper code on
devicetree based platforms. To get this working there must be a DRM
bridge chain from the DisplayPort controller to the USB-C connector.
E.g. on Rockchip RK3576:
root@rk3576 # cat /sys/kernel/debug/dri/0/encoder-0/bridges
bridge[0]: dw_dp_bridge_funcs
refcount: 7
type: [10] DP
OF: /soc/dp@27e40000:rockchip,rk3576-dp
ops: [0x47] detect edid hpd
bridge[1]: drm_aux_bridge_funcs
refcount: 4
type: [0] Unknown
OF: /soc/phy@2b010000:rockchip,rk3576-usbdp-phy
ops: [0x0]
bridge[2]: drm_aux_hpd_bridge_funcs
refcount: 5
type: [10] DP
OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector
ops: [0x4] hpd
It's fine to fatally error out when there is no follow-up bridge
as the Rockchip Designware Displayport controller is the only
user of the bridge helper and has the port marked as required
in its binding.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 42927335f0fd..7d42067df2cf 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -330,6 +330,8 @@ struct dw_dp {
struct dw_dp_plat_data plat_data;
u8 pixel_mode;
+ struct drm_bridge *next_bridge;
+
DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
};
@@ -2037,13 +2039,31 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
if (ret)
return dev_err_probe(dev, ret, "Failed to attach bridge\n");
+ dp->next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
+ if (IS_ERR(dp->next_bridge)) {
+ ret = PTR_ERR(dp->next_bridge);
+ return dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
+ }
+
+ ret = drm_bridge_attach(encoder, dp->next_bridge, bridge,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to attach next bridge\n");
+ goto put_next_bridge;
+ }
+
return 0;
+
+put_next_bridge:
+ drm_bridge_put(dp->next_bridge);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
- /* nothing to do */
+ drm_bridge_put(dp->next_bridge);
}
EXPORT_SYMBOL_GPL(dw_dp_unbind);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 11/21] drm/bridge: Add out-of-band HPD notify handler
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
For DP bridges, that can be used for DP AltMode, it might be necessary
to enforce HPD status. There is an existing ->oob_hotplug_event() on
the DRM connector, but it currently just calls into hpd_notify().
As DP bridge drivers usually also implement .detect and that also
generates calls into hpd_notify, this is a bad place to force the
HPD status as the follow-up detect call might force it off again
resulting in all follow-up calls to the detection routine also
failing.
Avoid this by having a dedicated function for OOB events.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/display/drm_bridge_connector.c | 6 ++++++
include/drm/drm_bridge.h | 14 ++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 8b54069fa53a..632cc3ae3b54 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -180,6 +180,12 @@ static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connect
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
+ /* Notify all bridges in the pipeline of hotplug events. */
+ drm_for_each_bridge_in_chain(bridge_connector->encoder, bridge) {
+ if (bridge->funcs->oob_notify)
+ bridge->funcs->oob_notify(bridge, connector, status);
+ }
+
drm_bridge_connector_handle_hpd(bridge_connector, status);
}
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 58fff047f43b..713652fca033 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -540,6 +540,20 @@ struct drm_bridge_funcs {
*/
void (*hpd_disable)(struct drm_bridge *bridge);
+ /**
+ * @oob_notify:
+ *
+ * Notify the bridge of out of band hot plug detection.
+ *
+ * This callback is optional, it may be implemented by bridges that
+ * need to be notified of display connection or disconnection for
+ * internal reasons. One use case is to force the DP controllers HPD
+ * signal for USB-C DP AltMode.
+ */
+ void (*oob_notify)(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ enum drm_connector_status status);
+
/**
* @hdmi_tmds_char_rate_valid:
*
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 11/21] drm/bridge: Add out-of-band HPD notify handler
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
For DP bridges, that can be used for DP AltMode, it might be necessary
to enforce HPD status. There is an existing ->oob_hotplug_event() on
the DRM connector, but it currently just calls into hpd_notify().
As DP bridge drivers usually also implement .detect and that also
generates calls into hpd_notify, this is a bad place to force the
HPD status as the follow-up detect call might force it off again
resulting in all follow-up calls to the detection routine also
failing.
Avoid this by having a dedicated function for OOB events.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/display/drm_bridge_connector.c | 6 ++++++
include/drm/drm_bridge.h | 14 ++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 8b54069fa53a..632cc3ae3b54 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -180,6 +180,12 @@ static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connect
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
+ /* Notify all bridges in the pipeline of hotplug events. */
+ drm_for_each_bridge_in_chain(bridge_connector->encoder, bridge) {
+ if (bridge->funcs->oob_notify)
+ bridge->funcs->oob_notify(bridge, connector, status);
+ }
+
drm_bridge_connector_handle_hpd(bridge_connector, status);
}
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 58fff047f43b..713652fca033 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -540,6 +540,20 @@ struct drm_bridge_funcs {
*/
void (*hpd_disable)(struct drm_bridge *bridge);
+ /**
+ * @oob_notify:
+ *
+ * Notify the bridge of out of band hot plug detection.
+ *
+ * This callback is optional, it may be implemented by bridges that
+ * need to be notified of display connection or disconnection for
+ * internal reasons. One use case is to force the DP controllers HPD
+ * signal for USB-C DP AltMode.
+ */
+ void (*oob_notify)(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ enum drm_connector_status status);
+
/**
* @hdmi_tmds_char_rate_valid:
*
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 12/21] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
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@collabora.com>
---
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 7d42067df2cf..b21c2016dbd2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1886,6 +1886,19 @@ static void dw_dp_bridge_detach(struct drm_bridge *bridge)
drm_dp_aux_unregister(&dp->aux);
}
+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 = {
.attach = dw_dp_bridge_attach,
.detach = dw_dp_bridge_detach,
@@ -1900,6 +1913,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)
@@ -2029,6 +2043,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 drm_bridge *bridge = &dp->bridge;
@@ -2052,6 +2079,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);
+ }
+
return 0;
put_next_bridge:
@@ -2088,6 +2122,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;
init_completion(&dp->complete);
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
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 12/21] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
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@collabora.com>
---
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 7d42067df2cf..b21c2016dbd2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1886,6 +1886,19 @@ static void dw_dp_bridge_detach(struct drm_bridge *bridge)
drm_dp_aux_unregister(&dp->aux);
}
+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 = {
.attach = dw_dp_bridge_attach,
.detach = dw_dp_bridge_detach,
@@ -1900,6 +1913,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)
@@ -2029,6 +2043,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 drm_bridge *bridge = &dp->bridge;
@@ -2052,6 +2079,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);
+ }
+
return 0;
put_next_bridge:
@@ -2088,6 +2122,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;
init_completion(&dp->complete);
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
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 13/21] drm/rockchip: dw_dp: Implement out-of-band HPD handling
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Implement out-of-band hotplug handling, which will be used to receive
external hotplug information from the USB-C state machine. This is
currently handled by the USBDP PHY, which brings quite some trouble
as the register being accessed requires the power-domain from the DP
controller. Thus this patch prevents massive SError problems once
runtime PM is enabled. Apart from that it avoids custom TypeC HPD
info parsing in the USBDP PHY driver.
In contrast to the USBDP PHY this does not just enable the hotplug
signal when a DP AltMode capable adapter is plugged in, but instead
properly detects if a cable is plugged in for things like USB-C to
HDMI adapters.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 118 ++++++++++++++++++++++++++++--
1 file changed, 113 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index 005938dc66c9..d516b3910b51 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -7,9 +7,12 @@
*/
#include <linux/component.h>
+#include <linux/hw_bitfield.h>
#include <linux/media-bus-format.h>
+#include <linux/mfd/syscon.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/regmap.h>
#include <linux/videodev2.h>
#include <drm/bridge/dw_dp.h>
@@ -23,12 +26,48 @@
#include "rockchip_drm_drv.h"
+#define ROCKCHIP_MAX_CTRLS 2
+
+#define ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL BIT(10)
+#define ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG BIT(11)
+
+struct rockchip_dw_dp_plat_data {
+ u8 num_ctrls;
+ u32 ctrl_ids[ROCKCHIP_MAX_CTRLS];
+ u32 max_link_rate;
+ u8 pixel_mode;
+ u32 hpd_reg[ROCKCHIP_MAX_CTRLS];
+};
+
struct rockchip_dw_dp {
struct dw_dp *base;
struct device *dev;
+ const struct rockchip_dw_dp_plat_data *pdata;
+ struct regmap *vo_grf;
struct rockchip_encoder *encoder;
+ int id;
};
+static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw)
+{
+ struct rockchip_dw_dp *dp = data;
+ u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+
+ regmap_write(dp->vo_grf, hpd_reg,
+ FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, force_hpd_from_sw));
+}
+
+static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd)
+{
+ struct rockchip_dw_dp *dp = data;
+ u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+
+ dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd));
+
+ regmap_write(dp->vo_grf, hpd_reg,
+ FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, hpd));
+}
+
static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
@@ -71,6 +110,35 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
.atomic_check = dw_dp_encoder_atomic_check,
};
+static struct regmap *dw_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp)
+{
+ struct device_node *np = dev_of_node(dp->dev);
+ struct of_phandle_args args;
+ struct regmap *regmap;
+ int ret;
+
+ ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args);
+ if (ret)
+ return ERR_PTR(-ENODEV);
+
+ /*
+ * Limit this workaround to RK3576 and RK3588, potential future platforms
+ * reusing the driver should just add a VO GRF phandle in the DisplayPort
+ * controller DT node.
+ */
+ if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") &&
+ !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) {
+ regmap = ERR_PTR(-ENODEV);
+ goto out_put_node;
+ }
+
+ regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf");
+
+out_put_node:
+ of_node_put(args.np);
+ return regmap;
+}
+
static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
{
struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
@@ -121,19 +189,53 @@ static const struct component_ops dw_dp_rockchip_component_ops = {
static int dw_dp_rockchip_probe(struct platform_device *pdev)
{
- const struct dw_dp_plat_data *plat_data;
+ const struct rockchip_dw_dp_plat_data *plat_data_const;
+ struct dw_dp_plat_data *plat_data;
struct device *dev = &pdev->dev;
struct rockchip_dw_dp *dp;
+ struct resource *res;
+ int id;
- plat_data = of_device_get_match_data(dev);
- if (!plat_data)
+ plat_data_const = device_get_match_data(dev);
+ if (!plat_data_const)
return -ENODEV;
+ plat_data = devm_kzalloc(dev, sizeof(*plat_data), GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+
dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
platform_set_drvdata(pdev, dp);
dp->dev = dev;
+ dp->pdata = plat_data_const;
+
+ res = platform_get_mem_or_io(pdev, 0);
+ if (!res)
+ return -ENODEV;
+
+ /* find the DisplayPort ID from the io address */
+ dp->id = -ENODEV;
+ for (id = 0; id < plat_data_const->num_ctrls; id++) {
+ if (res->start == plat_data_const->ctrl_ids[id]) {
+ dp->id = id;
+ break;
+ }
+ }
+
+ if (dp->id < 0)
+ return dp->id;
+
+ dp->vo_grf = dw_dp_rockchip_get_vo_grf(dp);
+ if (IS_ERR(dp->vo_grf))
+ return PTR_ERR(dp->vo_grf);
+
+ plat_data->max_link_rate = plat_data_const->max_link_rate;
+ plat_data->pixel_mode = plat_data_const->pixel_mode;
+ plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel;
+ plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg;
+ plat_data->data = dp;
dp->base = dw_dp_probe(pdev, plat_data);
if (IS_ERR(dp->base))
@@ -147,14 +249,20 @@ static void dw_dp_rockchip_remove(struct platform_device *pdev)
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
-static const struct dw_dp_plat_data rk3588_dp_plat_data = {
+static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
+ .num_ctrls = 2,
+ .ctrl_ids = {0xfde50000, 0xfde60000},
.max_link_rate = 810000,
.pixel_mode = DW_DP_MP_QUAD_PIXEL,
+ .hpd_reg = {0x0000, 0x0008},
};
-static const struct dw_dp_plat_data rk3576_dp_plat_data = {
+static const struct rockchip_dw_dp_plat_data rk3576_dp_plat_data = {
+ .num_ctrls = 1,
+ .ctrl_ids = {0x27e40000},
.max_link_rate = 810000,
.pixel_mode = DW_DP_MP_DUAL_PIXEL,
+ .hpd_reg = {0x0000},
};
static const struct of_device_id dw_dp_of_match[] = {
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 13/21] drm/rockchip: dw_dp: Implement out-of-band HPD handling
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Implement out-of-band hotplug handling, which will be used to receive
external hotplug information from the USB-C state machine. This is
currently handled by the USBDP PHY, which brings quite some trouble
as the register being accessed requires the power-domain from the DP
controller. Thus this patch prevents massive SError problems once
runtime PM is enabled. Apart from that it avoids custom TypeC HPD
info parsing in the USBDP PHY driver.
In contrast to the USBDP PHY this does not just enable the hotplug
signal when a DP AltMode capable adapter is plugged in, but instead
properly detects if a cable is plugged in for things like USB-C to
HDMI adapters.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 118 ++++++++++++++++++++++++++++--
1 file changed, 113 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index 005938dc66c9..d516b3910b51 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -7,9 +7,12 @@
*/
#include <linux/component.h>
+#include <linux/hw_bitfield.h>
#include <linux/media-bus-format.h>
+#include <linux/mfd/syscon.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/regmap.h>
#include <linux/videodev2.h>
#include <drm/bridge/dw_dp.h>
@@ -23,12 +26,48 @@
#include "rockchip_drm_drv.h"
+#define ROCKCHIP_MAX_CTRLS 2
+
+#define ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL BIT(10)
+#define ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG BIT(11)
+
+struct rockchip_dw_dp_plat_data {
+ u8 num_ctrls;
+ u32 ctrl_ids[ROCKCHIP_MAX_CTRLS];
+ u32 max_link_rate;
+ u8 pixel_mode;
+ u32 hpd_reg[ROCKCHIP_MAX_CTRLS];
+};
+
struct rockchip_dw_dp {
struct dw_dp *base;
struct device *dev;
+ const struct rockchip_dw_dp_plat_data *pdata;
+ struct regmap *vo_grf;
struct rockchip_encoder *encoder;
+ int id;
};
+static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw)
+{
+ struct rockchip_dw_dp *dp = data;
+ u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+
+ regmap_write(dp->vo_grf, hpd_reg,
+ FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, force_hpd_from_sw));
+}
+
+static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd)
+{
+ struct rockchip_dw_dp *dp = data;
+ u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+
+ dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd));
+
+ regmap_write(dp->vo_grf, hpd_reg,
+ FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, hpd));
+}
+
static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
@@ -71,6 +110,35 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
.atomic_check = dw_dp_encoder_atomic_check,
};
+static struct regmap *dw_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp)
+{
+ struct device_node *np = dev_of_node(dp->dev);
+ struct of_phandle_args args;
+ struct regmap *regmap;
+ int ret;
+
+ ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args);
+ if (ret)
+ return ERR_PTR(-ENODEV);
+
+ /*
+ * Limit this workaround to RK3576 and RK3588, potential future platforms
+ * reusing the driver should just add a VO GRF phandle in the DisplayPort
+ * controller DT node.
+ */
+ if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") &&
+ !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) {
+ regmap = ERR_PTR(-ENODEV);
+ goto out_put_node;
+ }
+
+ regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf");
+
+out_put_node:
+ of_node_put(args.np);
+ return regmap;
+}
+
static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
{
struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
@@ -121,19 +189,53 @@ static const struct component_ops dw_dp_rockchip_component_ops = {
static int dw_dp_rockchip_probe(struct platform_device *pdev)
{
- const struct dw_dp_plat_data *plat_data;
+ const struct rockchip_dw_dp_plat_data *plat_data_const;
+ struct dw_dp_plat_data *plat_data;
struct device *dev = &pdev->dev;
struct rockchip_dw_dp *dp;
+ struct resource *res;
+ int id;
- plat_data = of_device_get_match_data(dev);
- if (!plat_data)
+ plat_data_const = device_get_match_data(dev);
+ if (!plat_data_const)
return -ENODEV;
+ plat_data = devm_kzalloc(dev, sizeof(*plat_data), GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+
dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
platform_set_drvdata(pdev, dp);
dp->dev = dev;
+ dp->pdata = plat_data_const;
+
+ res = platform_get_mem_or_io(pdev, 0);
+ if (!res)
+ return -ENODEV;
+
+ /* find the DisplayPort ID from the io address */
+ dp->id = -ENODEV;
+ for (id = 0; id < plat_data_const->num_ctrls; id++) {
+ if (res->start == plat_data_const->ctrl_ids[id]) {
+ dp->id = id;
+ break;
+ }
+ }
+
+ if (dp->id < 0)
+ return dp->id;
+
+ dp->vo_grf = dw_dp_rockchip_get_vo_grf(dp);
+ if (IS_ERR(dp->vo_grf))
+ return PTR_ERR(dp->vo_grf);
+
+ plat_data->max_link_rate = plat_data_const->max_link_rate;
+ plat_data->pixel_mode = plat_data_const->pixel_mode;
+ plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel;
+ plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg;
+ plat_data->data = dp;
dp->base = dw_dp_probe(pdev, plat_data);
if (IS_ERR(dp->base))
@@ -147,14 +249,20 @@ static void dw_dp_rockchip_remove(struct platform_device *pdev)
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
-static const struct dw_dp_plat_data rk3588_dp_plat_data = {
+static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
+ .num_ctrls = 2,
+ .ctrl_ids = {0xfde50000, 0xfde60000},
.max_link_rate = 810000,
.pixel_mode = DW_DP_MP_QUAD_PIXEL,
+ .hpd_reg = {0x0000, 0x0008},
};
-static const struct dw_dp_plat_data rk3576_dp_plat_data = {
+static const struct rockchip_dw_dp_plat_data rk3576_dp_plat_data = {
+ .num_ctrls = 1,
+ .ctrl_ids = {0x27e40000},
.max_link_rate = 810000,
.pixel_mode = DW_DP_MP_DUAL_PIXEL,
+ .hpd_reg = {0x0000},
};
static const struct of_device_id dw_dp_of_match[] = {
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 14/21] drm/bridge: synopsys: dw-dp: Add Runtime PM support
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Add runtime PM stubs to the Synopsys DesignWare DisplayPort bridge
driver. Support is not enabled automatically and must be hooked up
in the platform specific glue code.
The early bits of the dw_dp_probe function are split into a new
function called dw_dp_alloc, so that the platform driver can assign
it before running dw_dp_probe. This is necessary because the runtime
PM resume/suspend events land at the platform driver and must be
forwarded to the helper once runtime PM is enabled in the middle
of the probe function.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 233 ++++++++++++++++++++++++++------
include/drm/bridge/dw_dp.h | 7 +-
2 files changed, 201 insertions(+), 39 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index b21c2016dbd2..c24a5436e8fe 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -329,6 +329,9 @@ struct dw_dp {
struct dw_dp_link link;
struct dw_dp_plat_data plat_data;
u8 pixel_mode;
+ bool usbc_mode;
+ bool usbc_hpd;
+ bool pm_active;
struct drm_bridge *next_bridge;
@@ -1468,6 +1471,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) {
@@ -1666,6 +1674,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");
@@ -1716,10 +1731,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)
@@ -1740,6 +1760,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;
@@ -1892,6 +1916,14 @@ 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;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
if (dp->plat_data.hpd_sw_cfg)
dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high);
@@ -1951,6 +1983,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);
@@ -2062,14 +2099,21 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
struct device *dev = dp->dev;
int ret;
- ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ ret = pm_runtime_get_active(dp->dev, RPM_TRANSPARENT);
if (ret)
- return dev_err_probe(dev, ret, "Failed to attach bridge\n");
+ return ret;
+
+ ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to attach bridge\n");
+ goto put_runtime_pm;
+ }
dp->next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
if (IS_ERR(dp->next_bridge)) {
ret = PTR_ERR(dp->next_bridge);
- return dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
+ dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
+ goto put_runtime_pm;
}
ret = drm_bridge_attach(encoder, dp->next_bridge, bridge,
@@ -2079,11 +2123,15 @@ 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, dp->usbc_mode);
- if (dp->plat_data.hpd_sw_sel)
- dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1);
+ /* USB-C has out-of-band hotplug detection, so device may runtime suspend */
+ if (dp->usbc_mode) {
+ dev_dbg(dev, "USB-C mode\n");
+ pm_runtime_put_autosuspend(dp->dev);
}
return 0;
@@ -2091,6 +2139,9 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
put_next_bridge:
drm_bridge_put(dp->next_bridge);
+put_runtime_pm:
+ pm_runtime_put_autosuspend(dp->dev);
+
return ret;
}
EXPORT_SYMBOL_GPL(dw_dp_bind);
@@ -2098,6 +2149,9 @@ EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
drm_bridge_put(dp->next_bridge);
+
+ if (!dp->usbc_mode)
+ pm_runtime_put_autosuspend(dp->dev);
}
EXPORT_SYMBOL_GPL(dw_dp_unbind);
@@ -2108,76 +2162,125 @@ static void dw_dp_phy_exit(void *data)
phy_exit(dp->phy);
}
-struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
+static void dw_dp_manual_suspend(void *data)
+{
+ struct dw_dp *dp = data;
+
+ dw_dp_runtime_suspend(dp);
+}
+
+static void dw_dp_irq_free(void *data)
+{
+ struct dw_dp *dp = data;
+
+ /* ignore error as the interrupt needs to be free'd in any case */
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+
+ free_irq(dp->irq, dp);
+ dp->irq = -1;
+}
+
+static int dw_dp_irq_request(struct dw_dp *dp, int irq)
+{
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = request_threaded_irq(irq, NULL, dw_dp_irq,
+ IRQF_ONESHOT, dev_name(dp->dev), dp);
+ if (ret)
+ return dev_err_probe(dp->dev, ret, "failed to request irq\n");
+
+ dp->irq = irq;
+
+ return devm_add_action_or_reset(dp->dev, dw_dp_irq_free, dp);
+}
+
+struct dw_dp *dw_dp_alloc(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
{
struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- void __iomem *res;
struct dw_dp *dp;
- int ret;
dp = devm_drm_bridge_alloc(dev, struct dw_dp, bridge, &dw_dp_bridge_funcs);
if (IS_ERR(dp))
return ERR_CAST(dp);
dp->dev = dev;
+ dp->irq = -1;
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;
+ dp->plat_data.autosuspend_delay = plat_data->autosuspend_delay;
init_completion(&dp->complete);
+ return dp;
+}
+EXPORT_SYMBOL_GPL(dw_dp_alloc);
+
+int dw_dp_probe(struct dw_dp *dp)
+{
+ struct device *dev = dp->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct drm_bridge *bridge;
+ void __iomem *res;
+ int irq, ret;
+
ret = devm_mutex_init(dev, &dp->irq_lock);
if (ret)
- return ERR_PTR(ret);
+ return ret;
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
- return ERR_CAST(res);
+ return PTR_ERR(res);
dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config);
if (IS_ERR(dp->regmap)) {
dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n");
- return ERR_CAST(dp->regmap);
+ return PTR_ERR(dp->regmap);
}
dp->phy = devm_of_phy_get(dev, dev->of_node, NULL);
if (IS_ERR(dp->phy)) {
dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n");
- return ERR_CAST(dp->phy);
+ return PTR_ERR(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);
+ return PTR_ERR(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);
+ return PTR_ERR(dp->aux_clk);
}
dp->i2s_clk = devm_clk_get_optional(dev, "i2s");
if (IS_ERR(dp->i2s_clk)) {
dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n");
- return ERR_CAST(dp->i2s_clk);
+ return PTR_ERR(dp->i2s_clk);
}
dp->spdif_clk = devm_clk_get_optional(dev, "spdif");
if (IS_ERR(dp->spdif_clk)) {
dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n");
- return ERR_CAST(dp->spdif_clk);
+ return PTR_ERR(dp->spdif_clk);
}
dp->hdcp_clk = devm_clk_get(dev, "hdcp");
if (IS_ERR(dp->hdcp_clk)) {
dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n");
- return ERR_CAST(dp->hdcp_clk);
+ return PTR_ERR(dp->hdcp_clk);
}
/*
@@ -2187,20 +2290,34 @@ 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);
}
- 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)) {
+ dw_dp_runtime_resume(dp);
+
+ ret = devm_add_action_or_reset(dev, dw_dp_manual_suspend, dp);
+ if (ret)
+ return ret;
+ }
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;
@@ -2210,26 +2327,66 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
ret = devm_drm_bridge_add(dev, bridge);
if (ret)
- return ERR_PTR(ret);
-
- dp->irq = platform_get_irq(pdev, 0);
- if (dp->irq < 0)
- return ERR_PTR(dp->irq);
+ return ret;
ret = devm_work_autocancel(dev, &dp->hpd_work, dw_dp_hpd_work);
if (ret)
- return ERR_PTR(ret);
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ return dw_dp_irq_request(dp, irq);
+}
+EXPORT_SYMBOL_GPL(dw_dp_probe);
+
+int dw_dp_runtime_suspend(struct dw_dp *dp)
+{
+ if (dp->irq >= 0)
+ 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 = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
- IRQF_ONESHOT, dev_name(dev), dp);
+ ret = clk_prepare_enable(dp->aux_clk);
if (ret) {
- dev_err_probe(dev, ret, "failed to request irq\n");
- return ERR_PTR(ret);
+ clk_disable_unprepare(dp->apb_clk);
+ return ret;
}
- return dp;
+ 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);
+
+ if (dp->irq >= 0) {
+ /*
+ * HPD_HOT_PLUG bit is asserted only after the sink holds HPD
+ * high for at least 100ms.
+ */
+ msleep(110);
+
+ enable_irq(dp->irq);
+ }
+
+ return 0;
}
-EXPORT_SYMBOL_GPL(dw_dp_probe);
+EXPORT_SYMBOL_GPL(dw_dp_runtime_resume);
MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
MODULE_DESCRIPTION("DW DP Core Library");
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 79b2cdf0df99..1e23180b565e 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -18,6 +18,7 @@ enum {
};
struct dw_dp_plat_data {
+ int autosuspend_delay;
u32 max_link_rate;
u8 pixel_mode;
void *data;
@@ -28,5 +29,9 @@ struct dw_dp_plat_data {
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);
+struct dw_dp *dw_dp_alloc(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data);
+int dw_dp_probe(struct dw_dp *dp);
+
+int dw_dp_runtime_suspend(struct dw_dp *dp);
+int dw_dp_runtime_resume(struct dw_dp *dp);
#endif /* __DW_DP__ */
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 14/21] drm/bridge: synopsys: dw-dp: Add Runtime PM support
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Add runtime PM stubs to the Synopsys DesignWare DisplayPort bridge
driver. Support is not enabled automatically and must be hooked up
in the platform specific glue code.
The early bits of the dw_dp_probe function are split into a new
function called dw_dp_alloc, so that the platform driver can assign
it before running dw_dp_probe. This is necessary because the runtime
PM resume/suspend events land at the platform driver and must be
forwarded to the helper once runtime PM is enabled in the middle
of the probe function.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 233 ++++++++++++++++++++++++++------
include/drm/bridge/dw_dp.h | 7 +-
2 files changed, 201 insertions(+), 39 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index b21c2016dbd2..c24a5436e8fe 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -329,6 +329,9 @@ struct dw_dp {
struct dw_dp_link link;
struct dw_dp_plat_data plat_data;
u8 pixel_mode;
+ bool usbc_mode;
+ bool usbc_hpd;
+ bool pm_active;
struct drm_bridge *next_bridge;
@@ -1468,6 +1471,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) {
@@ -1666,6 +1674,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");
@@ -1716,10 +1731,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)
@@ -1740,6 +1760,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;
@@ -1892,6 +1916,14 @@ 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;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
if (dp->plat_data.hpd_sw_cfg)
dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high);
@@ -1951,6 +1983,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);
@@ -2062,14 +2099,21 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
struct device *dev = dp->dev;
int ret;
- ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ ret = pm_runtime_get_active(dp->dev, RPM_TRANSPARENT);
if (ret)
- return dev_err_probe(dev, ret, "Failed to attach bridge\n");
+ return ret;
+
+ ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to attach bridge\n");
+ goto put_runtime_pm;
+ }
dp->next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
if (IS_ERR(dp->next_bridge)) {
ret = PTR_ERR(dp->next_bridge);
- return dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
+ dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
+ goto put_runtime_pm;
}
ret = drm_bridge_attach(encoder, dp->next_bridge, bridge,
@@ -2079,11 +2123,15 @@ 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, dp->usbc_mode);
- if (dp->plat_data.hpd_sw_sel)
- dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1);
+ /* USB-C has out-of-band hotplug detection, so device may runtime suspend */
+ if (dp->usbc_mode) {
+ dev_dbg(dev, "USB-C mode\n");
+ pm_runtime_put_autosuspend(dp->dev);
}
return 0;
@@ -2091,6 +2139,9 @@ int dw_dp_bind(struct dw_dp *dp, struct drm_encoder *encoder)
put_next_bridge:
drm_bridge_put(dp->next_bridge);
+put_runtime_pm:
+ pm_runtime_put_autosuspend(dp->dev);
+
return ret;
}
EXPORT_SYMBOL_GPL(dw_dp_bind);
@@ -2098,6 +2149,9 @@ EXPORT_SYMBOL_GPL(dw_dp_bind);
void dw_dp_unbind(struct dw_dp *dp)
{
drm_bridge_put(dp->next_bridge);
+
+ if (!dp->usbc_mode)
+ pm_runtime_put_autosuspend(dp->dev);
}
EXPORT_SYMBOL_GPL(dw_dp_unbind);
@@ -2108,76 +2162,125 @@ static void dw_dp_phy_exit(void *data)
phy_exit(dp->phy);
}
-struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
+static void dw_dp_manual_suspend(void *data)
+{
+ struct dw_dp *dp = data;
+
+ dw_dp_runtime_suspend(dp);
+}
+
+static void dw_dp_irq_free(void *data)
+{
+ struct dw_dp *dp = data;
+
+ /* ignore error as the interrupt needs to be free'd in any case */
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+
+ free_irq(dp->irq, dp);
+ dp->irq = -1;
+}
+
+static int dw_dp_irq_request(struct dw_dp *dp, int irq)
+{
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
+
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = request_threaded_irq(irq, NULL, dw_dp_irq,
+ IRQF_ONESHOT, dev_name(dp->dev), dp);
+ if (ret)
+ return dev_err_probe(dp->dev, ret, "failed to request irq\n");
+
+ dp->irq = irq;
+
+ return devm_add_action_or_reset(dp->dev, dw_dp_irq_free, dp);
+}
+
+struct dw_dp *dw_dp_alloc(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
{
struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- void __iomem *res;
struct dw_dp *dp;
- int ret;
dp = devm_drm_bridge_alloc(dev, struct dw_dp, bridge, &dw_dp_bridge_funcs);
if (IS_ERR(dp))
return ERR_CAST(dp);
dp->dev = dev;
+ dp->irq = -1;
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;
+ dp->plat_data.autosuspend_delay = plat_data->autosuspend_delay;
init_completion(&dp->complete);
+ return dp;
+}
+EXPORT_SYMBOL_GPL(dw_dp_alloc);
+
+int dw_dp_probe(struct dw_dp *dp)
+{
+ struct device *dev = dp->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct drm_bridge *bridge;
+ void __iomem *res;
+ int irq, ret;
+
ret = devm_mutex_init(dev, &dp->irq_lock);
if (ret)
- return ERR_PTR(ret);
+ return ret;
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
- return ERR_CAST(res);
+ return PTR_ERR(res);
dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config);
if (IS_ERR(dp->regmap)) {
dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n");
- return ERR_CAST(dp->regmap);
+ return PTR_ERR(dp->regmap);
}
dp->phy = devm_of_phy_get(dev, dev->of_node, NULL);
if (IS_ERR(dp->phy)) {
dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n");
- return ERR_CAST(dp->phy);
+ return PTR_ERR(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);
+ return PTR_ERR(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);
+ return PTR_ERR(dp->aux_clk);
}
dp->i2s_clk = devm_clk_get_optional(dev, "i2s");
if (IS_ERR(dp->i2s_clk)) {
dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n");
- return ERR_CAST(dp->i2s_clk);
+ return PTR_ERR(dp->i2s_clk);
}
dp->spdif_clk = devm_clk_get_optional(dev, "spdif");
if (IS_ERR(dp->spdif_clk)) {
dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n");
- return ERR_CAST(dp->spdif_clk);
+ return PTR_ERR(dp->spdif_clk);
}
dp->hdcp_clk = devm_clk_get(dev, "hdcp");
if (IS_ERR(dp->hdcp_clk)) {
dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n");
- return ERR_CAST(dp->hdcp_clk);
+ return PTR_ERR(dp->hdcp_clk);
}
/*
@@ -2187,20 +2290,34 @@ 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);
}
- 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)) {
+ dw_dp_runtime_resume(dp);
+
+ ret = devm_add_action_or_reset(dev, dw_dp_manual_suspend, dp);
+ if (ret)
+ return ret;
+ }
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;
@@ -2210,26 +2327,66 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
ret = devm_drm_bridge_add(dev, bridge);
if (ret)
- return ERR_PTR(ret);
-
- dp->irq = platform_get_irq(pdev, 0);
- if (dp->irq < 0)
- return ERR_PTR(dp->irq);
+ return ret;
ret = devm_work_autocancel(dev, &dp->hpd_work, dw_dp_hpd_work);
if (ret)
- return ERR_PTR(ret);
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ return dw_dp_irq_request(dp, irq);
+}
+EXPORT_SYMBOL_GPL(dw_dp_probe);
+
+int dw_dp_runtime_suspend(struct dw_dp *dp)
+{
+ if (dp->irq >= 0)
+ 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 = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
- IRQF_ONESHOT, dev_name(dev), dp);
+ ret = clk_prepare_enable(dp->aux_clk);
if (ret) {
- dev_err_probe(dev, ret, "failed to request irq\n");
- return ERR_PTR(ret);
+ clk_disable_unprepare(dp->apb_clk);
+ return ret;
}
- return dp;
+ 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);
+
+ if (dp->irq >= 0) {
+ /*
+ * HPD_HOT_PLUG bit is asserted only after the sink holds HPD
+ * high for at least 100ms.
+ */
+ msleep(110);
+
+ enable_irq(dp->irq);
+ }
+
+ return 0;
}
-EXPORT_SYMBOL_GPL(dw_dp_probe);
+EXPORT_SYMBOL_GPL(dw_dp_runtime_resume);
MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
MODULE_DESCRIPTION("DW DP Core Library");
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 79b2cdf0df99..1e23180b565e 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -18,6 +18,7 @@ enum {
};
struct dw_dp_plat_data {
+ int autosuspend_delay;
u32 max_link_rate;
u8 pixel_mode;
void *data;
@@ -28,5 +29,9 @@ struct dw_dp_plat_data {
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);
+struct dw_dp *dw_dp_alloc(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data);
+int dw_dp_probe(struct dw_dp *dp);
+
+int dw_dp_runtime_suspend(struct dw_dp *dp);
+int dw_dp_runtime_resume(struct dw_dp *dp);
#endif /* __DW_DP__ */
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 15/21] drm/rockchip: dw_dp: Add runtime PM support
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Add support for runtime PM to the Rockchip RK3576/3588 Synopsys
DesignWare DisplayPort driver.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index d516b3910b51..dde3881efad8 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -12,6 +12,7 @@
#include <linux/mfd/syscon.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/videodev2.h>
@@ -194,7 +195,7 @@ static int dw_dp_rockchip_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct rockchip_dw_dp *dp;
struct resource *res;
- int id;
+ int id, ret;
plat_data_const = device_get_match_data(dev);
if (!plat_data_const)
@@ -231,16 +232,21 @@ static int dw_dp_rockchip_probe(struct platform_device *pdev)
if (IS_ERR(dp->vo_grf))
return PTR_ERR(dp->vo_grf);
+ plat_data->autosuspend_delay = 500;
plat_data->max_link_rate = plat_data_const->max_link_rate;
plat_data->pixel_mode = plat_data_const->pixel_mode;
plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel;
plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg;
plat_data->data = dp;
- dp->base = dw_dp_probe(pdev, plat_data);
+ dp->base = dw_dp_alloc(pdev, plat_data);
if (IS_ERR(dp->base))
return PTR_ERR(dp->base);
+ ret = dw_dp_probe(dp->base);
+ if (ret)
+ return ret;
+
return component_add(&pdev->dev, &dw_dp_rockchip_component_ops);
}
@@ -249,6 +255,24 @@ static void dw_dp_rockchip_remove(struct platform_device *pdev)
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
+static int dw_dp_rockchip_runtime_suspend(struct device *dev)
+{
+ struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
+
+ return dw_dp_runtime_suspend(dp->base);
+}
+
+static int dw_dp_rockchip_runtime_resume(struct device *dev)
+{
+ struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
+
+ return dw_dp_runtime_resume(dp->base);
+}
+
+static const struct dev_pm_ops dw_dp_pm_ops = {
+ RUNTIME_PM_OPS(dw_dp_rockchip_runtime_suspend, dw_dp_rockchip_runtime_resume, NULL)
+};
+
static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
.num_ctrls = 2,
.ctrl_ids = {0xfde50000, 0xfde60000},
@@ -283,5 +307,6 @@ struct platform_driver dw_dp_driver = {
.driver = {
.name = "dw-dp",
.of_match_table = dw_dp_of_match,
+ .pm = pm_ptr(&dw_dp_pm_ops),
},
};
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 15/21] drm/rockchip: dw_dp: Add runtime PM support
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Add support for runtime PM to the Rockchip RK3576/3588 Synopsys
DesignWare DisplayPort driver.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index d516b3910b51..dde3881efad8 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -12,6 +12,7 @@
#include <linux/mfd/syscon.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/videodev2.h>
@@ -194,7 +195,7 @@ static int dw_dp_rockchip_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct rockchip_dw_dp *dp;
struct resource *res;
- int id;
+ int id, ret;
plat_data_const = device_get_match_data(dev);
if (!plat_data_const)
@@ -231,16 +232,21 @@ static int dw_dp_rockchip_probe(struct platform_device *pdev)
if (IS_ERR(dp->vo_grf))
return PTR_ERR(dp->vo_grf);
+ plat_data->autosuspend_delay = 500;
plat_data->max_link_rate = plat_data_const->max_link_rate;
plat_data->pixel_mode = plat_data_const->pixel_mode;
plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel;
plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg;
plat_data->data = dp;
- dp->base = dw_dp_probe(pdev, plat_data);
+ dp->base = dw_dp_alloc(pdev, plat_data);
if (IS_ERR(dp->base))
return PTR_ERR(dp->base);
+ ret = dw_dp_probe(dp->base);
+ if (ret)
+ return ret;
+
return component_add(&pdev->dev, &dw_dp_rockchip_component_ops);
}
@@ -249,6 +255,24 @@ static void dw_dp_rockchip_remove(struct platform_device *pdev)
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
+static int dw_dp_rockchip_runtime_suspend(struct device *dev)
+{
+ struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
+
+ return dw_dp_runtime_suspend(dp->base);
+}
+
+static int dw_dp_rockchip_runtime_resume(struct device *dev)
+{
+ struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
+
+ return dw_dp_runtime_resume(dp->base);
+}
+
+static const struct dev_pm_ops dw_dp_pm_ops = {
+ RUNTIME_PM_OPS(dw_dp_rockchip_runtime_suspend, dw_dp_rockchip_runtime_resume, NULL)
+};
+
static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
.num_ctrls = 2,
.ctrl_ids = {0xfde50000, 0xfde60000},
@@ -283,5 +307,6 @@ struct platform_driver dw_dp_driver = {
.driver = {
.name = "dw-dp",
.of_match_table = dw_dp_of_match,
+ .pm = pm_ptr(&dw_dp_pm_ops),
},
};
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 16/21] drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Right now sdp_reg_bank is only used during atomic enable/disable and
thus there is no risk of two threads accidently claiming the same bit.
This changes once more SDP users (like audio support) are added, so
introduce a mutex to protect concurrent access to the bitmap.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index c24a5436e8fe..4d4af6a388b1 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -323,6 +323,8 @@ struct dw_dp {
struct dw_dp_hotplug hotplug;
/* Serialize hpd status access */
struct mutex irq_lock;
+ /* Serialize sdp_reg_bank access */
+ struct mutex sdp_lock;
struct drm_dp_aux aux;
@@ -1048,11 +1050,13 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
u32 reg;
int i, nr;
- nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
- if (nr < SDP_REG_BANK_SIZE)
- set_bit(nr, dp->sdp_reg_bank);
- else
- return -EBUSY;
+ scoped_guard(mutex, &dp->sdp_lock) {
+ nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+ if (nr < SDP_REG_BANK_SIZE)
+ set_bit(nr, dp->sdp_reg_bank);
+ else
+ return -EBUSY;
+ }
reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4;
@@ -1693,7 +1697,8 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
return;
}
- set_bit(0, dp->sdp_reg_bank);
+ scoped_guard(mutex, &dp->sdp_lock)
+ set_bit(0, dp->sdp_reg_bank);
ret = dw_dp_link_enable(dp);
if (ret < 0) {
@@ -1737,7 +1742,8 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
dw_dp_video_disable(dp);
dw_dp_link_disable(dp);
- bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+ scoped_guard(mutex, &dp->sdp_lock)
+ bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
dw_dp_reset(dp);
pm_runtime_put_autosuspend(dp->dev);
}
@@ -2237,6 +2243,10 @@ int dw_dp_probe(struct dw_dp *dp)
if (ret)
return ret;
+ ret = devm_mutex_init(dev, &dp->sdp_lock);
+ if (ret)
+ return ret;
+
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
return PTR_ERR(res);
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 16/21] drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Right now sdp_reg_bank is only used during atomic enable/disable and
thus there is no risk of two threads accidently claiming the same bit.
This changes once more SDP users (like audio support) are added, so
introduce a mutex to protect concurrent access to the bitmap.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index c24a5436e8fe..4d4af6a388b1 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -323,6 +323,8 @@ struct dw_dp {
struct dw_dp_hotplug hotplug;
/* Serialize hpd status access */
struct mutex irq_lock;
+ /* Serialize sdp_reg_bank access */
+ struct mutex sdp_lock;
struct drm_dp_aux aux;
@@ -1048,11 +1050,13 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
u32 reg;
int i, nr;
- nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
- if (nr < SDP_REG_BANK_SIZE)
- set_bit(nr, dp->sdp_reg_bank);
- else
- return -EBUSY;
+ scoped_guard(mutex, &dp->sdp_lock) {
+ nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+ if (nr < SDP_REG_BANK_SIZE)
+ set_bit(nr, dp->sdp_reg_bank);
+ else
+ return -EBUSY;
+ }
reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4;
@@ -1693,7 +1697,8 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
return;
}
- set_bit(0, dp->sdp_reg_bank);
+ scoped_guard(mutex, &dp->sdp_lock)
+ set_bit(0, dp->sdp_reg_bank);
ret = dw_dp_link_enable(dp);
if (ret < 0) {
@@ -1737,7 +1742,8 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
dw_dp_video_disable(dp);
dw_dp_link_disable(dp);
- bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+ scoped_guard(mutex, &dp->sdp_lock)
+ bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
dw_dp_reset(dp);
pm_runtime_put_autosuspend(dp->dev);
}
@@ -2237,6 +2243,10 @@ int dw_dp_probe(struct dw_dp *dp)
if (ret)
return ret;
+ ret = devm_mutex_init(dev, &dp->sdp_lock);
+ if (ret)
+ return ret;
+
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
return PTR_ERR(res);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 17/21] drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
The origin of this reservation is unclear, but it is a problem in the
atomic_enable code since it potentially races with the audio SDP
reservation once that feature is added. I suppose it was either meant to
be bitmap_zero(), but that is obviously not needed (and would also be a
problem for audio support) or some left-over development code before the
VSC SDP slot was allocated automatically.
From my tests SDP slot 0 works fine and can be used. If SDP slot 0
really needs to be reserved for some reason, the bit should be set in
the probe function to ensure there are no race conditions.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 4d4af6a388b1..193816126e44 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1697,9 +1697,6 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
return;
}
- scoped_guard(mutex, &dp->sdp_lock)
- set_bit(0, dp->sdp_reg_bank);
-
ret = dw_dp_link_enable(dp);
if (ret < 0) {
dev_err(dp->dev, "failed to enable link: %d\n", ret);
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 17/21] drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
The origin of this reservation is unclear, but it is a problem in the
atomic_enable code since it potentially races with the audio SDP
reservation once that feature is added. I suppose it was either meant to
be bitmap_zero(), but that is obviously not needed (and would also be a
problem for audio support) or some left-over development code before the
VSC SDP slot was allocated automatically.
From my tests SDP slot 0 works fine and can be used. If SDP slot 0
really needs to be reserved for some reason, the bit should be set in
the probe function to ensure there are no race conditions.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 4d4af6a388b1..193816126e44 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1697,9 +1697,6 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
return;
}
- scoped_guard(mutex, &dp->sdp_lock)
- set_bit(0, dp->sdp_reg_bank);
-
ret = dw_dp_link_enable(dp);
if (ret < 0) {
dev_err(dp->dev, "failed to enable link: %d\n", ret);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 18/21] drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
dw_dp_bridge_atomic_disable() bulk-cleared the whole SDP register bank
allocation bitmap via bitmap_zero() resulting in the loss of all
tracking information. This results in a slot potentially being handed
out again by dw_dp_send_sdp(), which is still considered to be held
by the previous owner. Then the previous owner might free up the wrong
SDP later on.
Instead of bulk clearing the tracking information, the new
implementation only clears the SDPs actually configured during
dw_dp_bridge_atomic_enable() instead of the entire bank. The
introduced functionality for that will also be used by the to-be-added
audio infrastructure.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 27 +++++++++++++++++++++++----
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 193816126e44..9b9c6d7c8cd2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -337,6 +337,7 @@ struct dw_dp {
struct drm_bridge *next_bridge;
+ int vsc_sdp_nr;
DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
};
@@ -1078,7 +1079,19 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
EN_HORIZONTAL_SDP << nr,
EN_HORIZONTAL_SDP << nr);
- return 0;
+ return nr;
+}
+
+static void dw_dp_clear_sdp(struct dw_dp *dp, int nr)
+{
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_VERTICAL_SDP << nr);
+
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_HORIZONTAL_SDP << nr);
+
+ scoped_guard(mutex, &dp->sdp_lock)
+ clear_bit(nr, dp->sdp_reg_bank);
}
static int dw_dp_send_vsc_sdp(struct dw_dp *dp)
@@ -1396,7 +1409,7 @@ static int dw_dp_video_enable(struct dw_dp *dp)
FIELD_PREP(VIDEO_STREAM_ENABLE, 1));
if (dw_dp_video_need_vsc_sdp(dp))
- dw_dp_send_vsc_sdp(dp);
+ dp->vsc_sdp_nr = dw_dp_send_vsc_sdp(dp);
return 0;
}
@@ -1739,8 +1752,12 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
dw_dp_video_disable(dp);
dw_dp_link_disable(dp);
- scoped_guard(mutex, &dp->sdp_lock)
- bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+
+ if (dp->vsc_sdp_nr >= 0) {
+ dw_dp_clear_sdp(dp, dp->vsc_sdp_nr);
+ dp->vsc_sdp_nr = -1;
+ }
+
dw_dp_reset(dp);
pm_runtime_put_autosuspend(dp->dev);
}
@@ -2326,6 +2343,8 @@ int dw_dp_probe(struct dw_dp *dp)
if (ret)
return ret;
+ dp->vsc_sdp_nr = -1;
+
bridge = &dp->bridge;
bridge->of_node = dev->of_node;
bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 18/21] drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
dw_dp_bridge_atomic_disable() bulk-cleared the whole SDP register bank
allocation bitmap via bitmap_zero() resulting in the loss of all
tracking information. This results in a slot potentially being handed
out again by dw_dp_send_sdp(), which is still considered to be held
by the previous owner. Then the previous owner might free up the wrong
SDP later on.
Instead of bulk clearing the tracking information, the new
implementation only clears the SDPs actually configured during
dw_dp_bridge_atomic_enable() instead of the entire bank. The
introduced functionality for that will also be used by the to-be-added
audio infrastructure.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 27 +++++++++++++++++++++++----
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 193816126e44..9b9c6d7c8cd2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -337,6 +337,7 @@ struct dw_dp {
struct drm_bridge *next_bridge;
+ int vsc_sdp_nr;
DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
};
@@ -1078,7 +1079,19 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
EN_HORIZONTAL_SDP << nr,
EN_HORIZONTAL_SDP << nr);
- return 0;
+ return nr;
+}
+
+static void dw_dp_clear_sdp(struct dw_dp *dp, int nr)
+{
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_VERTICAL_SDP << nr);
+
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_HORIZONTAL_SDP << nr);
+
+ scoped_guard(mutex, &dp->sdp_lock)
+ clear_bit(nr, dp->sdp_reg_bank);
}
static int dw_dp_send_vsc_sdp(struct dw_dp *dp)
@@ -1396,7 +1409,7 @@ static int dw_dp_video_enable(struct dw_dp *dp)
FIELD_PREP(VIDEO_STREAM_ENABLE, 1));
if (dw_dp_video_need_vsc_sdp(dp))
- dw_dp_send_vsc_sdp(dp);
+ dp->vsc_sdp_nr = dw_dp_send_vsc_sdp(dp);
return 0;
}
@@ -1739,8 +1752,12 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
dw_dp_video_disable(dp);
dw_dp_link_disable(dp);
- scoped_guard(mutex, &dp->sdp_lock)
- bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+
+ if (dp->vsc_sdp_nr >= 0) {
+ dw_dp_clear_sdp(dp, dp->vsc_sdp_nr);
+ dp->vsc_sdp_nr = -1;
+ }
+
dw_dp_reset(dp);
pm_runtime_put_autosuspend(dp->dev);
}
@@ -2326,6 +2343,8 @@ int dw_dp_probe(struct dw_dp *dp)
if (ret)
return ret;
+ dp->vsc_sdp_nr = -1;
+
bridge = &dp->bridge;
bridge->of_node = dev->of_node;
bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 19/21] drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Simplify dw_dp_send_sdp() a little bit by making use of
regmap_set_bits.
No functional change intended.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 9b9c6d7c8cd2..a39007f13a05 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1070,14 +1070,12 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
FIELD_PREP(SDP_REGS, get_unaligned_le32(payload)));
if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL)
- regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
- EN_VERTICAL_SDP << nr,
- EN_VERTICAL_SDP << nr);
+ regmap_set_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_VERTICAL_SDP << nr);
if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL)
- regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
- EN_HORIZONTAL_SDP << nr,
- EN_HORIZONTAL_SDP << nr);
+ regmap_set_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_HORIZONTAL_SDP << nr);
return nr;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 19/21] drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Simplify dw_dp_send_sdp() a little bit by making use of
regmap_set_bits.
No functional change intended.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 9b9c6d7c8cd2..a39007f13a05 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1070,14 +1070,12 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
FIELD_PREP(SDP_REGS, get_unaligned_le32(payload)));
if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL)
- regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
- EN_VERTICAL_SDP << nr,
- EN_VERTICAL_SDP << nr);
+ regmap_set_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_VERTICAL_SDP << nr);
if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL)
- regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
- EN_HORIZONTAL_SDP << nr,
- EN_HORIZONTAL_SDP << nr);
+ regmap_set_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_HORIZONTAL_SDP << nr);
return nr;
}
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 20/21] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel, Krzysztof Kozlowski
The RK3588 and RK3576 DesignWare DisplayPort controllers both have two
possible DAI interfaces: I2S and S/PDIF. Thus an argument is needed to
to select the right interface.
In addition to that the RK3576 DisplayPort controller is configured
with Multi Stream Transport (MST) enabled for up to 3 displays and
thus has a total of 6 DAI interfaces (I2S and S/PDIF for each possible
stream). Meanwhile the RK3588 does not support MST and thus has only 2
DAI interfaces.
The binding update from this patch has only been tested with the simple
single stream transport (SST) setup as the Linux driver does not yet
support MST. Once MST support is added, the plan is to simply add more
numbers to the argument, so that it looks like this for RK3576:
0 = I2S on stream 0,
1 = S/PDIF on stream 0
2 = I2S on stream 1,
3 = S/PDIF on stream 1
4 = I2S on stream 2,
5 = S/PDIF on stream 2
As the arguments are not part of the binding itself the audio side
is also ready for MST after this change.
Switching '#sound-dai-cells' from 0 to 1 without keeping compatibility
is an ABI break. The rationale for going that way is, that there is not
a single known driver implementation for the current binding. It's also
unclear how the current binding would be used (only support I2S or
S/PDIF for stream 0?). The mainline rk3588 DTS include sets it to 0, but
does not have any soundcard using the DAI. This will be fixed up
separately. The RK3576 does not set it at all.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
.../devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
index 2b0d9e23e943..c4f8959dd65d 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
@@ -25,7 +25,7 @@ description: |
* Supports up to 8/10 bits per color component
* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
* Pixel clock up to 594MHz
- * I2S, SPDIF audio interface
+ * I2S, S/PDIF audio interface
properties:
compatible:
@@ -46,7 +46,7 @@ properties:
- description: DisplayPort AUX clock
- description: HDCP clock
- description: I2S interface clock
- - description: SPDIF interfce clock
+ - description: S/PDIF interfce clock
clock-names:
minItems: 3
@@ -83,7 +83,8 @@ properties:
maxItems: 1
"#sound-dai-cells":
- const: 0
+ const: 1
+ description: 0 for I2S, 1 for S/PDIF
required:
- compatible
@@ -144,7 +145,7 @@ examples:
resets = <&cru SRST_DP0>;
phys = <&usbdp_phy0 PHY_TYPE_DP>;
power-domains = <&power RK3588_PD_VO0>;
- #sound-dai-cells = <0>;
+ #sound-dai-cells = <1>;
ports {
#address-cells = <1>;
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 20/21] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, Krzysztof Kozlowski, linux-kernel,
dri-devel, Sebastian Reichel, Alexey Charkov, linux-rockchip,
Damon Ding, kernel, linux-arm-kernel
The RK3588 and RK3576 DesignWare DisplayPort controllers both have two
possible DAI interfaces: I2S and S/PDIF. Thus an argument is needed to
to select the right interface.
In addition to that the RK3576 DisplayPort controller is configured
with Multi Stream Transport (MST) enabled for up to 3 displays and
thus has a total of 6 DAI interfaces (I2S and S/PDIF for each possible
stream). Meanwhile the RK3588 does not support MST and thus has only 2
DAI interfaces.
The binding update from this patch has only been tested with the simple
single stream transport (SST) setup as the Linux driver does not yet
support MST. Once MST support is added, the plan is to simply add more
numbers to the argument, so that it looks like this for RK3576:
0 = I2S on stream 0,
1 = S/PDIF on stream 0
2 = I2S on stream 1,
3 = S/PDIF on stream 1
4 = I2S on stream 2,
5 = S/PDIF on stream 2
As the arguments are not part of the binding itself the audio side
is also ready for MST after this change.
Switching '#sound-dai-cells' from 0 to 1 without keeping compatibility
is an ABI break. The rationale for going that way is, that there is not
a single known driver implementation for the current binding. It's also
unclear how the current binding would be used (only support I2S or
S/PDIF for stream 0?). The mainline rk3588 DTS include sets it to 0, but
does not have any soundcard using the DAI. This will be fixed up
separately. The RK3576 does not set it at all.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
.../devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
index 2b0d9e23e943..c4f8959dd65d 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
@@ -25,7 +25,7 @@ description: |
* Supports up to 8/10 bits per color component
* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
* Pixel clock up to 594MHz
- * I2S, SPDIF audio interface
+ * I2S, S/PDIF audio interface
properties:
compatible:
@@ -46,7 +46,7 @@ properties:
- description: DisplayPort AUX clock
- description: HDCP clock
- description: I2S interface clock
- - description: SPDIF interfce clock
+ - description: S/PDIF interfce clock
clock-names:
minItems: 3
@@ -83,7 +83,8 @@ properties:
maxItems: 1
"#sound-dai-cells":
- const: 0
+ const: 1
+ description: 0 for I2S, 1 for S/PDIF
required:
- compatible
@@ -144,7 +145,7 @@ examples:
resets = <&cru SRST_DP0>;
phys = <&usbdp_phy0 PHY_TYPE_DP>;
power-domains = <&power RK3588_PD_VO0>;
- #sound-dai-cells = <0>;
+ #sound-dai-cells = <1>;
ports {
#address-cells = <1>;
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 21/21] drm/bridge: synopsys: dw-dp: Add audio support
2026-07-31 14:42 ` Sebastian Reichel
@ 2026-07-31 14:42 ` Sebastian Reichel
-1 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
linux-arm-kernel, Sebastian Reichel
Implement audio support for the Synopsys DesignWare DisplayPort
controller.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 305 +++++++++++++++++++++++++++++++-
1 file changed, 304 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index a39007f13a05..a62fca20ac2f 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -24,17 +24,21 @@
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
+#include <sound/hdmi-codec.h>
+
#define DW_DP_VERSION_NUMBER 0x0000
#define DW_DP_VERSION_TYPE 0x0004
#define DW_DP_ID 0x0008
#define DW_DP_CONFIG_REG1 0x0100
+#define AUDIO_SELECT GENMASK(2, 1)
#define DW_DP_CONFIG_REG2 0x0104
#define DW_DP_CONFIG_REG3 0x0108
@@ -111,6 +115,10 @@
#define HBR_MODE_ENABLE BIT(10)
#define AUDIO_DATA_WIDTH GENMASK(9, 5)
#define AUDIO_DATA_IN_EN GENMASK(4, 1)
+#define AUDIO_DATA_IN_EN_CHANNEL12 BIT(0)
+#define AUDIO_DATA_IN_EN_CHANNEL34 BIT(1)
+#define AUDIO_DATA_IN_EN_CHANNEL56 BIT(2)
+#define AUDIO_DATA_IN_EN_CHANNEL78 BIT(3)
#define AUDIO_INF_SELECT BIT(0)
#define DW_DP_SDP_VERTICAL_CTRL 0x0500
@@ -254,6 +262,8 @@
#define SDP_REG_BANK_SIZE 16
+#define DW_DP_SDP_VERSION 0x12
+
struct dw_dp_link_caps {
bool enhanced_framing;
bool tps3_supported;
@@ -306,6 +316,19 @@ struct dw_dp_hotplug {
bool long_hpd;
};
+enum dw_dp_audio_interface_support {
+ DW_DP_AUDIO_I2S_ONLY = 0,
+ DW_DP_AUDIO_SPDIF_ONLY = 1,
+ DW_DP_AUDIO_I2S_AND_SPDIF = 2,
+ DW_DP_AUDIO_NONE = 3,
+};
+
+enum dw_dp_audio_interface {
+ DW_DP_AUDIO_I2S = 0,
+ DW_DP_AUDIO_SPDIF = 1,
+ DW_DP_AUDIO_UNUSED,
+};
+
struct dw_dp {
struct drm_bridge bridge;
struct device *dev;
@@ -321,10 +344,18 @@ struct dw_dp {
int irq;
struct work_struct hpd_work;
struct dw_dp_hotplug hotplug;
+ enum dw_dp_audio_interface audio_interface;
+ int audio_channels;
+ int audio_channel_allocation;
+ int audio_sample_width;
+ bool audio_muted;
+ int audio_sdp_nr;
/* Serialize hpd status access */
struct mutex irq_lock;
/* Serialize sdp_reg_bank access */
struct mutex sdp_lock;
+ /* Serialize audio state */
+ struct mutex audio_lock;
struct drm_dp_aux aux;
@@ -1681,6 +1712,252 @@ static int dw_dp_link_enable(struct dw_dp *dp)
return ret;
}
+static int dw_dp_audio_infoframe_send(struct dw_dp *dp)
+{
+ struct hdmi_audio_infoframe frame;
+ struct dw_dp_sdp sdp;
+ int ret;
+
+ ret = hdmi_audio_infoframe_init(&frame);
+ if (ret < 0)
+ return ret;
+
+ frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+ frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+ frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+ frame.channels = dp->audio_channels;
+ frame.channel_allocation = dp->audio_channel_allocation;
+
+ ret = hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VERSION);
+ if (ret < 0)
+ return ret;
+
+ sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
+
+ return dw_dp_send_sdp(dp, &sdp);
+}
+
+static void __dw_dp_audio_disable(struct dw_dp *dp)
+{
+ if (dp->audio_sdp_nr >= 0) {
+ dw_dp_clear_sdp(dp, dp->audio_sdp_nr);
+ dp->audio_sdp_nr = -1;
+ }
+
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP);
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_AUDIO_STREAM_SDP);
+
+ regmap_clear_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_DATA_IN_EN);
+
+ if (dp->audio_interface == DW_DP_AUDIO_SPDIF)
+ clk_disable_unprepare(dp->spdif_clk);
+ else if (dp->audio_interface == DW_DP_AUDIO_I2S)
+ clk_disable_unprepare(dp->i2s_clk);
+
+ dp->audio_interface = DW_DP_AUDIO_UNUSED;
+}
+
+static int __dw_dp_audio_enable(struct dw_dp *dp)
+{
+ u8 audio_data_in_en;
+
+ switch (dp->audio_channels) {
+ case 1:
+ case 2:
+ audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12;
+ break;
+ case 8:
+ audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12 |
+ AUDIO_DATA_IN_EN_CHANNEL34 |
+ AUDIO_DATA_IN_EN_CHANNEL56 |
+ AUDIO_DATA_IN_EN_CHANNEL78;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1,
+ AUDIO_DATA_IN_EN | NUM_CHANNELS | AUDIO_DATA_WIDTH |
+ AUDIO_INF_SELECT | HBR_MODE_ENABLE | AUDIO_MUTE,
+ FIELD_PREP(AUDIO_DATA_IN_EN, audio_data_in_en) |
+ FIELD_PREP(NUM_CHANNELS, dp->audio_channels - 1) |
+ FIELD_PREP(AUDIO_DATA_WIDTH, dp->audio_sample_width) |
+ FIELD_PREP(AUDIO_INF_SELECT, dp->audio_interface) |
+ FIELD_PREP(HBR_MODE_ENABLE, 0) |
+ FIELD_PREP(AUDIO_MUTE, dp->audio_muted));
+
+ /* Wait for inf switch */
+ usleep_range(20, 40);
+
+ /*
+ * Send audio stream during vertical and horizontal blanking periods.
+ * Send out audio timestamp SDP once per video frame during the vertical
+ * blanking period
+ */
+ regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP,
+ FIELD_PREP(EN_AUDIO_STREAM_SDP, 1) |
+ FIELD_PREP(EN_AUDIO_TIMESTAMP_SDP, 1));
+ regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_AUDIO_STREAM_SDP,
+ FIELD_PREP(EN_AUDIO_STREAM_SDP, 1));
+
+ if (dp->audio_sdp_nr >= 0) {
+ dw_dp_clear_sdp(dp, dp->audio_sdp_nr);
+ dp->audio_sdp_nr = -1;
+ }
+
+ dp->audio_sdp_nr = dw_dp_audio_infoframe_send(dp);
+ if (dp->audio_sdp_nr < 0)
+ return dp->audio_sdp_nr;
+
+ return 0;
+}
+
+static int dw_dp_audio_startup(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio startup\n");
+
+ return pm_runtime_get_active(dp->dev, RPM_TRANSPARENT);
+}
+
+static void dw_dp_audio_unprepare(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ guard(mutex)(&dp->audio_lock);
+
+ __dw_dp_audio_disable(dp);
+}
+
+static int dw_dp_audio_prepare(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+ u8 supported_audio_interfaces;
+ enum dw_dp_audio_interface audio_interface;
+ u32 cfg1;
+ int ret;
+
+ guard(mutex)(&dp->audio_lock);
+
+ /*
+ * prepare might be called multiple times, so release the clocks
+ * from previous calls to keep the calls in balance.
+ */
+ if (dp->audio_interface != DW_DP_AUDIO_UNUSED)
+ __dw_dp_audio_disable(dp);
+
+ /* The hardware is limited to 1,2 or 8 channels */
+ switch (params->cea.channels) {
+ case 1:
+ case 2:
+ case 8:
+ break;
+ default:
+ dev_err(dp->dev, "invalid audio channels %d\n", params->cea.channels);
+ return -EINVAL;
+ }
+
+ if (params->sample_width < 16 || params->sample_width > 24) {
+ dev_err(dp->dev, "invalid data sample width %d\n", params->sample_width);
+ return -EINVAL;
+ }
+
+ switch (daifmt->fmt) {
+ case HDMI_SPDIF:
+ audio_interface = DW_DP_AUDIO_SPDIF;
+ break;
+ case HDMI_I2S:
+ /*
+ * It is recommended to use SPDIF instead of I2S, since I2S mode requires
+ * manually inserting PCUV control bits from userspace and this is done
+ * automatically in hardware for SPDIF mode.
+ */
+ audio_interface = DW_DP_AUDIO_I2S;
+ break;
+ default:
+ dev_err(dp->dev, "invalid DAI format %d\n", daifmt->fmt);
+ return -EINVAL;
+ }
+
+ regmap_read(dp->regmap, DW_DP_CONFIG_REG1, &cfg1);
+ supported_audio_interfaces = FIELD_GET(AUDIO_SELECT, cfg1);
+
+ if (supported_audio_interfaces != DW_DP_AUDIO_I2S_AND_SPDIF &&
+ supported_audio_interfaces != audio_interface) {
+ dev_err(dp->dev, "unsupported DAI %d\n", daifmt->fmt);
+ return -EINVAL;
+ }
+
+ ret = clk_prepare_enable(dp->spdif_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dp->i2s_clk);
+ if (ret) {
+ clk_disable_unprepare(dp->spdif_clk);
+ return ret;
+ }
+
+ if (audio_interface == DW_DP_AUDIO_I2S)
+ clk_disable_unprepare(dp->spdif_clk);
+ else if (audio_interface == DW_DP_AUDIO_SPDIF)
+ clk_disable_unprepare(dp->i2s_clk);
+
+ dp->audio_channels = params->cea.channels;
+ dp->audio_channel_allocation = params->cea.channel_allocation;
+ dp->audio_sample_width = params->sample_width;
+ dp->audio_interface = audio_interface;
+
+ ret = __dw_dp_audio_enable(dp);
+ if (ret < 0) {
+ dev_err(dp->dev, "failed to enable audio\n");
+ __dw_dp_audio_disable(dp);
+ return ret;
+ }
+
+ dev_dbg(dp->dev, "audio prepare with %d channels using DAI=%d\n",
+ dp->audio_channels, dp->audio_interface);
+
+ return 0;
+}
+
+static void dw_dp_audio_shutdown(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio shutdown\n");
+
+ dw_dp_audio_unprepare(bridge, connector);
+ pm_runtime_put_autosuspend(dp->dev);
+}
+
+static int dw_dp_audio_mute_stream(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ bool enable, int direction)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio %smute\n", enable ? "" : "un");
+
+ dp->audio_muted = enable;
+
+ regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_MUTE,
+ FIELD_PREP(AUDIO_MUTE, enable));
+
+ return 0;
+}
+
static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
@@ -1719,6 +1996,14 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
dev_err(dp->dev, "failed to enable video: %d\n", ret);
return;
}
+
+ scoped_guard(mutex, &dp->audio_lock) {
+ if (dp->audio_interface != DW_DP_AUDIO_UNUSED) {
+ ret = __dw_dp_audio_enable(dp);
+ if (ret < 0)
+ dev_err(dp->dev, "failed to restore audio: %d\n", ret);
+ }
+ }
}
static void dw_dp_reset(struct dw_dp *dp)
@@ -1964,6 +2249,11 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
.detect = dw_dp_bridge_detect,
.edid_read = dw_dp_bridge_edid_read,
.oob_notify = dw_dp_bridge_oob_notify,
+
+ .dp_audio_startup = dw_dp_audio_startup,
+ .dp_audio_prepare = dw_dp_audio_prepare,
+ .dp_audio_shutdown = dw_dp_audio_shutdown,
+ .dp_audio_mute_stream = dw_dp_audio_mute_stream,
};
static int dw_dp_link_retrain(struct dw_dp *dp)
@@ -2259,6 +2549,10 @@ int dw_dp_probe(struct dw_dp *dp)
if (ret)
return ret;
+ ret = devm_mutex_init(dev, &dp->audio_lock);
+ if (ret)
+ return ret;
+
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
return PTR_ERR(res);
@@ -2342,12 +2636,21 @@ int dw_dp_probe(struct dw_dp *dp)
return ret;
dp->vsc_sdp_nr = -1;
+ dp->audio_interface = DW_DP_AUDIO_UNUSED;
+ dp->audio_sdp_nr = -1;
bridge = &dp->bridge;
bridge->of_node = dev->of_node;
- bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+ bridge->ops = DRM_BRIDGE_OP_DP_AUDIO |
+ DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD;
bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
bridge->ycbcr_420_allowed = true;
+ bridge->hdmi_audio_dev = dev;
+ bridge->hdmi_audio_max_i2s_playback_channels = 8;
+ bridge->hdmi_audio_dai_port = 1;
+ bridge->hdmi_audio_spdif_playback = true;
ret = devm_drm_bridge_add(dev, bridge);
if (ret)
--
2.53.0
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 21/21] drm/bridge: synopsys: dw-dp: Add audio support
@ 2026-07-31 14:42 ` Sebastian Reichel
0 siblings, 0 replies; 57+ messages in thread
From: Sebastian Reichel @ 2026-07-31 14:42 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
Simona Vetter, Dmitry Baryshkov, Luca Ceresoli, Philipp Zabel
Cc: devicetree, Dmitry Baryshkov, linux-kernel, dri-devel,
Sebastian Reichel, Alexey Charkov, linux-rockchip, Damon Ding,
kernel, linux-arm-kernel
Implement audio support for the Synopsys DesignWare DisplayPort
controller.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 305 +++++++++++++++++++++++++++++++-
1 file changed, 304 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index a39007f13a05..a62fca20ac2f 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -24,17 +24,21 @@
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
+#include <sound/hdmi-codec.h>
+
#define DW_DP_VERSION_NUMBER 0x0000
#define DW_DP_VERSION_TYPE 0x0004
#define DW_DP_ID 0x0008
#define DW_DP_CONFIG_REG1 0x0100
+#define AUDIO_SELECT GENMASK(2, 1)
#define DW_DP_CONFIG_REG2 0x0104
#define DW_DP_CONFIG_REG3 0x0108
@@ -111,6 +115,10 @@
#define HBR_MODE_ENABLE BIT(10)
#define AUDIO_DATA_WIDTH GENMASK(9, 5)
#define AUDIO_DATA_IN_EN GENMASK(4, 1)
+#define AUDIO_DATA_IN_EN_CHANNEL12 BIT(0)
+#define AUDIO_DATA_IN_EN_CHANNEL34 BIT(1)
+#define AUDIO_DATA_IN_EN_CHANNEL56 BIT(2)
+#define AUDIO_DATA_IN_EN_CHANNEL78 BIT(3)
#define AUDIO_INF_SELECT BIT(0)
#define DW_DP_SDP_VERTICAL_CTRL 0x0500
@@ -254,6 +262,8 @@
#define SDP_REG_BANK_SIZE 16
+#define DW_DP_SDP_VERSION 0x12
+
struct dw_dp_link_caps {
bool enhanced_framing;
bool tps3_supported;
@@ -306,6 +316,19 @@ struct dw_dp_hotplug {
bool long_hpd;
};
+enum dw_dp_audio_interface_support {
+ DW_DP_AUDIO_I2S_ONLY = 0,
+ DW_DP_AUDIO_SPDIF_ONLY = 1,
+ DW_DP_AUDIO_I2S_AND_SPDIF = 2,
+ DW_DP_AUDIO_NONE = 3,
+};
+
+enum dw_dp_audio_interface {
+ DW_DP_AUDIO_I2S = 0,
+ DW_DP_AUDIO_SPDIF = 1,
+ DW_DP_AUDIO_UNUSED,
+};
+
struct dw_dp {
struct drm_bridge bridge;
struct device *dev;
@@ -321,10 +344,18 @@ struct dw_dp {
int irq;
struct work_struct hpd_work;
struct dw_dp_hotplug hotplug;
+ enum dw_dp_audio_interface audio_interface;
+ int audio_channels;
+ int audio_channel_allocation;
+ int audio_sample_width;
+ bool audio_muted;
+ int audio_sdp_nr;
/* Serialize hpd status access */
struct mutex irq_lock;
/* Serialize sdp_reg_bank access */
struct mutex sdp_lock;
+ /* Serialize audio state */
+ struct mutex audio_lock;
struct drm_dp_aux aux;
@@ -1681,6 +1712,252 @@ static int dw_dp_link_enable(struct dw_dp *dp)
return ret;
}
+static int dw_dp_audio_infoframe_send(struct dw_dp *dp)
+{
+ struct hdmi_audio_infoframe frame;
+ struct dw_dp_sdp sdp;
+ int ret;
+
+ ret = hdmi_audio_infoframe_init(&frame);
+ if (ret < 0)
+ return ret;
+
+ frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+ frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+ frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+ frame.channels = dp->audio_channels;
+ frame.channel_allocation = dp->audio_channel_allocation;
+
+ ret = hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VERSION);
+ if (ret < 0)
+ return ret;
+
+ sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
+
+ return dw_dp_send_sdp(dp, &sdp);
+}
+
+static void __dw_dp_audio_disable(struct dw_dp *dp)
+{
+ if (dp->audio_sdp_nr >= 0) {
+ dw_dp_clear_sdp(dp, dp->audio_sdp_nr);
+ dp->audio_sdp_nr = -1;
+ }
+
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP);
+ regmap_clear_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_AUDIO_STREAM_SDP);
+
+ regmap_clear_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_DATA_IN_EN);
+
+ if (dp->audio_interface == DW_DP_AUDIO_SPDIF)
+ clk_disable_unprepare(dp->spdif_clk);
+ else if (dp->audio_interface == DW_DP_AUDIO_I2S)
+ clk_disable_unprepare(dp->i2s_clk);
+
+ dp->audio_interface = DW_DP_AUDIO_UNUSED;
+}
+
+static int __dw_dp_audio_enable(struct dw_dp *dp)
+{
+ u8 audio_data_in_en;
+
+ switch (dp->audio_channels) {
+ case 1:
+ case 2:
+ audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12;
+ break;
+ case 8:
+ audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12 |
+ AUDIO_DATA_IN_EN_CHANNEL34 |
+ AUDIO_DATA_IN_EN_CHANNEL56 |
+ AUDIO_DATA_IN_EN_CHANNEL78;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1,
+ AUDIO_DATA_IN_EN | NUM_CHANNELS | AUDIO_DATA_WIDTH |
+ AUDIO_INF_SELECT | HBR_MODE_ENABLE | AUDIO_MUTE,
+ FIELD_PREP(AUDIO_DATA_IN_EN, audio_data_in_en) |
+ FIELD_PREP(NUM_CHANNELS, dp->audio_channels - 1) |
+ FIELD_PREP(AUDIO_DATA_WIDTH, dp->audio_sample_width) |
+ FIELD_PREP(AUDIO_INF_SELECT, dp->audio_interface) |
+ FIELD_PREP(HBR_MODE_ENABLE, 0) |
+ FIELD_PREP(AUDIO_MUTE, dp->audio_muted));
+
+ /* Wait for inf switch */
+ usleep_range(20, 40);
+
+ /*
+ * Send audio stream during vertical and horizontal blanking periods.
+ * Send out audio timestamp SDP once per video frame during the vertical
+ * blanking period
+ */
+ regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+ EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP,
+ FIELD_PREP(EN_AUDIO_STREAM_SDP, 1) |
+ FIELD_PREP(EN_AUDIO_TIMESTAMP_SDP, 1));
+ regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+ EN_AUDIO_STREAM_SDP,
+ FIELD_PREP(EN_AUDIO_STREAM_SDP, 1));
+
+ if (dp->audio_sdp_nr >= 0) {
+ dw_dp_clear_sdp(dp, dp->audio_sdp_nr);
+ dp->audio_sdp_nr = -1;
+ }
+
+ dp->audio_sdp_nr = dw_dp_audio_infoframe_send(dp);
+ if (dp->audio_sdp_nr < 0)
+ return dp->audio_sdp_nr;
+
+ return 0;
+}
+
+static int dw_dp_audio_startup(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio startup\n");
+
+ return pm_runtime_get_active(dp->dev, RPM_TRANSPARENT);
+}
+
+static void dw_dp_audio_unprepare(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ guard(mutex)(&dp->audio_lock);
+
+ __dw_dp_audio_disable(dp);
+}
+
+static int dw_dp_audio_prepare(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+ u8 supported_audio_interfaces;
+ enum dw_dp_audio_interface audio_interface;
+ u32 cfg1;
+ int ret;
+
+ guard(mutex)(&dp->audio_lock);
+
+ /*
+ * prepare might be called multiple times, so release the clocks
+ * from previous calls to keep the calls in balance.
+ */
+ if (dp->audio_interface != DW_DP_AUDIO_UNUSED)
+ __dw_dp_audio_disable(dp);
+
+ /* The hardware is limited to 1,2 or 8 channels */
+ switch (params->cea.channels) {
+ case 1:
+ case 2:
+ case 8:
+ break;
+ default:
+ dev_err(dp->dev, "invalid audio channels %d\n", params->cea.channels);
+ return -EINVAL;
+ }
+
+ if (params->sample_width < 16 || params->sample_width > 24) {
+ dev_err(dp->dev, "invalid data sample width %d\n", params->sample_width);
+ return -EINVAL;
+ }
+
+ switch (daifmt->fmt) {
+ case HDMI_SPDIF:
+ audio_interface = DW_DP_AUDIO_SPDIF;
+ break;
+ case HDMI_I2S:
+ /*
+ * It is recommended to use SPDIF instead of I2S, since I2S mode requires
+ * manually inserting PCUV control bits from userspace and this is done
+ * automatically in hardware for SPDIF mode.
+ */
+ audio_interface = DW_DP_AUDIO_I2S;
+ break;
+ default:
+ dev_err(dp->dev, "invalid DAI format %d\n", daifmt->fmt);
+ return -EINVAL;
+ }
+
+ regmap_read(dp->regmap, DW_DP_CONFIG_REG1, &cfg1);
+ supported_audio_interfaces = FIELD_GET(AUDIO_SELECT, cfg1);
+
+ if (supported_audio_interfaces != DW_DP_AUDIO_I2S_AND_SPDIF &&
+ supported_audio_interfaces != audio_interface) {
+ dev_err(dp->dev, "unsupported DAI %d\n", daifmt->fmt);
+ return -EINVAL;
+ }
+
+ ret = clk_prepare_enable(dp->spdif_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dp->i2s_clk);
+ if (ret) {
+ clk_disable_unprepare(dp->spdif_clk);
+ return ret;
+ }
+
+ if (audio_interface == DW_DP_AUDIO_I2S)
+ clk_disable_unprepare(dp->spdif_clk);
+ else if (audio_interface == DW_DP_AUDIO_SPDIF)
+ clk_disable_unprepare(dp->i2s_clk);
+
+ dp->audio_channels = params->cea.channels;
+ dp->audio_channel_allocation = params->cea.channel_allocation;
+ dp->audio_sample_width = params->sample_width;
+ dp->audio_interface = audio_interface;
+
+ ret = __dw_dp_audio_enable(dp);
+ if (ret < 0) {
+ dev_err(dp->dev, "failed to enable audio\n");
+ __dw_dp_audio_disable(dp);
+ return ret;
+ }
+
+ dev_dbg(dp->dev, "audio prepare with %d channels using DAI=%d\n",
+ dp->audio_channels, dp->audio_interface);
+
+ return 0;
+}
+
+static void dw_dp_audio_shutdown(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio shutdown\n");
+
+ dw_dp_audio_unprepare(bridge, connector);
+ pm_runtime_put_autosuspend(dp->dev);
+}
+
+static int dw_dp_audio_mute_stream(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ bool enable, int direction)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio %smute\n", enable ? "" : "un");
+
+ dp->audio_muted = enable;
+
+ regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_MUTE,
+ FIELD_PREP(AUDIO_MUTE, enable));
+
+ return 0;
+}
+
static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
@@ -1719,6 +1996,14 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
dev_err(dp->dev, "failed to enable video: %d\n", ret);
return;
}
+
+ scoped_guard(mutex, &dp->audio_lock) {
+ if (dp->audio_interface != DW_DP_AUDIO_UNUSED) {
+ ret = __dw_dp_audio_enable(dp);
+ if (ret < 0)
+ dev_err(dp->dev, "failed to restore audio: %d\n", ret);
+ }
+ }
}
static void dw_dp_reset(struct dw_dp *dp)
@@ -1964,6 +2249,11 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
.detect = dw_dp_bridge_detect,
.edid_read = dw_dp_bridge_edid_read,
.oob_notify = dw_dp_bridge_oob_notify,
+
+ .dp_audio_startup = dw_dp_audio_startup,
+ .dp_audio_prepare = dw_dp_audio_prepare,
+ .dp_audio_shutdown = dw_dp_audio_shutdown,
+ .dp_audio_mute_stream = dw_dp_audio_mute_stream,
};
static int dw_dp_link_retrain(struct dw_dp *dp)
@@ -2259,6 +2549,10 @@ int dw_dp_probe(struct dw_dp *dp)
if (ret)
return ret;
+ ret = devm_mutex_init(dev, &dp->audio_lock);
+ if (ret)
+ return ret;
+
res = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(res))
return PTR_ERR(res);
@@ -2342,12 +2636,21 @@ int dw_dp_probe(struct dw_dp *dp)
return ret;
dp->vsc_sdp_nr = -1;
+ dp->audio_interface = DW_DP_AUDIO_UNUSED;
+ dp->audio_sdp_nr = -1;
bridge = &dp->bridge;
bridge->of_node = dev->of_node;
- bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+ bridge->ops = DRM_BRIDGE_OP_DP_AUDIO |
+ DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD;
bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
bridge->ycbcr_420_allowed = true;
+ bridge->hdmi_audio_dev = dev;
+ bridge->hdmi_audio_max_i2s_playback_channels = 8;
+ bridge->hdmi_audio_dai_port = 1;
+ bridge->hdmi_audio_spdif_playback = true;
ret = devm_drm_bridge_add(dev, bridge);
if (ret)
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 01/21] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 15:03 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 15:03 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: robh, conor+dt, dri-devel, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Moving `drm_dp_aux_unregister()` to `dw_dp_bridge_detach()` decouples the AUX device lifecycle from the component device's `devm_` resources, causing a Use-After-Free vulnerability on unbind or error paths.
Pre-existing issues:
- [High] The AUX transfer function does not reinitialize the completion object before waiting, leading to state corruption on timeout.
--
--- Patch [1]: [PATCH v8 01/21] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach ---
commit 356b23a8a66946521d5979525583ffc3eb67acf6
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach
This commit moves the DP AUX device registration from the bind/unbind
callbacks into the bridge attach/detach callbacks. This is intended to
ensure the AUX device is only accessible when the DRM device is ready
and the bridge is powered.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 3445c82e6f50e..112a499113095 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -1813,7 +1813,36 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b
> return &state->base;
> }
>
> +static int dw_dp_bridge_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
[ ... ]
> +static void dw_dp_bridge_detach(struct drm_bridge *bridge)
> +{
> + struct dw_dp *dp = bridge_to_dp(bridge);
> +
> + drm_dp_aux_unregister(&dp->aux);
> +}
[Severity: High]
Does moving the unregistration here decouple the AUX device lifecycle
from the component device's devres resources?
If a user unbinds the component device via sysfs while the DRM master
device is held open by userspace, the component unbinds and its devres
group frees dp and dp->regmap.
However, the DRM bridge remains attached until the DRM device is fully
closed, delaying dw_dp_bridge_detach(). The AUX char device
/dev/drm_dp_auxN would remain registered and accessible, backed by freed
memory.
A subsequent AUX transfer could dereference the freed dp->regmap, and
when the DRM device is finally closed, drm_encoder_cleanup() would call
dw_dp_bridge_detach(), which calls drm_dp_aux_unregister() on the freed
dp memory.
[ ... ]
> @@ -2065,37 +2084,33 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
> ret = phy_init(dp->phy);
> if (ret) {
> dev_err_probe(dev, ret, "phy init failed\n");
> - goto unregister_aux;
> + return ERR_PTR(ret);
> }
[Severity: High]
Can returning directly on these error paths without unregistering the AUX
device or detaching the bridge cause a use-after-free regression?
The dw_dp_bind() function allocates dp and dp->regmap using devm_ APIs.
Since the synchronous drm_dp_aux_unregister() was removed from the error
paths, the AUX device remains registered. When bind fails, the devres
group will free dp, leaving a dangling pointer.
[ ... ]
> void dw_dp_unbind(struct dw_dp *dp)
> {
> - drm_dp_aux_unregister(&dp->aux);
> + /* nothing to do */
> }
[Severity: High]
By removing the synchronous AUX unregistration here, could the char device
outlive the component and lead to the use-after-free described above?
[Severity: High]
This is a pre-existing issue, but does dw_dp_aux_transfer() fail to
reinitialize the completion object before waiting?
Looking at dw_dp_aux_transfer() in drivers/gpu/drm/bridge/synopsys/dw-dp.c:
regmap_write(dp->regmap, DW_DP_AUX_CMD, value);
status = wait_for_completion_timeout(&dp->complete, timeout);
If a transfer times out, it returns without decrementing the completion's
internal counter. When the hardware eventually completes the delayed
transfer, the IRQ handler calls complete(&dp->complete).
On the next AUX transfer, wait_for_completion_timeout() will immediately
return success by consuming the stray completion instead of waiting for
the new transfer. The driver would then read DW_DP_AUX_STATUS containing
in-progress or stale data.
Should reinit_completion(&dp->complete) be called before initiating the
hardware transfer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=1
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 02/21] drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 15:16 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 15:16 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: dri-devel, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Moving IRQ registration and hardware initialization to probe creates a window where an HPD interrupt causes a NULL pointer dereference on `dp->bridge.dev`.
Pre-existing issues:
- [Critical] Use-After-Free of the `dp` structure due to uncancelled `hpd_work` on driver removal.
--
--- Patch [2]: [PATCH v8 02/21] drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback ---
commit 9de3c09754cbe88e934e9c5f4adf581795df6d41
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback
Currently the Synopsys DesignWare DP controller driver's bind function
requests lots of resources using device managed functions. These are
free'd on driver removal instead of at unbind time. Fix this discrepancy
by introducing a new probe helper function and moving over the whole
bind function.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 112a499113095..7f82495141711 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
[ ... ]
> @@ -2091,11 +2094,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
> if (ret)
> return ERR_PTR(ret);
>
> - dp->irq = platform_get_irq(pdev, 0);
> - if (dp->irq < 0) {
> - ret = dp->irq;
> + bridge = &dp->bridge;
> + bridge->of_node = dev->of_node;
> + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> + bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
> + bridge->ycbcr_420_allowed = true;
> +
> + ret = devm_drm_bridge_add(dev, bridge);
> + if (ret)
> return ERR_PTR(ret);
> - }
> +
> + dp->irq = platform_get_irq(pdev, 0);
> + if (dp->irq < 0)
> + return ERR_PTR(dp->irq);
>
> ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
> IRQF_ONESHOT, dev_name(dev), dp);
[Severity: High]
Can this IRQ registration create a window for a NULL pointer dereference?
If a monitor is connected, the HPD interrupt can fire immediately after
devm_request_threaded_irq() completes in dw_dp_probe(). The interrupt handler
schedules dw_dp_hpd_work(), which directly dereferences dp->bridge.dev:
dw_dp_hpd_work()
drm_helper_hpd_irq_event(dp->bridge.dev);
Since the component bind hasn't run yet, drm_bridge_attach() hasn't been
called to assign dp->bridge.dev. Could this cause dw_dp_hpd_work() to
dereference a NULL pointer if an interrupt fires before the bridge is
attached?
> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> index b23efb153c9e6..005938dc66c9f 100644
> --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
[ ... ]
> @@ -128,12 +119,30 @@ static const struct component_ops dw_dp_rockchip_component_ops = {
> .unbind = dw_dp_rockchip_unbind,
> };
>
> -static int dw_dp_probe(struct platform_device *pdev)
> +static int dw_dp_rockchip_probe(struct platform_device *pdev)
> {
[ ... ]
> }
>
> -static void dw_dp_remove(struct platform_device *pdev)
> +static void dw_dp_rockchip_remove(struct platform_device *pdev)
> {
> component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
> }
[Severity: Critical]
This is a pre-existing issue, but does this remove function leave a pending
work item that can cause a use-after-free?
Since there is no explicit cancel_work_sync(&dp->hpd_work) before the driver
exits dw_dp_rockchip_remove(), if an HPD interrupt fires right before removal,
the scheduled dw_dp_hpd_work() might remain on the system workqueue.
The devm cleanup runs after this function returns, freeing the IRQ and the dp
structure. If the work executes after dp is freed, it will access freed memory
when it runs container_of(work, struct dw_dp, hpd_work).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=2
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 03/21] drm/bridge: synopsys: dw-dp: Cancel pending HPD work
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 15:30 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 15:30 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: dri-devel, devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The HPD worker `dw_dp_hpd_work` unconditionally accesses `dp->bridge.dev`, causing a NULL pointer dereference or use-after-free.
- [High] Missing `reinit_completion()` in `dw_dp_aux_transfer()` causes premature completions after a previous timeout.
- [High] Sleeping functions are called from within the `dw_dp_bridge_atomic_disable` callback.
--
--- Patch [3]: [PATCH v8 03/21] drm/bridge: synopsys: dw-dp: Cancel pending HPD work ---
commit 2a199fd0ed454a3e93c9003823239c66f8489c2a
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Cancel pending HPD work
There is a race condition during device removal: If the HPD interrupt
started an HPD worker exactly when the device is removed, the worker
thread might access invalid resources. Avoid this by stopping any
pending work immediately after disabling the interrupt.
Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 7f82495141711..6289ed06e2317 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
[ ... ]
> @@ -2027,7 +2028,6 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
> dp->plat_data.max_link_rate = plat_data->max_link_rate;
>
> mutex_init(&dp->irq_lock);
> - INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
> init_completion(&dp->complete);
[Severity: High]
This is a pre-existing issue, but can dw_dp_aux_transfer() complete
prematurely after a timeout?
We initialize the completion here, but dw_dp_aux_transfer() calls
wait_for_completion_timeout(&dp->complete, timeout) without calling
reinit_completion() before starting the transfer.
If a previous transfer times out, but a delayed hardware interrupt
later calls complete(&dp->complete), won't the next call to
wait_for_completion_timeout() return immediately with the stale
completion state?
>
> res = devm_platform_ioremap_resource(pdev, 0);
[ ... ]
> @@ -2108,6 +2108,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
> if (dp->irq < 0)
> return ERR_PTR(dp->irq);
>
> + ret = devm_work_autocancel(dev, &dp->hpd_work, dw_dp_hpd_work);
[Severity: High]
This isn't a bug introduced by this patch, but does the devres cleanup
still leave a window for a NULL pointer dereference in the worker?
Looking at dw_dp_hpd_work(), it unconditionally accesses dp->bridge.dev:
dw_dp_hpd_work()
drm_helper_hpd_irq_event(dp->bridge.dev);
And in dw_dp_link_retrain(), it does:
dw_dp_link_retrain()
ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
During component bind/unbind, drm_bridge_attach() sets up and tears down
bridge->dev. Since component_del() is typically called before devres
actions, the DRM device is unbound and bridge->dev becomes NULL before
this devm_work_autocancel() takes effect. Does this allow a pending HPD
interrupt to execute the worker against a NULL or freed bridge->dev
during teardown?
> + if (ret)
> + return ERR_PTR(ret);
> +
> ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
> IRQF_ONESHOT, dev_name(dev), dp);
> if (ret) {
[Severity: High]
This is a pre-existing issue, but can the atomic disable path sleep?
According to the DRM subsystem rules, atomic disable callbacks run in
atomic context and cannot sleep. However, the driver's
dw_dp_bridge_atomic_disable() unconditionally calls dw_dp_reset(), which
contains sleeping functions:
dw_dp_bridge_atomic_disable()
dw_dp_reset()
usleep_range(10, 20);
Since usleep_range() and disable_irq() can sleep, does this cause kernel
warnings or deadlocks when the DRM core disables the display pipeline
during modesetting?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=3
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 04/21] drm/bridge: synopsys: dw-dp: Document missing reset line deassert
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 15:38 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 15:38 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: devicetree, conor+dt, dri-devel, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The bridge subclasses the atomic state (`struct dw_dp_bridge_state`) but fails to implement `.atomic_create_state` correctly and does not fully copy subclass fields during `.atomic_duplicate_state`.
--
--- Patch [4]: [PATCH v8 04/21] drm/bridge: synopsys: dw-dp: Document missing reset line deassert ---
commit eae7b13a259bfd47bfead9cca99a86275cf00d9a
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Document missing reset line deassert
If the driver uses devm_reset_control_get_exclusive_deasserted() instead
of devm_reset_control_get() and thus automatically deasserts during
probe, the SoC will hang when the device is unbound.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 6289ed06e2317..a8ae13cf91d60 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -2076,6 +2076,10 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
> return ERR_CAST(dp->hdcp_clk);
> }
>
> + /*
> + * This reset line is deasserted by default; asserting it hangs the SoC if the
> + * related power-domain is still active.
> + */
> 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");
While reviewing this, I noticed a pre-existing issue in the same file:
drivers/gpu/drm/bridge/synopsys/dw-dp.c:
> static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
> {
> struct dw_dp_bridge_state *state;
>
> state = kzalloc_obj(*state);
> if (!state)
> return NULL;
>
> __drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
This isn't a bug introduced by this patch, but does this correctly copy
subclass fields?
The generic helper __drm_atomic_helper_bridge_duplicate_state() only copies
the base struct size, failing to carry over the subclass-specific fields
to the newly allocated state.
[ ... ]
> static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
> .attach = dw_dp_bridge_attach,
> .detach = dw_dp_bridge_detach,
> .atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
> .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> .atomic_create_state = drm_atomic_helper_bridge_create_state,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
This is also a pre-existing issue, but is the initial state allocation large
enough for the subclass?
When drm_mode_config_reset() creates the initial atomic state using the
generic helper, the allocated object is only sizeof(struct drm_bridge_state).
If a hotplug event occurs before the first modeset, dw_dp_hpd_work() calls
dw_dp_link_retrain() -> dw_dp_link_downgrade(), which fetches the initial
state via dw_dp_get_bridge_state() and reads subclass fields like state->mode
and state->bpp.
Could this result in a heap out-of-bounds read because the custom subclass
state wasn't fully allocated?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=4
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 05/21] drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 15:50 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 15:50 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: robh, dri-devel, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The DRM bridge is registered to the subsystem before its internal resources (IRQ and workqueue) are fully initialized, creating a race condition that leads to a Use-After-Free.
- [High] The completion structure `dp->complete` is not reinitialized before an AUX transfer, causing subsequent transfers to prematurely return if a previous transfer timed out.
- [High] The shared `dp->link` state is modified without locking during connector detection, racing with link training in the atomic modeset path.
--
--- Patch [5]: [PATCH v8 05/21] drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal ---
commit a0602d7d2fea7b25f801546caecd555dc659c65d
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal
The driver is currently missing to fully clean up after itself. Ensure
that the mutex is cleaned up.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index a8ae13cf91d60..f373643a9cc10 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -2027,9 +2027,12 @@ struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_
> dp->pixel_mode = plat_data->pixel_mode;
> dp->plat_data.max_link_rate = plat_data->max_link_rate;
>
> - mutex_init(&dp->irq_lock);
> init_completion(&dp->complete);
[Severity: High]
This is a pre-existing issue, but since we are looking at the initialization
of dp->complete, I noticed that dw_dp_aux_transfer() waits for this
completion without reinitializing it:
drivers/gpu/drm/bridge/synopsys/dw-dp.c:dw_dp_aux_transfer() {
...
regmap_write(dp->regmap, DW_DP_AUX_CMD, value);
status = wait_for_completion_timeout(&dp->complete, timeout);
if (!status) {
dev_err(dp->dev, "timeout waiting for AUX reply\n");
...
}
If a transfer times out, wait_for_completion_timeout() returns -ETIMEDOUT
and leaves the completion state unchanged.
If the hardware later generates a delayed interrupt and dw_dp_irq() calls
complete(&dp->complete), wouldn't the next invocation of
dw_dp_aux_transfer() immediately return success by consuming the stale
completion before the new transaction finishes?
>
> + ret = devm_mutex_init(dev, &dp->irq_lock);
> + if (ret)
> + return ERR_PTR(ret);
> +
[Severity: High]
This isn't a bug introduced by this patch, but looking further down in
dw_dp_probe(), could the probe error handling cause a Use-After-Free?
The bridge is registered globally before the IRQ is requested:
drivers/gpu/drm/bridge/synopsys/dw-dp.c:dw_dp_probe() {
...
ret = devm_drm_bridge_add(dev, bridge);
if (ret)
return ERR_PTR(ret);
dp->irq = platform_get_irq(pdev, 0);
if (dp->irq < 0)
return ERR_PTR(dp->irq);
...
}
If devm_request_threaded_irq() or another subsequent step fails, devres will
free the struct dw_dp memory.
If a KMS driver probing concurrently has already found and attached to the
bridge via of_drm_find_bridge(), wouldn't it hold a dangling pointer to the
freed bridge?
[Severity: High]
This is another pre-existing issue, but I wanted to ask about the locking
around the shared dp->link state.
When userspace triggers connector detection, dw_dp_link_parse()
zero-initializes dp->link fields:
drivers/gpu/drm/bridge/synopsys/dw-dp.c:dw_dp_link_parse() {
...
dw_dp_link_reset(link);
ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd);
...
}
Concurrently, a non-blocking atomic modeset commit executing in a workqueue
might call dw_dp_link_configure(), which reads those same fields:
drivers/gpu/drm/bridge/synopsys/dw-dp.c:dw_dp_link_configure() {
...
ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc);
...
}
If the read in dw_dp_link_configure() races with the reset in
dw_dp_link_parse(), could the driver program link->rate = 0 and
link->lanes = 0 into the PHY registers?
> res = devm_platform_ioremap_resource(pdev, 0);
> if (IS_ERR(res))
> return ERR_CAST(res);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=5
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/21] drm/bridge: synopsys: dw-dp: Fix support for short I2C reads
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 16:16 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 16:16 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: robh, devicetree, conor+dt, dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Buffer overflow and integer underflow due to missing bounds check on DP AUX read length.
--
--- Patch [7]: [PATCH v8 07/21] drm/bridge: synopsys: dw-dp: Fix support for short I2C reads ---
commit db106fc9593a19fc4e0e7028df22f1cecf9e1c41
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Fix support for short I2C reads
This commit modifies dw_dp_aux_transfer to support short I2C reads by
returning the data received rather than returning -EBUSY when the read
count doesn't exactly match the requested size.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -1513,7 +1513,7 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
> if (msg->request & DP_AUX_I2C_READ) {
> size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
>
> - if (count != msg->size)
> + if (!count)
> return -EBUSY;
>
> ret = dw_dp_aux_read_data(dp, msg->buffer, count);
[Severity: High]
Does this code adequately protect msg->buffer from overflow if the hardware
reports a count greater than msg->size?
By removing the exact length match check, an anomalous DisplayPort sink might
return an AUX_BYTES_READ value resulting in count > msg->size. This unverified
count would then be passed to dw_dp_aux_read_data(), which could write past
the bounds of msg->buffer.
Additionally, if the hardware reports an AUX_BYTES_READ of 0, could count
underflow to SIZE_MAX? Since SIZE_MAX is non-zero, it would bypass the !count
check, potentially causing dw_dp_aux_read_data() to attempt an excessively
large read and write operation.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=7
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 08/21] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 16:30 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 16:30 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: robh, dri-devel, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Unbalanced phy_power_off() and regulator_disable() if phy_power_on() fails in dw_dp_link_enable().
- [High] NULL pointer dereference in dw_dp_hpd_work() and dw_dp_link_retrain() due to missing bridge.dev check.
--
--- Patch [8]: [PATCH v8 08/21] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid ---
commit 749c8cb81f0227b8f3795cdf2bcba3b9e0038179
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid
If dw_dp_bandwidth_ok() returns false for all formats, *num_output_fmts
might end up becoming 0. This commit frees output_fmts within the function
in that case to avoid leaking memory.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 760e1e0f8234b..7127928c26506 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -1804,6 +1804,11 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
> output_fmts[j++] = fmt->bus_format;
> }
>
> + if (j == 0) {
> + kfree(output_fmts);
> + output_fmts = NULL;
> + }
> +
> *num_output_fmts = j;
>
> return output_fmts;
[Severity: High]
This isn't a bug introduced by this patch, but could there be an unbalanced
phy_power_off() if phy_power_on() fails during dw_dp_link_enable()?
In dw_dp_link_enable(), if phy_power_on() fails:
ret = phy_power_on(dp->phy);
if (ret)
return ret;
It returns without incrementing the PHY's power_count. However, if the DRM
core continues tracking the bridge as enabled, it will eventually call
dw_dp_link_disable() when the bridge is disabled.
dw_dp_link_disable() does:
phy_power_off(dp->phy);
Because the power_count was not incremented earlier, does this cause
phy_power_off() to decrement it to -1 and unconditionally call
regulator_disable(), potentially dropping power for other shared devices?
[Severity: High]
This is also a pre-existing issue, but can dw_dp_hpd_work() or
dw_dp_link_retrain() trigger a NULL pointer dereference if the bridge is
detached?
If the DRM encoder is unbound, drm_bridge_detach() explicitly sets
bridge->dev = NULL. If a physical hotplug event occurs before the bridge
device itself is unbound, the IRQ can fire and schedule dw_dp_hpd_work().
In dw_dp_hpd_work():
drm_helper_hpd_irq_event(dp->bridge.dev);
And in dw_dp_link_retrain():
struct drm_device *dev = dp->bridge.dev;
...
ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
Since neither function checks if dp->bridge.dev is NULL, could a hotplug
event after encoder unbind cause a kernel panic here?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=8
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 13/21] drm/rockchip: dw_dp: Implement out-of-band HPD handling
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 17:10 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 17:10 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: conor+dt, devicetree, dri-devel, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing runtime PM acquisition in `dw_dp_rockchip_hpd_sw_cfg` and `dw_dp_rockchip_hpd_sw_sel` causes potential SErrors when accessing `vo_grf`.
- [Medium] Logical flaw in `dw_dp_rockchip_get_vo_grf` prevents probing on future platforms, contrary to its comment.
--
--- Patch [13]: [PATCH v8 13/21] drm/rockchip: dw_dp: Implement out-of-band HPD handling ---
commit 1e82a4f2c1a3fedc07fd8e258e2008997986261a
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/rockchip: dw_dp: Implement out-of-band HPD handling
This commit implements out-of-band hotplug handling for USB-C by accessing
the vo_grf directly.
> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> index 005938dc66c9f..d516b3910b516 100644
> --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
[ ... ]
> @@ -23,12 +26,48 @@
>
> #include "rockchip_drm_drv.h"
>
> +#define ROCKCHIP_MAX_CTRLS 2
> +
> +#define ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL BIT(10)
> +#define ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG BIT(11)
[ ... ]
> +static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw)
> +{
> + struct rockchip_dw_dp *dp = data;
> + u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
> +
> + regmap_write(dp->vo_grf, hpd_reg,
> + FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, force_hpd_from_sw));
[Severity: High]
Does this register access require a runtime PM reference?
The commit message notes that this register requires the power-domain from the
DP controller, and that moving the access here prevents massive SError problems
once runtime PM is enabled. However, it appears this code accesses the register
without acquiring a runtime PM reference (e.g., pm_runtime_get_sync()).
If the DP controller is runtime suspended when a hotplug event occurs, won't
this regmap_write() still cause an SError?
> +}
> +
> +static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd)
> +{
> + struct rockchip_dw_dp *dp = data;
> + u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
> +
> + dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd));
> +
> + regmap_write(dp->vo_grf, hpd_reg,
> + FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, hpd));
[Severity: High]
Does this register access also require a runtime PM reference to prevent an
SError, for the same reasons as above?
> +}
> +
> static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
> struct drm_crtc_state *crtc_state,
> struct drm_connector_state *conn_state)
[ ... ]
> @@ -71,6 +110,35 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
> .atomic_check = dw_dp_encoder_atomic_check,
> };
>
> +static struct regmap *dw_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp)
> +{
> + struct device_node *np = dev_of_node(dp->dev);
> + struct of_phandle_args args;
> + struct regmap *regmap;
> + int ret;
> +
> + ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args);
> + if (ret)
> + return ERR_PTR(-ENODEV);
> +
> + /*
> + * Limit this workaround to RK3576 and RK3588, potential future platforms
> + * reusing the driver should just add a VO GRF phandle in the DisplayPort
> + * controller DT node.
> + */
> + if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") &&
> + !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) {
> + regmap = ERR_PTR(-ENODEV);
> + goto out_put_node;
> + }
[Severity: Medium]
Will this compatibility check prevent the driver from probing on future
platforms?
The comment suggests that future platforms should add a "rockchip,vo-grf"
phandle in the DisplayPort controller DT node. However, if the PHY node does
not match the legacy compatibles, this code unconditionally returns
ERR_PTR(-ENODEV) and never attempts to parse the "rockchip,vo-grf" phandle
from the DP controller node.
Does this effectively break probe for any new platforms?
> +
> + regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf");
> +
> +out_put_node:
> + of_node_put(args.np);
> + return regmap;
> +}
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=13
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 14/21] drm/bridge: synopsys: dw-dp: Add Runtime PM support
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 17:23 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 17:23 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: dri-devel, devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Asynchronous pm_runtime_put_autosuspend() during driver teardown and error paths is cancelled by pm_runtime_disable(), leaving the device permanently active and leaking clock resources.
- [High] phy_init() is called while the device is in its initial RPM_SUSPENDED state with APB and AUX clocks disabled, risking a bus hang.
--
--- Patch [14]: [PATCH v8 14/21] drm/bridge: synopsys: dw-dp: Add Runtime PM support ---
commit e3dd87d799174b12a4a2ad492ab236cf0e86bc18
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
Subject: drm/bridge: synopsys: dw-dp: Add Runtime PM support
This commit adds runtime PM stubs to the Synopsys DesignWare DisplayPort
bridge driver. It splits the early parts of dw_dp_probe into dw_dp_alloc
to allow platform drivers to assign it before probing.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index b21c2016dbd29..c24a5436e8fe8 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
[ ... ]
> @@ -2108,76 +2162,125 @@ static void dw_dp_phy_exit(void *data)
> phy_exit(dp->phy);
> }
>
> -struct dw_dp *dw_dp_probe(struct platform_device *pdev, const struct dw_dp_plat_data *plat_data)
> +static void dw_dp_manual_suspend(void *data)
> +{
> + struct dw_dp *dp = data;
> +
> + dw_dp_runtime_suspend(dp);
> +}
> +
> +static void dw_dp_irq_free(void *data)
> +{
> + struct dw_dp *dp = data;
> +
> + /* ignore error as the interrupt needs to be free'd in any case */
> + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dp->dev, pm);
> +
> + free_irq(dp->irq, dp);
> + dp->irq = -1;
> +}
[Severity: High]
Will this asynchronous autosuspend be cancelled by pm_runtime_disable() during
device removal?
When this devm teardown action runs, it releases the PM runtime reference
asynchronously via the PM_RUNTIME_ACQUIRE_AUTOSUSPEND scope guard. Immediately
afterward, the devm_pm_runtime_enable cleanup callback executes
pm_runtime_disable().
As documented in the PM subsystem rules, pm_runtime_disable() cancels pending
autosuspend work, meaning dw_dp_runtime_suspend() will never be called. Would
it be safer to use a synchronous suspend operation like pm_runtime_put_sync()
here to ensure the clocks do not remain permanently enabled?
[ ... ]
> + 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)) {
> + dw_dp_runtime_resume(dp);
> +
> + ret = devm_add_action_or_reset(dev, dw_dp_manual_suspend, dp);
> + if (ret)
> + return ret;
> + }
>
> ret = phy_init(dp->phy);
> if (ret) {
> dev_err_probe(dev, ret, "phy init failed\n");
> - return ERR_PTR(ret);
> + return ret;
> }
[Severity: High]
Could this phy_init() call trigger a bus hang if the device is still suspended?
Prior to this patch, the APB and AUX clocks were explicitly enabled before
phy_init() was called. Now, clock enablement is delegated to
dw_dp_runtime_resume().
If autosuspend_delay > 0, devm_pm_runtime_enable() is called but the device is
left in its default RPM_SUSPENDED state. Since phy_init() is called without
first waking the device via pm_runtime_get_sync(), the DP controller's APB
and AUX clocks are still disabled.
If the external PHY shares the APB bus or relies on the controller being
clocked to access its registers, will this unclocked access cause a
synchronous external abort?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=14
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 15/21] drm/rockchip: dw_dp: Add runtime PM support
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 17:35 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 17:35 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: conor+dt, dri-devel, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Asynchronous runtime PM put operations during device removal and error paths are cancelled by pm_runtime_disable(), leaving the device permanently powered on.
- [High] The device will remain powered on during system suspend because it uses runtime PM autosuspend without providing system sleep PM operations.
--
--- Patch [15]: [PATCH v8 15/21] drm/rockchip: dw_dp: Add runtime PM support ---
commit 7c3ff2ff43dcc0fce64eaf4461fe2a1101bcb4ad
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/rockchip: dw_dp: Add runtime PM support
Add support for runtime PM to the Rockchip RK3576/3588 Synopsys
DesignWare DisplayPort driver.
> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> index d516b3910b516..dde3881efad84 100644
> --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
[ ... ]
> @@ -231,16 +232,21 @@ static int dw_dp_rockchip_probe(struct platform_device *pdev)
> if (IS_ERR(dp->vo_grf))
> return PTR_ERR(dp->vo_grf);
>
> + plat_data->autosuspend_delay = 500;
[Severity: High]
Does setting autosuspend_delay here expose a resource leak in the core
driver's removal/error paths?
When autosuspend_delay > 0, the core dw-dp driver opts into the shared PM
runtime framework. During device unbind (dw_dp_unbind()) and devm IRQ
cleanup (dw_dp_irq_free()), the core driver releases its PM references
asynchronously using pm_runtime_put_autosuspend().
Immediately afterward, the removal process triggers the devres cleanup for
devm_pm_runtime_enable(), which calls pm_runtime_disable(). As documented
in the PM subsystem guidelines, pm_runtime_disable() invokes
__pm_runtime_barrier(), which flushes and cancels any pending asynchronous
suspend requests. This leaves the DP controller's clocks (apb_clk and
aux_clk) enabled indefinitely.
Should the core driver use synchronous puts (like pm_runtime_put_sync())
before pm_runtime_disable() is called?
> plat_data->max_link_rate = plat_data_const->max_link_rate;
> plat_data->pixel_mode = plat_data_const->pixel_mode;
> plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel;
[ ... ]
> @@ -249,6 +255,24 @@ static void dw_dp_rockchip_remove(struct platform_device *pdev)
> component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
> }
>
> +static int dw_dp_rockchip_runtime_suspend(struct device *dev)
> +{
> + struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
> +
> + return dw_dp_runtime_suspend(dp->base);
> +}
> +
> +static int dw_dp_rockchip_runtime_resume(struct device *dev)
> +{
> + struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
> +
> + return dw_dp_runtime_resume(dp->base);
> +}
> +
> +static const struct dev_pm_ops dw_dp_pm_ops = {
> + RUNTIME_PM_OPS(dw_dp_rockchip_runtime_suspend, dw_dp_rockchip_runtime_resume, NULL)
> +};
[Severity: High]
Will the device remain powered on during system suspend?
This dev_pm_ops struct defines runtime PM callbacks but omits system sleep
callbacks (like SYSTEM_SLEEP_PM_OPS with pm_runtime_force_suspend).
When the system suspends, the DRM core disables the display, which drops
the runtime PM reference and schedules the 500ms autosuspend timer via
pm_runtime_put_autosuspend().
If the PM core suspends the system before the 500ms timer expires, the
device is left in the RPM_ACTIVE state. Without system sleep callbacks to
force suspension, the DP controller's clocks could remain enabled while
the rest of the system sleeps.
> +
> static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
> .num_ctrls = 2,
> .ctrl_ids = {0xfde50000, 0xfde60000},
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=15
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 17/21] drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 18:09 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 18:09 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: robh, devicetree, dri-devel, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Critical] Missing bounds check and integer underflow in DP AUX read allows a malicious DP sink to overflow the kernel stack.
Pre-existing issues:
- [High] Infinite loop in `dw_dp_link_clock_recovery()` triggered by oscillating sink requests.
--
--- Patch [17]: [PATCH v8 17/21] drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot ---
commit 1a405fda22c71ea438c7181332eedd3948880f18
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot
This commit drops the useless reservation of the first SDP slot in the
atomic_enable code. It resolves a potential race condition with audio SDP
reservation once that feature is added.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 4d4af6a388b19..193816126e44e 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -1697,9 +1697,6 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
> return;
> }
>
> - scoped_guard(mutex, &dp->sdp_lock)
> - set_bit(0, dp->sdp_reg_bank);
> -
> ret = dw_dp_link_enable(dp);
> if (ret < 0) {
> dev_err(dp->dev, "failed to enable link: %d\n", ret);
[ ... ]
> static int dw_dp_link_clock_recovery(struct dw_dp *dp)
> {
[ ... ]
> for (;;) {
[ ... ]
> adj_changed = dw_dp_link_get_adjustments(link, status);
> if (!adj_changed)
> tries++;
> else
> tries = 0;
>
> if (tries == 5)
> break;
> }
>
> return 0;
> }
[Severity: High]
This is a pre-existing issue, but does this code allow an infinite loop if a
non-compliant DP sink continuously alternates its requested voltage or
pre-emphasis levels?
Since there is no hard iteration limit, if the sink changes requests without
achieving clock recovery, adj_changed remains true and resets tries to 0.
Could this trap the kernel thread executing the atomic commit in an infinite
loop, leading to a permanent hung task?
[ ... ]
> @@ -1513,7 +1513,7 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
> if (msg->request & DP_AUX_I2C_READ) {
> size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
>
> - if (count != msg->size)
> + if (!count)
> return -EBUSY;
>
> ret = dw_dp_aux_read_data(dp, msg->buffer, count);
[Severity: Critical]
Does this change remove the bounds check against msg->size, introducing an
integer underflow and a potential buffer overflow?
If the hardware reports 0 bytes read, count = 0 - 1 underflows to SIZE_MAX.
This would cause !count to evaluate to false and dw_dp_aux_read_data() to
execute an effectively infinite write loop.
Even without underflow, if the hardware reports a size larger than msg->size,
could it write past the end of msg->buffer?
Since msg->buffer is frequently allocated on the stack by the DRM core for
small reads, this might grant an external device the ability to corrupt
kernel stack memory.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=17
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 19/21] drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 18:29 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 18:29 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: conor+dt, devicetree, robh, dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] A mutex is acquired in dw_dp_send_sdp(), which is called from the bridge's atomic enable path, violating atomic context rules.
--
--- Patch [19]: [PATCH v8 19/21] drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp ---
commit fbf6588f46fe1b8bc7ef347644cccb5c7a752410
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp
This commit simplifies dw_dp_send_sdp() by making use of regmap_set_bits
instead of regmap_update_bits. No functional change is intended.
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -1070,14 +1070,12 @@ static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
[Severity: High]
Does this function sleep in an atomic context?
Looking at dw_dp_send_sdp(), it acquires a sleeping lock via
scoped_guard(mutex, &dp->sdp_lock) earlier in the function. However, this
is called from the bridge's atomic enable path:
drm_atomic_helper_commit_tail()
-> dw_dp_bridge_atomic_enable()
-> dw_dp_video_enable()
-> dw_dp_send_vsc_sdp()
-> dw_dp_send_sdp()
Can this sequence cause a kernel splat ("BUG: sleeping function called from
invalid context") or potential deadlocks when modesetting, since it executes
within drm_atomic_helper_commit_tail() where sleeping is forbidden?
> FIELD_PREP(SDP_REGS, get_unaligned_le32(payload)));
>
> if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL)
> - regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
> - EN_VERTICAL_SDP << nr,
> - EN_VERTICAL_SDP << nr);
> + regmap_set_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
> + EN_VERTICAL_SDP << nr);
>
> if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL)
> - regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
> - EN_HORIZONTAL_SDP << nr,
> - EN_HORIZONTAL_SDP << nr);
> + regmap_set_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
> + EN_HORIZONTAL_SDP << nr);
>
> return nr;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=19
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 20/21] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells
2026-07-31 14:42 ` Sebastian Reichel
(?)
@ 2026-07-31 18:37 ` sashiko-bot
-1 siblings, 0 replies; 57+ messages in thread
From: sashiko-bot @ 2026-07-31 18:37 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: devicetree, conor+dt, robh, dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Changing `#sound-dai-cells` to `const: 1` without updating in-tree DTS files in the same series breaks `dtbs_check` bisectability.
--
--- Patch [20]: [PATCH v8 20/21] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells ---
commit afb955faffe4407ec88a5c35c168c8dd82310361
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells
This commit updates the #sound-dai-cells property for the Rockchip dw-dp
controllers from 0 to 1 to allow selecting between I2S and S/PDIF interfaces.
It anticipates future Multi Stream Transport (MST) support where additional
numbers will identify both the interface and the stream.
> diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
> index 2b0d9e23e9432..c4f8959dd65da 100644
> --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
> +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
[ ... ]
> @@ -83,7 +83,8 @@ properties:
> maxItems: 1
>
> "#sound-dai-cells":
> - const: 0
> + const: 1
[Severity: Low]
Does this change break dtbs_check bisectability for RK3588 boards?
The commit message mentions that the mainline rk3588 DTS include sets this
to 0 and will be fixed up separately. If the in-tree DTS files (such as
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi) are not updated in the same
patch series, make dtbs_check will fail on those boards during a bisect
until the separate fix is applied.
> + description: 0 for I2S, 1 for S/PDIF
>
> required:
> - compatible
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-synopsys-dw-dp-improvements-v8-0-ac1e6a75782f@collabora.com?part=20
^ permalink raw reply [flat|nested] 57+ messages in thread
end of thread, other threads:[~2026-07-31 18:37 UTC | newest]
Thread overview: 57+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 14:42 [PATCH v8 00/21] Synopsys DisplayPort Controller improvements for Rockchip platforms Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 01/21] drm/bridge: synopsys: dw-dp: Register DP AUX on bridge attach Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 15:03 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 02/21] drm/bridge: synopsys: dw-dp: Fix incorrect resource lifetimes in bind callback Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 15:16 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 03/21] drm/bridge: synopsys: dw-dp: Cancel pending HPD work Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 15:30 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 04/21] drm/bridge: synopsys: dw-dp: Document missing reset line deassert Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 15:38 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 05/21] drm/bridge: synopsys: dw-dp: Add missing mutex cleanups on module removal Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 15:50 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 06/21] drm/bridge: synopsys: dw-dp: Fix AUX transfer timeout race condition Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 07/21] drm/bridge: synopsys: dw-dp: Fix support for short I2C reads Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 16:16 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 08/21] drm/bridge: synopsys: dw-dp: Free output_fmts when none are valid Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 16:30 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 09/21] drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 10/21] drm/bridge: synopsys: dw-dp: Add follow-up bridge support Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 11/21] drm/bridge: Add out-of-band HPD notify handler Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 12/21] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 13/21] drm/rockchip: dw_dp: Implement out-of-band HPD handling Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 17:10 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 14/21] drm/bridge: synopsys: dw-dp: Add Runtime PM support Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 17:23 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 15/21] drm/rockchip: dw_dp: Add runtime " Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 17:35 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 16/21] drm/bridge: synopsys: dw-dp: Protect sdp_reg_bank from concurrent access Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 17/21] drm/bridge: synopsys: dw-dp: Drop useless reservation of first slot Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 18:09 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 18/21] drm/bridge: synopsys: dw-dp: Clear only enabled SDPs on atomic disable Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 14:42 ` [PATCH v8 19/21] drm/bridge: synopsys: dw-dp: Use regmap_set_bits in dw_dp_send_sdp Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 18:29 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 20/21] dt-bindings: display: rockchip: dw-dp: Fix sound DAI cells Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
2026-07-31 18:37 ` sashiko-bot
2026-07-31 14:42 ` [PATCH v8 21/21] drm/bridge: synopsys: dw-dp: Add audio support Sebastian Reichel
2026-07-31 14:42 ` Sebastian Reichel
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.