* [PATCH v3 0/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
@ 2026-07-17 7:23 ` Chaoyi Chen
0 siblings, 0 replies; 28+ messages in thread
From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul,
Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
This series is split from the v15 "Add Type-C DP support for RK3399 EVB
IND board" series [1]. It focuses on the DRM bridge and Rockchip
platform CDN-DP controller changes.
[1] https://lore.kernel.org/all/20260304094152.92-1-kernel@airkyi.com/
====
1. Generic Type-C DP HPD bridge
Currently, several USB-C controller drivers register their own DP HPD
bridge via aux-hpd-bridge.c, each duplicating the same logic. For
devicetree based platforms, the USB-C controller may vary across boards,
and not every USB-C controller driver implements this feature. Patch 1
implements a generic DP HPD bridge that monitors Type-C bus events and
automatically creates an HPD bridge when a Type-C port device with DP
SVID is registered.
2. Multiple bridge model for CDN-DP
The RK3399 has two USB/DP combo PHY and one CDN-DP controller. Patch 5
introduces a multi-bridge model where each PHY port gets a separate
encoder and bridge, allowing flexible selection of the output PHY port.
This is based on the DRM AUX HPD bridge rather than extcon.
====
Patch 1 adds generic USB Type-C DP HPD bridge (Dmitry, Heikki).
Patch 2 adds new API drm_aux_bridge_register_from_node() (Neil).
Patch 3 adds DRM AUX bridge support for RK3399 USBDP PHY (Neil).
Patch 4 drops CDN-DP's extcon dependency when Type-C is present (Dmitry).
Patch 5 adds multiple bridges to support PHY port selection (Dmitry, Luca).
Changes in v3:
- Link to v2: https://lore.kernel.org/all/20260608070805.88-1-kernel@airkyi.com/
(Sorry, I forgot to add the "v2" title, so it looks like v1.)
- Remove now-redundant call to drm_connector_attach_encoder()
- Rebase to v7.2-rc2
Changes in v2:
- Link to v1: https://lore.kernel.org/all/20260521032854.103-1-kernel@airkyi.com/
- Add copyright text.
- Remove useless goto.
- Fix incorrect function names in the documentation comments.
- Fix the check logic for dp->active_port (0 is valid).
- Fix the uninitialized prev_port.
- Remove duplicate logic from cdn_dp_switch_port().
- Fix the prototype of cdn_dp_bridge_hpd_notify().
- Properly release connector->fwnode.
- Properly release next_bridge.
Chaoyi Chen (5):
drm/bridge: Implement generic USB Type-C DP HPD bridge
drm/bridge: aux: Add drm_aux_bridge_register_from_node()
phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
drm/rockchip: cdn-dp: Support handle lane info without extcon
drm/rockchip: cdn-dp: Add multiple bridges to support PHY port
selection
drivers/gpu/drm/bridge/Kconfig | 10 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/aux-bridge.c | 26 +-
.../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++
drivers/gpu/drm/rockchip/Kconfig | 1 +
drivers/gpu/drm/rockchip/cdn-dp-core.c | 358 ++++++++++++++----
drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +-
drivers/phy/rockchip/Kconfig | 2 +
drivers/phy/rockchip/phy-rockchip-typec.c | 13 +-
include/drm/bridge/aux-bridge.h | 6 +
10 files changed, 418 insertions(+), 71 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
--
2.53.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v3 0/5] drm/bridge: Implement generic USB Type-C DP HPD bridge @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> This series is split from the v15 "Add Type-C DP support for RK3399 EVB IND board" series [1]. It focuses on the DRM bridge and Rockchip platform CDN-DP controller changes. [1] https://lore.kernel.org/all/20260304094152.92-1-kernel@airkyi.com/ ==== 1. Generic Type-C DP HPD bridge Currently, several USB-C controller drivers register their own DP HPD bridge via aux-hpd-bridge.c, each duplicating the same logic. For devicetree based platforms, the USB-C controller may vary across boards, and not every USB-C controller driver implements this feature. Patch 1 implements a generic DP HPD bridge that monitors Type-C bus events and automatically creates an HPD bridge when a Type-C port device with DP SVID is registered. 2. Multiple bridge model for CDN-DP The RK3399 has two USB/DP combo PHY and one CDN-DP controller. Patch 5 introduces a multi-bridge model where each PHY port gets a separate encoder and bridge, allowing flexible selection of the output PHY port. This is based on the DRM AUX HPD bridge rather than extcon. ==== Patch 1 adds generic USB Type-C DP HPD bridge (Dmitry, Heikki). Patch 2 adds new API drm_aux_bridge_register_from_node() (Neil). Patch 3 adds DRM AUX bridge support for RK3399 USBDP PHY (Neil). Patch 4 drops CDN-DP's extcon dependency when Type-C is present (Dmitry). Patch 5 adds multiple bridges to support PHY port selection (Dmitry, Luca). Changes in v3: - Link to v2: https://lore.kernel.org/all/20260608070805.88-1-kernel@airkyi.com/ (Sorry, I forgot to add the "v2" title, so it looks like v1.) - Remove now-redundant call to drm_connector_attach_encoder() - Rebase to v7.2-rc2 Changes in v2: - Link to v1: https://lore.kernel.org/all/20260521032854.103-1-kernel@airkyi.com/ - Add copyright text. - Remove useless goto. - Fix incorrect function names in the documentation comments. - Fix the check logic for dp->active_port (0 is valid). - Fix the uninitialized prev_port. - Remove duplicate logic from cdn_dp_switch_port(). - Fix the prototype of cdn_dp_bridge_hpd_notify(). - Properly release connector->fwnode. - Properly release next_bridge. Chaoyi Chen (5): drm/bridge: Implement generic USB Type-C DP HPD bridge drm/bridge: aux: Add drm_aux_bridge_register_from_node() phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge drm/rockchip: cdn-dp: Support handle lane info without extcon drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection drivers/gpu/drm/bridge/Kconfig | 10 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/aux-bridge.c | 26 +- .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++ drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/rockchip/cdn-dp-core.c | 358 ++++++++++++++---- drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +- drivers/phy/rockchip/Kconfig | 2 + drivers/phy/rockchip/phy-rockchip-typec.c | 13 +- include/drm/bridge/aux-bridge.h | 6 + 10 files changed, 418 insertions(+), 71 deletions(-) create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c -- 2.53.0 ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3 0/5] drm/bridge: Implement generic USB Type-C DP HPD bridge @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> This series is split from the v15 "Add Type-C DP support for RK3399 EVB IND board" series [1]. It focuses on the DRM bridge and Rockchip platform CDN-DP controller changes. [1] https://lore.kernel.org/all/20260304094152.92-1-kernel@airkyi.com/ ==== 1. Generic Type-C DP HPD bridge Currently, several USB-C controller drivers register their own DP HPD bridge via aux-hpd-bridge.c, each duplicating the same logic. For devicetree based platforms, the USB-C controller may vary across boards, and not every USB-C controller driver implements this feature. Patch 1 implements a generic DP HPD bridge that monitors Type-C bus events and automatically creates an HPD bridge when a Type-C port device with DP SVID is registered. 2. Multiple bridge model for CDN-DP The RK3399 has two USB/DP combo PHY and one CDN-DP controller. Patch 5 introduces a multi-bridge model where each PHY port gets a separate encoder and bridge, allowing flexible selection of the output PHY port. This is based on the DRM AUX HPD bridge rather than extcon. ==== Patch 1 adds generic USB Type-C DP HPD bridge (Dmitry, Heikki). Patch 2 adds new API drm_aux_bridge_register_from_node() (Neil). Patch 3 adds DRM AUX bridge support for RK3399 USBDP PHY (Neil). Patch 4 drops CDN-DP's extcon dependency when Type-C is present (Dmitry). Patch 5 adds multiple bridges to support PHY port selection (Dmitry, Luca). Changes in v3: - Link to v2: https://lore.kernel.org/all/20260608070805.88-1-kernel@airkyi.com/ (Sorry, I forgot to add the "v2" title, so it looks like v1.) - Remove now-redundant call to drm_connector_attach_encoder() - Rebase to v7.2-rc2 Changes in v2: - Link to v1: https://lore.kernel.org/all/20260521032854.103-1-kernel@airkyi.com/ - Add copyright text. - Remove useless goto. - Fix incorrect function names in the documentation comments. - Fix the check logic for dp->active_port (0 is valid). - Fix the uninitialized prev_port. - Remove duplicate logic from cdn_dp_switch_port(). - Fix the prototype of cdn_dp_bridge_hpd_notify(). - Properly release connector->fwnode. - Properly release next_bridge. Chaoyi Chen (5): drm/bridge: Implement generic USB Type-C DP HPD bridge drm/bridge: aux: Add drm_aux_bridge_register_from_node() phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge drm/rockchip: cdn-dp: Support handle lane info without extcon drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection drivers/gpu/drm/bridge/Kconfig | 10 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/aux-bridge.c | 26 +- .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++ drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/rockchip/cdn-dp-core.c | 358 ++++++++++++++---- drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +- drivers/phy/rockchip/Kconfig | 2 + drivers/phy/rockchip/phy-rockchip-typec.c | 13 +- include/drm/bridge/aux-bridge.h | 6 + 10 files changed, 418 insertions(+), 71 deletions(-) create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c -- 2.53.0 _______________________________________________ Linux-rockchip mailing list Linux-rockchip@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-rockchip ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge 2026-07-17 7:23 ` Chaoyi Chen (?) @ 2026-07-17 7:23 ` Chaoyi Chen -1 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The HPD function of Type-C DP is implemented through drm_connector_oob_hotplug_event(). For embedded DP, it is required that the DRM connector fwnode corresponds to the Type-C port fwnode. To describe the relationship between the DP controller and the Type-C port device, we usually using drm_bridge to build a bridge chain. Now several USB-C controller drivers have already implemented the DP HPD bridge function provided by aux-hpd-bridge.c, it will build a DP HPD bridge on USB-C connector port device. But this requires the USB-C controller driver to manually register the HPD bridge. If the driver does not implement this feature, the bridge will not be create. So this patch implements a generic DP HPD bridge based on aux-hpd-bridge.c. It will monitor Type-C bus events, and when a Type-C port device containing the DP svid is registered, it will create an HPD bridge for it without the need for the USB-C controller driver to implement it. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com> --- (no changes since v3) Changes in v2: - Add copyright text. - Remove useless goto. --- drivers/gpu/drm/bridge/Kconfig | 10 ++++ drivers/gpu/drm/bridge/Makefile | 1 + .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 4a57d49b4c6d..9739b2a19758 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE Simple bridge that terminates the bridge chain and provides HPD support. +if DRM_AUX_HPD_BRIDGE +config DRM_AUX_HPD_TYPEC_BRIDGE + tristate + depends on TYPEC || !TYPEC + default TYPEC + help + Simple bridge that terminates the bridge chain and provides HPD + support. It build bridge on each USB-C connector device node. +endif + menu "Display Interface Bridges" depends on DRM && DRM_BRIDGE diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 15cc821d85b7..d88a9e1ccc9a 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o +obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c new file mode 100644 index 000000000000..c09579ff7ab9 --- /dev/null +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> + */ +#include <linux/of.h> +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_dp.h> + +#include <drm/bridge/aux-bridge.h> + +static int drm_typec_bus_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = (struct device *)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (action != BUS_NOTIFY_ADD_DEVICE) + return NOTIFY_OK; + + /* + * alt->dev.parent->parent : USB-C controller device + * alt->dev.parent : USB-C connector device + */ + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID) + drm_dp_hpd_bridge_register(alt->dev.parent->parent, + to_of_node(alt->dev.parent->fwnode)); + + return NOTIFY_OK; +} + +static struct notifier_block drm_typec_event_nb = { + .notifier_call = drm_typec_bus_event, +}; + +static void drm_aux_hpd_typec_dp_bridge_module_exit(void) +{ + bus_unregister_notifier(&typec_bus, &drm_typec_event_nb); +} + +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void) +{ + bus_register_notifier(&typec_bus, &drm_typec_event_nb); + + return 0; +} + +module_init(drm_aux_hpd_typec_dp_bridge_module_init); +module_exit(drm_aux_hpd_typec_dp_bridge_module_exit); + +MODULE_AUTHOR("Chaoyi Chen <chaoyi.chen@rock-chips.com>"); +MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE"); +MODULE_LICENSE("GPL"); -- 2.53.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The HPD function of Type-C DP is implemented through drm_connector_oob_hotplug_event(). For embedded DP, it is required that the DRM connector fwnode corresponds to the Type-C port fwnode. To describe the relationship between the DP controller and the Type-C port device, we usually using drm_bridge to build a bridge chain. Now several USB-C controller drivers have already implemented the DP HPD bridge function provided by aux-hpd-bridge.c, it will build a DP HPD bridge on USB-C connector port device. But this requires the USB-C controller driver to manually register the HPD bridge. If the driver does not implement this feature, the bridge will not be create. So this patch implements a generic DP HPD bridge based on aux-hpd-bridge.c. It will monitor Type-C bus events, and when a Type-C port device containing the DP svid is registered, it will create an HPD bridge for it without the need for the USB-C controller driver to implement it. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com> --- (no changes since v3) Changes in v2: - Add copyright text. - Remove useless goto. --- drivers/gpu/drm/bridge/Kconfig | 10 ++++ drivers/gpu/drm/bridge/Makefile | 1 + .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 4a57d49b4c6d..9739b2a19758 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE Simple bridge that terminates the bridge chain and provides HPD support. +if DRM_AUX_HPD_BRIDGE +config DRM_AUX_HPD_TYPEC_BRIDGE + tristate + depends on TYPEC || !TYPEC + default TYPEC + help + Simple bridge that terminates the bridge chain and provides HPD + support. It build bridge on each USB-C connector device node. +endif + menu "Display Interface Bridges" depends on DRM && DRM_BRIDGE diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 15cc821d85b7..d88a9e1ccc9a 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o +obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c new file mode 100644 index 000000000000..c09579ff7ab9 --- /dev/null +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> + */ +#include <linux/of.h> +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_dp.h> + +#include <drm/bridge/aux-bridge.h> + +static int drm_typec_bus_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = (struct device *)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (action != BUS_NOTIFY_ADD_DEVICE) + return NOTIFY_OK; + + /* + * alt->dev.parent->parent : USB-C controller device + * alt->dev.parent : USB-C connector device + */ + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID) + drm_dp_hpd_bridge_register(alt->dev.parent->parent, + to_of_node(alt->dev.parent->fwnode)); + + return NOTIFY_OK; +} + +static struct notifier_block drm_typec_event_nb = { + .notifier_call = drm_typec_bus_event, +}; + +static void drm_aux_hpd_typec_dp_bridge_module_exit(void) +{ + bus_unregister_notifier(&typec_bus, &drm_typec_event_nb); +} + +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void) +{ + bus_register_notifier(&typec_bus, &drm_typec_event_nb); + + return 0; +} + +module_init(drm_aux_hpd_typec_dp_bridge_module_init); +module_exit(drm_aux_hpd_typec_dp_bridge_module_exit); + +MODULE_AUTHOR("Chaoyi Chen <chaoyi.chen@rock-chips.com>"); +MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE"); +MODULE_LICENSE("GPL"); -- 2.53.0 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The HPD function of Type-C DP is implemented through drm_connector_oob_hotplug_event(). For embedded DP, it is required that the DRM connector fwnode corresponds to the Type-C port fwnode. To describe the relationship between the DP controller and the Type-C port device, we usually using drm_bridge to build a bridge chain. Now several USB-C controller drivers have already implemented the DP HPD bridge function provided by aux-hpd-bridge.c, it will build a DP HPD bridge on USB-C connector port device. But this requires the USB-C controller driver to manually register the HPD bridge. If the driver does not implement this feature, the bridge will not be create. So this patch implements a generic DP HPD bridge based on aux-hpd-bridge.c. It will monitor Type-C bus events, and when a Type-C port device containing the DP svid is registered, it will create an HPD bridge for it without the need for the USB-C controller driver to implement it. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com> --- (no changes since v3) Changes in v2: - Add copyright text. - Remove useless goto. --- drivers/gpu/drm/bridge/Kconfig | 10 ++++ drivers/gpu/drm/bridge/Makefile | 1 + .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 54 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 4a57d49b4c6d..9739b2a19758 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE Simple bridge that terminates the bridge chain and provides HPD support. +if DRM_AUX_HPD_BRIDGE +config DRM_AUX_HPD_TYPEC_BRIDGE + tristate + depends on TYPEC || !TYPEC + default TYPEC + help + Simple bridge that terminates the bridge chain and provides HPD + support. It build bridge on each USB-C connector device node. +endif + menu "Display Interface Bridges" depends on DRM && DRM_BRIDGE diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 15cc821d85b7..d88a9e1ccc9a 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o +obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c new file mode 100644 index 000000000000..c09579ff7ab9 --- /dev/null +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> + */ +#include <linux/of.h> +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_dp.h> + +#include <drm/bridge/aux-bridge.h> + +static int drm_typec_bus_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = (struct device *)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (action != BUS_NOTIFY_ADD_DEVICE) + return NOTIFY_OK; + + /* + * alt->dev.parent->parent : USB-C controller device + * alt->dev.parent : USB-C connector device + */ + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID) + drm_dp_hpd_bridge_register(alt->dev.parent->parent, + to_of_node(alt->dev.parent->fwnode)); + + return NOTIFY_OK; +} + +static struct notifier_block drm_typec_event_nb = { + .notifier_call = drm_typec_bus_event, +}; + +static void drm_aux_hpd_typec_dp_bridge_module_exit(void) +{ + bus_unregister_notifier(&typec_bus, &drm_typec_event_nb); +} + +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void) +{ + bus_register_notifier(&typec_bus, &drm_typec_event_nb); + + return 0; +} + +module_init(drm_aux_hpd_typec_dp_bridge_module_init); +module_exit(drm_aux_hpd_typec_dp_bridge_module_exit); + +MODULE_AUTHOR("Chaoyi Chen <chaoyi.chen@rock-chips.com>"); +MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE"); +MODULE_LICENSE("GPL"); -- 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] 28+ messages in thread
* Re: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge 2026-07-17 7:23 ` Chaoyi Chen @ 2026-07-17 7:36 ` sashiko-bot -1 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:36 UTC (permalink / raw) To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, olteanv, dri-devel, linux-phy Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The module registers a bus notifier to catch device additions but fails to iterate over already-registered devices, ignoring any Type-C ports present before the module was loaded. - [High] The module attaches devm-managed bridges to a foreign USB-C controller device but fails to clean them up on module unload or device removal, leading to resource leaks and an execute-after-free kernel panic. - [Medium] The return value of `bus_register_notifier()` is ignored. -- --- Patch [1]: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge --- commit 5c3c19cbb4491c572fdd386c797dfba9412af2b3 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/bridge: Implement generic USB Type-C DP HPD bridge This patch implements a generic DP HPD bridge based on aux-hpd-bridge.c. It monitors Type-C bus events and automatically creates an HPD bridge when a Type-C port device containing the DP svid is registered. > diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c > new file mode 100644 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c > @@ -0,0 +1,54 @@ [ ... ] > +static int drm_typec_bus_event(struct notifier_block *nb, > + unsigned long action, void *data) > +{ > + struct device *dev = (struct device *)data; > + struct typec_altmode *alt = to_typec_altmode(dev); > + > + if (action != BUS_NOTIFY_ADD_DEVICE) > + return NOTIFY_OK; > + > + /* > + * alt->dev.parent->parent : USB-C controller device > + * alt->dev.parent : USB-C connector device > + */ > + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID) > + drm_dp_hpd_bridge_register(alt->dev.parent->parent, > + to_of_node(alt->dev.parent->fwnode)); [Severity: High] Will this leak devm resources on the controller device when a port is removed and re-added, since BUS_NOTIFY_DEL_DEVICE is ignored? Also, because the devm cleanup callbacks are tied to the controller device rather than this module, could this cause an execute-after-free panic? If this module and aux-hpd-bridge are unloaded, the devres callbacks on the controller will point to unmapped memory when the controller is later unbound. [ ... ] > +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void) > +{ > + bus_register_notifier(&typec_bus, &drm_typec_event_nb); [Severity: Medium] Is it safe to ignore the return value of bus_register_notifier() here? If the notifier registration fails, the module initialization will succeed but remain non-functional. [Severity: High] Does this code miss existing Type-C DP ports that were probed before this module was loaded? bus_register_notifier() only triggers notifications for devices added after registration. Since there is no bus_for_each_dev() iteration over existing devices on the typec_bus after registering the notifier, pre-existing ports might never get an HPD bridge created. > + > + return 0; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=1 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge @ 2026-07-17 7:36 ` sashiko-bot 0 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:36 UTC (permalink / raw) To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, olteanv, dri-devel, linux-phy Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The module registers a bus notifier to catch device additions but fails to iterate over already-registered devices, ignoring any Type-C ports present before the module was loaded. - [High] The module attaches devm-managed bridges to a foreign USB-C controller device but fails to clean them up on module unload or device removal, leading to resource leaks and an execute-after-free kernel panic. - [Medium] The return value of `bus_register_notifier()` is ignored. -- --- Patch [1]: [PATCH v3 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge --- commit 5c3c19cbb4491c572fdd386c797dfba9412af2b3 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/bridge: Implement generic USB Type-C DP HPD bridge This patch implements a generic DP HPD bridge based on aux-hpd-bridge.c. It monitors Type-C bus events and automatically creates an HPD bridge when a Type-C port device containing the DP svid is registered. > diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c > new file mode 100644 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c > @@ -0,0 +1,54 @@ [ ... ] > +static int drm_typec_bus_event(struct notifier_block *nb, > + unsigned long action, void *data) > +{ > + struct device *dev = (struct device *)data; > + struct typec_altmode *alt = to_typec_altmode(dev); > + > + if (action != BUS_NOTIFY_ADD_DEVICE) > + return NOTIFY_OK; > + > + /* > + * alt->dev.parent->parent : USB-C controller device > + * alt->dev.parent : USB-C connector device > + */ > + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID) > + drm_dp_hpd_bridge_register(alt->dev.parent->parent, > + to_of_node(alt->dev.parent->fwnode)); [Severity: High] Will this leak devm resources on the controller device when a port is removed and re-added, since BUS_NOTIFY_DEL_DEVICE is ignored? Also, because the devm cleanup callbacks are tied to the controller device rather than this module, could this cause an execute-after-free panic? If this module and aux-hpd-bridge are unloaded, the devres callbacks on the controller will point to unmapped memory when the controller is later unbound. [ ... ] > +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void) > +{ > + bus_register_notifier(&typec_bus, &drm_typec_event_nb); [Severity: Medium] Is it safe to ignore the return value of bus_register_notifier() here? If the notifier registration fails, the module initialization will succeed but remain non-functional. [Severity: High] Does this code miss existing Type-C DP ports that were probed before this module was loaded? bus_register_notifier() only triggers notifications for devices added after registration. Since there is no bus_for_each_dev() iteration over existing devices on the typec_bus after registering the notifier, pre-existing ports might never get an HPD bridge created. > + > + return 0; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=1 ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() 2026-07-17 7:23 ` Chaoyi Chen (?) @ 2026-07-17 7:23 ` Chaoyi Chen -1 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The drm_aux_bridge_register() uses the device->of_node as the bridge->of_node. This patch adds drm_aux_bridge_register_from_node() to allow specifying the of_node corresponding to the bridge. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- (no changes since v3) Changes in v2: - Fix incorrect function names in the documentation comments. --- drivers/gpu/drm/bridge/aux-bridge.c | 26 +++++++++++++++++++++++--- include/drm/bridge/aux-bridge.h | 6 ++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c index 1ed21a8713bf..06a1466f49bd 100644 --- a/drivers/gpu/drm/bridge/aux-bridge.c +++ b/drivers/gpu/drm/bridge/aux-bridge.c @@ -33,8 +33,9 @@ static void drm_aux_bridge_unregister_adev(void *_adev) } /** - * drm_aux_bridge_register - Create a simple bridge device to link the chain + * drm_aux_bridge_register_from_node - Create a simple bridge device to link the chain * @parent: device instance providing this bridge + * @np: device node pointer corresponding to this bridge instance * * Creates a simple DRM bridge that doesn't implement any drm_bridge * operations. Such bridges merely fill a place in the bridge chain linking @@ -42,7 +43,7 @@ static void drm_aux_bridge_unregister_adev(void *_adev) * * Return: zero on success, negative error code on failure */ -int drm_aux_bridge_register(struct device *parent) +int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np) { struct auxiliary_device *adev; int ret; @@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent) adev->dev.parent = parent; adev->dev.release = drm_aux_bridge_release; - device_set_of_node_from_dev(&adev->dev, parent); + if (np) + device_set_node(&adev->dev, of_fwnode_handle(np)); + else + device_set_of_node_from_dev(&adev->dev, parent); ret = auxiliary_device_init(adev); if (ret) { @@ -80,6 +84,22 @@ int drm_aux_bridge_register(struct device *parent) return devm_add_action_or_reset(parent, drm_aux_bridge_unregister_adev, adev); } +EXPORT_SYMBOL_GPL(drm_aux_bridge_register_from_node); + +/** + * drm_aux_bridge_register - Create a simple bridge device to link the chain + * @parent: device instance providing this bridge + * + * Creates a simple DRM bridge that doesn't implement any drm_bridge + * operations. Such bridges merely fill a place in the bridge chain linking + * surrounding DRM bridges. + * + * Return: zero on success, negative error code on failure + */ +int drm_aux_bridge_register(struct device *parent) +{ + return drm_aux_bridge_register_from_node(parent, NULL); +} EXPORT_SYMBOL_GPL(drm_aux_bridge_register); struct drm_aux_bridge_data { diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h index c2f5a855512f..7dd1f17a1354 100644 --- a/include/drm/bridge/aux-bridge.h +++ b/include/drm/bridge/aux-bridge.h @@ -13,11 +13,17 @@ struct auxiliary_device; #if IS_ENABLED(CONFIG_DRM_AUX_BRIDGE) int drm_aux_bridge_register(struct device *parent); +int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np); #else static inline int drm_aux_bridge_register(struct device *parent) { return 0; } + +static inline int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np) +{ + return 0; +} #endif #if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE) -- 2.53.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The drm_aux_bridge_register() uses the device->of_node as the bridge->of_node. This patch adds drm_aux_bridge_register_from_node() to allow specifying the of_node corresponding to the bridge. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- (no changes since v3) Changes in v2: - Fix incorrect function names in the documentation comments. --- drivers/gpu/drm/bridge/aux-bridge.c | 26 +++++++++++++++++++++++--- include/drm/bridge/aux-bridge.h | 6 ++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c index 1ed21a8713bf..06a1466f49bd 100644 --- a/drivers/gpu/drm/bridge/aux-bridge.c +++ b/drivers/gpu/drm/bridge/aux-bridge.c @@ -33,8 +33,9 @@ static void drm_aux_bridge_unregister_adev(void *_adev) } /** - * drm_aux_bridge_register - Create a simple bridge device to link the chain + * drm_aux_bridge_register_from_node - Create a simple bridge device to link the chain * @parent: device instance providing this bridge + * @np: device node pointer corresponding to this bridge instance * * Creates a simple DRM bridge that doesn't implement any drm_bridge * operations. Such bridges merely fill a place in the bridge chain linking @@ -42,7 +43,7 @@ static void drm_aux_bridge_unregister_adev(void *_adev) * * Return: zero on success, negative error code on failure */ -int drm_aux_bridge_register(struct device *parent) +int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np) { struct auxiliary_device *adev; int ret; @@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent) adev->dev.parent = parent; adev->dev.release = drm_aux_bridge_release; - device_set_of_node_from_dev(&adev->dev, parent); + if (np) + device_set_node(&adev->dev, of_fwnode_handle(np)); + else + device_set_of_node_from_dev(&adev->dev, parent); ret = auxiliary_device_init(adev); if (ret) { @@ -80,6 +84,22 @@ int drm_aux_bridge_register(struct device *parent) return devm_add_action_or_reset(parent, drm_aux_bridge_unregister_adev, adev); } +EXPORT_SYMBOL_GPL(drm_aux_bridge_register_from_node); + +/** + * drm_aux_bridge_register - Create a simple bridge device to link the chain + * @parent: device instance providing this bridge + * + * Creates a simple DRM bridge that doesn't implement any drm_bridge + * operations. Such bridges merely fill a place in the bridge chain linking + * surrounding DRM bridges. + * + * Return: zero on success, negative error code on failure + */ +int drm_aux_bridge_register(struct device *parent) +{ + return drm_aux_bridge_register_from_node(parent, NULL); +} EXPORT_SYMBOL_GPL(drm_aux_bridge_register); struct drm_aux_bridge_data { diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h index c2f5a855512f..7dd1f17a1354 100644 --- a/include/drm/bridge/aux-bridge.h +++ b/include/drm/bridge/aux-bridge.h @@ -13,11 +13,17 @@ struct auxiliary_device; #if IS_ENABLED(CONFIG_DRM_AUX_BRIDGE) int drm_aux_bridge_register(struct device *parent); +int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np); #else static inline int drm_aux_bridge_register(struct device *parent) { return 0; } + +static inline int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np) +{ + return 0; +} #endif #if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE) -- 2.53.0 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The drm_aux_bridge_register() uses the device->of_node as the bridge->of_node. This patch adds drm_aux_bridge_register_from_node() to allow specifying the of_node corresponding to the bridge. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- (no changes since v3) Changes in v2: - Fix incorrect function names in the documentation comments. --- drivers/gpu/drm/bridge/aux-bridge.c | 26 +++++++++++++++++++++++--- include/drm/bridge/aux-bridge.h | 6 ++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c index 1ed21a8713bf..06a1466f49bd 100644 --- a/drivers/gpu/drm/bridge/aux-bridge.c +++ b/drivers/gpu/drm/bridge/aux-bridge.c @@ -33,8 +33,9 @@ static void drm_aux_bridge_unregister_adev(void *_adev) } /** - * drm_aux_bridge_register - Create a simple bridge device to link the chain + * drm_aux_bridge_register_from_node - Create a simple bridge device to link the chain * @parent: device instance providing this bridge + * @np: device node pointer corresponding to this bridge instance * * Creates a simple DRM bridge that doesn't implement any drm_bridge * operations. Such bridges merely fill a place in the bridge chain linking @@ -42,7 +43,7 @@ static void drm_aux_bridge_unregister_adev(void *_adev) * * Return: zero on success, negative error code on failure */ -int drm_aux_bridge_register(struct device *parent) +int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np) { struct auxiliary_device *adev; int ret; @@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent) adev->dev.parent = parent; adev->dev.release = drm_aux_bridge_release; - device_set_of_node_from_dev(&adev->dev, parent); + if (np) + device_set_node(&adev->dev, of_fwnode_handle(np)); + else + device_set_of_node_from_dev(&adev->dev, parent); ret = auxiliary_device_init(adev); if (ret) { @@ -80,6 +84,22 @@ int drm_aux_bridge_register(struct device *parent) return devm_add_action_or_reset(parent, drm_aux_bridge_unregister_adev, adev); } +EXPORT_SYMBOL_GPL(drm_aux_bridge_register_from_node); + +/** + * drm_aux_bridge_register - Create a simple bridge device to link the chain + * @parent: device instance providing this bridge + * + * Creates a simple DRM bridge that doesn't implement any drm_bridge + * operations. Such bridges merely fill a place in the bridge chain linking + * surrounding DRM bridges. + * + * Return: zero on success, negative error code on failure + */ +int drm_aux_bridge_register(struct device *parent) +{ + return drm_aux_bridge_register_from_node(parent, NULL); +} EXPORT_SYMBOL_GPL(drm_aux_bridge_register); struct drm_aux_bridge_data { diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h index c2f5a855512f..7dd1f17a1354 100644 --- a/include/drm/bridge/aux-bridge.h +++ b/include/drm/bridge/aux-bridge.h @@ -13,11 +13,17 @@ struct auxiliary_device; #if IS_ENABLED(CONFIG_DRM_AUX_BRIDGE) int drm_aux_bridge_register(struct device *parent); +int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np); #else static inline int drm_aux_bridge_register(struct device *parent) { return 0; } + +static inline int drm_aux_bridge_register_from_node(struct device *parent, struct device_node *np) +{ + return 0; +} #endif #if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE) -- 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] 28+ messages in thread
* Re: [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() 2026-07-17 7:23 ` Chaoyi Chen @ 2026-07-17 7:33 ` sashiko-bot -1 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:33 UTC (permalink / raw) To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, dri-devel, linux-phy, olteanv Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Unbalanced `of_node_put()` in `drm_aux_bridge_register_from_node()` and `drm_aux_bridge_release()` causes a reference count underflow for the provided `device_node`. -- --- Patch [2]: [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() --- commit b2b66972200bdc4b6541ca41f8ae44c08d81a964 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/bridge: aux: Add drm_aux_bridge_register_from_node() The drm_aux_bridge_register() uses the device->of_node as the bridge->of_node. This patch adds drm_aux_bridge_register_from_node() to allow specifying the of_node corresponding to the bridge. > diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c > index 1ed21a8713bfd..06a1466f49bd2 100644 > --- a/drivers/gpu/drm/bridge/aux-bridge.c > +++ b/drivers/gpu/drm/bridge/aux-bridge.c [ ... ] > @@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent) > adev->dev.parent = parent; > adev->dev.release = drm_aux_bridge_release; > > - device_set_of_node_from_dev(&adev->dev, parent); > + if (np) > + device_set_node(&adev->dev, of_fwnode_handle(np)); [Severity: High] Does this cause a reference count underflow for the device node? Unlike device_set_of_node_from_dev() which safely acquires a reference to the of_node, device_set_node() just assigns the pointer without calling of_node_get(). If auxiliary_device_init() fails, the error path below unconditionally calls of_node_put(adev->dev.of_node). Similarly, the pre-existing drm_aux_bridge_release() callback drops a reference when the device is destroyed. Could this cause of_node_put() to drop a reference that was never acquired? > + else > + device_set_of_node_from_dev(&adev->dev, parent); > > ret = auxiliary_device_init(adev); > if (ret) { -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=2 ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() @ 2026-07-17 7:33 ` sashiko-bot 0 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:33 UTC (permalink / raw) To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, dri-devel, linux-phy, olteanv Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Unbalanced `of_node_put()` in `drm_aux_bridge_register_from_node()` and `drm_aux_bridge_release()` causes a reference count underflow for the provided `device_node`. -- --- Patch [2]: [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() --- commit b2b66972200bdc4b6541ca41f8ae44c08d81a964 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/bridge: aux: Add drm_aux_bridge_register_from_node() The drm_aux_bridge_register() uses the device->of_node as the bridge->of_node. This patch adds drm_aux_bridge_register_from_node() to allow specifying the of_node corresponding to the bridge. > diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c > index 1ed21a8713bfd..06a1466f49bd2 100644 > --- a/drivers/gpu/drm/bridge/aux-bridge.c > +++ b/drivers/gpu/drm/bridge/aux-bridge.c [ ... ] > @@ -62,7 +63,10 @@ int drm_aux_bridge_register(struct device *parent) > adev->dev.parent = parent; > adev->dev.release = drm_aux_bridge_release; > > - device_set_of_node_from_dev(&adev->dev, parent); > + if (np) > + device_set_node(&adev->dev, of_fwnode_handle(np)); [Severity: High] Does this cause a reference count underflow for the device node? Unlike device_set_of_node_from_dev() which safely acquires a reference to the of_node, device_set_node() just assigns the pointer without calling of_node_get(). If auxiliary_device_init() fails, the error path below unconditionally calls of_node_put(adev->dev.of_node). Similarly, the pre-existing drm_aux_bridge_release() callback drops a reference when the device is destroyed. Could this cause of_node_put() to drop a reference that was never acquired? > + else > + device_set_of_node_from_dev(&adev->dev, parent); > > ret = auxiliary_device_init(adev); > if (ret) { -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=2 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge 2026-07-17 7:23 ` Chaoyi Chen (?) @ 2026-07-17 7:23 ` Chaoyi Chen -1 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge device. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- (no changes since v1) --- drivers/phy/rockchip/Kconfig | 2 ++ drivers/phy/rockchip/phy-rockchip-typec.c | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b607..9173d3b4fef4 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -119,6 +119,8 @@ config PHY_ROCKCHIP_SNPS_PCIE3 config PHY_ROCKCHIP_TYPEC tristate "Rockchip TYPEC PHY Driver" depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE select EXTCON select GENERIC_PHY select RESET_CONTROLLER diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index d9701b6106d5..48070b50416e 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -54,6 +54,7 @@ #include <linux/mfd/syscon.h> #include <linux/phy/phy.h> +#include <drm/bridge/aux-bridge.h> #define CMN_SSM_BANDGAP (0x21 << 2) #define CMN_SSM_BIAS (0x22 << 2) @@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) for_each_available_child_of_node(np, child_np) { struct phy *phy; + ret = 0; - if (of_node_name_eq(child_np, "dp-port")) + if (of_node_name_eq(child_np, "dp-port")) { phy = devm_phy_create(dev, child_np, &rockchip_dp_phy_ops); - else if (of_node_name_eq(child_np, "usb3-port")) + ret = drm_aux_bridge_register_from_node(dev, child_np); + } else if (of_node_name_eq(child_np, "usb3-port")) phy = devm_phy_create(dev, child_np, &rockchip_usb3_phy_ops); else continue; + if (ret) { + pm_runtime_disable(dev); + of_node_put(child_np); + return ret; + } + if (IS_ERR(phy)) { dev_err(dev, "failed to create phy: %pOFn\n", child_np); -- 2.53.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge device. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- (no changes since v1) --- drivers/phy/rockchip/Kconfig | 2 ++ drivers/phy/rockchip/phy-rockchip-typec.c | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b607..9173d3b4fef4 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -119,6 +119,8 @@ config PHY_ROCKCHIP_SNPS_PCIE3 config PHY_ROCKCHIP_TYPEC tristate "Rockchip TYPEC PHY Driver" depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE select EXTCON select GENERIC_PHY select RESET_CONTROLLER diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index d9701b6106d5..48070b50416e 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -54,6 +54,7 @@ #include <linux/mfd/syscon.h> #include <linux/phy/phy.h> +#include <drm/bridge/aux-bridge.h> #define CMN_SSM_BANDGAP (0x21 << 2) #define CMN_SSM_BIAS (0x22 << 2) @@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) for_each_available_child_of_node(np, child_np) { struct phy *phy; + ret = 0; - if (of_node_name_eq(child_np, "dp-port")) + if (of_node_name_eq(child_np, "dp-port")) { phy = devm_phy_create(dev, child_np, &rockchip_dp_phy_ops); - else if (of_node_name_eq(child_np, "usb3-port")) + ret = drm_aux_bridge_register_from_node(dev, child_np); + } else if (of_node_name_eq(child_np, "usb3-port")) phy = devm_phy_create(dev, child_np, &rockchip_usb3_phy_ops); else continue; + if (ret) { + pm_runtime_disable(dev); + of_node_put(child_np); + return ret; + } + if (IS_ERR(phy)) { dev_err(dev, "failed to create phy: %pOFn\n", child_np); -- 2.53.0 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge device. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- (no changes since v1) --- drivers/phy/rockchip/Kconfig | 2 ++ drivers/phy/rockchip/phy-rockchip-typec.c | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b607..9173d3b4fef4 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -119,6 +119,8 @@ config PHY_ROCKCHIP_SNPS_PCIE3 config PHY_ROCKCHIP_TYPEC tristate "Rockchip TYPEC PHY Driver" depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE select EXTCON select GENERIC_PHY select RESET_CONTROLLER diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index d9701b6106d5..48070b50416e 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -54,6 +54,7 @@ #include <linux/mfd/syscon.h> #include <linux/phy/phy.h> +#include <drm/bridge/aux-bridge.h> #define CMN_SSM_BANDGAP (0x21 << 2) #define CMN_SSM_BIAS (0x22 << 2) @@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) for_each_available_child_of_node(np, child_np) { struct phy *phy; + ret = 0; - if (of_node_name_eq(child_np, "dp-port")) + if (of_node_name_eq(child_np, "dp-port")) { phy = devm_phy_create(dev, child_np, &rockchip_dp_phy_ops); - else if (of_node_name_eq(child_np, "usb3-port")) + ret = drm_aux_bridge_register_from_node(dev, child_np); + } else if (of_node_name_eq(child_np, "usb3-port")) phy = devm_phy_create(dev, child_np, &rockchip_usb3_phy_ops); else continue; + if (ret) { + pm_runtime_disable(dev); + of_node_put(child_np); + return ret; + } + if (IS_ERR(phy)) { dev_err(dev, "failed to create phy: %pOFn\n", child_np); -- 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] 28+ messages in thread
* Re: [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge 2026-07-17 7:23 ` Chaoyi Chen @ 2026-07-17 7:39 ` sashiko-bot -1 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:39 UTC (permalink / raw) To: Chaoyi Chen; +Cc: olteanv, linux-phy, dri-devel, vkoul, neil.armstrong Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Double-free and reference count underflow of the `child_np` device tree node reference due to conflicting ownership with `drm_aux_bridge_register_from_node()`. -- --- Patch [3]: [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge --- commit 7121c384753e095d5a040f2bd93021b7ae8110d7 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge device. > diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c > index d9701b6106d55..48070b50416e5 100644 > --- a/drivers/phy/rockchip/phy-rockchip-typec.c > +++ b/drivers/phy/rockchip/phy-rockchip-typec.c > @@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) > > for_each_available_child_of_node(np, child_np) { > struct phy *phy; > + ret = 0; > > - if (of_node_name_eq(child_np, "dp-port")) > + if (of_node_name_eq(child_np, "dp-port")) { > phy = devm_phy_create(dev, child_np, > &rockchip_dp_phy_ops); > - else if (of_node_name_eq(child_np, "usb3-port")) > + ret = drm_aux_bridge_register_from_node(dev, child_np); > + } else if (of_node_name_eq(child_np, "usb3-port")) > phy = devm_phy_create(dev, child_np, > &rockchip_usb3_phy_ops); > else > continue; > > + if (ret) { > + pm_runtime_disable(dev); > + of_node_put(child_np); [Severity: High] Does this cause a reference count underflow for the child_np device node? drm_aux_bridge_register_from_node() assigns the node to an auxiliary device, and its error and release paths in drm/bridge/aux-bridge.c unconditionally call of_node_put() to drop the reference. However, the for_each_available_child_of_node() iterator owns the reference to child_np during this loop iteration. Passing it to drm_aux_bridge_register_from_node() without acquiring an additional reference via of_node_get() appears to hand off a reference the bridge doesn't own. If drm_aux_bridge_register_from_node() succeeds, the loop advances and drops the iterator's reference, leading to a double-put when the bridge is eventually released. If drm_aux_bridge_register_from_node() fails, it drops the reference internally. The explicit of_node_put(child_np) here in the error block would then cause an immediate double-put. > + return ret; > + } [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=3 ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge @ 2026-07-17 7:39 ` sashiko-bot 0 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:39 UTC (permalink / raw) To: Chaoyi Chen; +Cc: olteanv, linux-phy, dri-devel, vkoul, neil.armstrong Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Double-free and reference count underflow of the `child_np` device tree node reference due to conflicting ownership with `drm_aux_bridge_register_from_node()`. -- --- Patch [3]: [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge --- commit 7121c384753e095d5a040f2bd93021b7ae8110d7 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge device. > diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c > index d9701b6106d55..48070b50416e5 100644 > --- a/drivers/phy/rockchip/phy-rockchip-typec.c > +++ b/drivers/phy/rockchip/phy-rockchip-typec.c > @@ -1162,16 +1163,24 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) > > for_each_available_child_of_node(np, child_np) { > struct phy *phy; > + ret = 0; > > - if (of_node_name_eq(child_np, "dp-port")) > + if (of_node_name_eq(child_np, "dp-port")) { > phy = devm_phy_create(dev, child_np, > &rockchip_dp_phy_ops); > - else if (of_node_name_eq(child_np, "usb3-port")) > + ret = drm_aux_bridge_register_from_node(dev, child_np); > + } else if (of_node_name_eq(child_np, "usb3-port")) > phy = devm_phy_create(dev, child_np, > &rockchip_usb3_phy_ops); > else > continue; > > + if (ret) { > + pm_runtime_disable(dev); > + of_node_put(child_np); [Severity: High] Does this cause a reference count underflow for the child_np device node? drm_aux_bridge_register_from_node() assigns the node to an auxiliary device, and its error and release paths in drm/bridge/aux-bridge.c unconditionally call of_node_put() to drop the reference. However, the for_each_available_child_of_node() iterator owns the reference to child_np during this loop iteration. Passing it to drm_aux_bridge_register_from_node() without acquiring an additional reference via of_node_get() appears to hand off a reference the bridge doesn't own. If drm_aux_bridge_register_from_node() succeeds, the loop advances and drops the iterator's reference, leading to a double-put when the bridge is eventually released. If drm_aux_bridge_register_from_node() fails, it drops the reference internally. The explicit of_node_put(child_np) here in the error block would then cause an immediate double-put. > + return ret; > + } [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=3 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon 2026-07-17 7:23 ` Chaoyi Chen (?) @ 2026-07-17 7:23 ` Chaoyi Chen -1 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> This patch add support for get PHY lane info without help of extcon. There is no extcon needed if the Type-C controller is present. In this case, the lane info can be get from PHY instead of extcon. The extcon device should still be supported if Type-C controller is not present. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> --- (no changes since v1) --- drivers/gpu/drm/rockchip/cdn-dp-core.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 190cce9530c8..74e1f5422a6e 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -157,6 +157,9 @@ static int cdn_dp_get_port_lanes(struct cdn_dp_port *port) int dptx; u8 lanes; + if (!edev) + return phy_get_bus_width(port->phy); + dptx = extcon_get_state(edev, EXTCON_DISP_DP); if (dptx > 0) { extcon_get_property(edev, EXTCON_DISP_DP, @@ -220,7 +223,7 @@ static bool cdn_dp_check_sink_connection(struct cdn_dp_device *dp) * some docks need more time to power up. */ while (time_before(jiffies, timeout)) { - if (!extcon_get_state(port->extcon, EXTCON_DISP_DP)) + if (port->extcon && !extcon_get_state(port->extcon, EXTCON_DISP_DP)) return false; if (!cdn_dp_get_sink_count(dp, &sink_count)) @@ -386,11 +389,14 @@ static int cdn_dp_enable_phy(struct cdn_dp_device *dp, struct cdn_dp_port *port) goto err_power_on; } - ret = extcon_get_property(port->extcon, EXTCON_DISP_DP, - EXTCON_PROP_USB_TYPEC_POLARITY, &property); - if (ret) { - DRM_DEV_ERROR(dp->dev, "get property failed\n"); - goto err_power_on; + property.intval = 0; + if (port->extcon) { + ret = extcon_get_property(port->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY, &property); + if (ret) { + DRM_DEV_ERROR(dp->dev, "get property failed\n"); + goto err_power_on; + } } port->lanes = cdn_dp_get_port_lanes(port); @@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) for (i = 0; i < dp->ports; i++) { port = dp->port[i]; + if (!port->extcon) + continue; + port->event_nb.notifier_call = cdn_dp_pd_event; ret = devm_extcon_register_notifier(dp->dev, port->extcon, EXTCON_DISP_DP, @@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev) PTR_ERR(phy) == -EPROBE_DEFER) return -EPROBE_DEFER; - if (IS_ERR(extcon) || IS_ERR(phy)) + if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV) continue; port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; - port->extcon = extcon; + port->extcon = IS_ERR(extcon) ? NULL : extcon; port->phy = phy; port->dp = dp; port->id = i; -- 2.53.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> This patch add support for get PHY lane info without help of extcon. There is no extcon needed if the Type-C controller is present. In this case, the lane info can be get from PHY instead of extcon. The extcon device should still be supported if Type-C controller is not present. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> --- (no changes since v1) --- drivers/gpu/drm/rockchip/cdn-dp-core.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 190cce9530c8..74e1f5422a6e 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -157,6 +157,9 @@ static int cdn_dp_get_port_lanes(struct cdn_dp_port *port) int dptx; u8 lanes; + if (!edev) + return phy_get_bus_width(port->phy); + dptx = extcon_get_state(edev, EXTCON_DISP_DP); if (dptx > 0) { extcon_get_property(edev, EXTCON_DISP_DP, @@ -220,7 +223,7 @@ static bool cdn_dp_check_sink_connection(struct cdn_dp_device *dp) * some docks need more time to power up. */ while (time_before(jiffies, timeout)) { - if (!extcon_get_state(port->extcon, EXTCON_DISP_DP)) + if (port->extcon && !extcon_get_state(port->extcon, EXTCON_DISP_DP)) return false; if (!cdn_dp_get_sink_count(dp, &sink_count)) @@ -386,11 +389,14 @@ static int cdn_dp_enable_phy(struct cdn_dp_device *dp, struct cdn_dp_port *port) goto err_power_on; } - ret = extcon_get_property(port->extcon, EXTCON_DISP_DP, - EXTCON_PROP_USB_TYPEC_POLARITY, &property); - if (ret) { - DRM_DEV_ERROR(dp->dev, "get property failed\n"); - goto err_power_on; + property.intval = 0; + if (port->extcon) { + ret = extcon_get_property(port->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY, &property); + if (ret) { + DRM_DEV_ERROR(dp->dev, "get property failed\n"); + goto err_power_on; + } } port->lanes = cdn_dp_get_port_lanes(port); @@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) for (i = 0; i < dp->ports; i++) { port = dp->port[i]; + if (!port->extcon) + continue; + port->event_nb.notifier_call = cdn_dp_pd_event; ret = devm_extcon_register_notifier(dp->dev, port->extcon, EXTCON_DISP_DP, @@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev) PTR_ERR(phy) == -EPROBE_DEFER) return -EPROBE_DEFER; - if (IS_ERR(extcon) || IS_ERR(phy)) + if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV) continue; port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; - port->extcon = extcon; + port->extcon = IS_ERR(extcon) ? NULL : extcon; port->phy = phy; port->dp = dp; port->id = i; -- 2.53.0 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> This patch add support for get PHY lane info without help of extcon. There is no extcon needed if the Type-C controller is present. In this case, the lane info can be get from PHY instead of extcon. The extcon device should still be supported if Type-C controller is not present. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> --- (no changes since v1) --- drivers/gpu/drm/rockchip/cdn-dp-core.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 190cce9530c8..74e1f5422a6e 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -157,6 +157,9 @@ static int cdn_dp_get_port_lanes(struct cdn_dp_port *port) int dptx; u8 lanes; + if (!edev) + return phy_get_bus_width(port->phy); + dptx = extcon_get_state(edev, EXTCON_DISP_DP); if (dptx > 0) { extcon_get_property(edev, EXTCON_DISP_DP, @@ -220,7 +223,7 @@ static bool cdn_dp_check_sink_connection(struct cdn_dp_device *dp) * some docks need more time to power up. */ while (time_before(jiffies, timeout)) { - if (!extcon_get_state(port->extcon, EXTCON_DISP_DP)) + if (port->extcon && !extcon_get_state(port->extcon, EXTCON_DISP_DP)) return false; if (!cdn_dp_get_sink_count(dp, &sink_count)) @@ -386,11 +389,14 @@ static int cdn_dp_enable_phy(struct cdn_dp_device *dp, struct cdn_dp_port *port) goto err_power_on; } - ret = extcon_get_property(port->extcon, EXTCON_DISP_DP, - EXTCON_PROP_USB_TYPEC_POLARITY, &property); - if (ret) { - DRM_DEV_ERROR(dp->dev, "get property failed\n"); - goto err_power_on; + property.intval = 0; + if (port->extcon) { + ret = extcon_get_property(port->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY, &property); + if (ret) { + DRM_DEV_ERROR(dp->dev, "get property failed\n"); + goto err_power_on; + } } port->lanes = cdn_dp_get_port_lanes(port); @@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) for (i = 0; i < dp->ports; i++) { port = dp->port[i]; + if (!port->extcon) + continue; + port->event_nb.notifier_call = cdn_dp_pd_event; ret = devm_extcon_register_notifier(dp->dev, port->extcon, EXTCON_DISP_DP, @@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev) PTR_ERR(phy) == -EPROBE_DEFER) return -EPROBE_DEFER; - if (IS_ERR(extcon) || IS_ERR(phy)) + if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV) continue; port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; - port->extcon = extcon; + port->extcon = IS_ERR(extcon) ? NULL : extcon; port->phy = phy; port->dp = dp; port->id = i; -- 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] 28+ messages in thread
* Re: [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon 2026-07-17 7:23 ` Chaoyi Chen @ 2026-07-17 7:56 ` sashiko-bot -1 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:56 UTC (permalink / raw) To: Chaoyi Chen; +Cc: olteanv, linux-phy, vkoul, dri-devel, neil.armstrong Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Incorrect error checking logic in `cdn_dp_probe` skips port initialization for valid extcon devices, breaking DisplayPort for legacy boards. - [High] Removing the `extcon` notifier without providing an alternative mechanism completely breaks hotplug detection for Type-C connections without an extcon device. -- --- Patch [4]: [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon --- commit 2789d7ccb0bb45f82d439538cf9aeaac1da9c5e9 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/rockchip: cdn-dp: Support handle lane info without extcon This patch adds support for getting PHY lane info without the help of extcon. There is no extcon needed if the Type-C controller is present. The extcon device should still be supported if the Type-C controller is not present. > diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c > index 190cce9530c82..74e1f5422a6e2 100644 > --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c > +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c [ ... ] > @@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) > for (i = 0; i < dp->ports; i++) { > port = dp->port[i]; > > + if (!port->extcon) > + continue; > + [Severity: High] Does skipping the notifier registration when extcon is absent leave the driver without any hotplug detection mechanism? The driver relies on the cdn_dp_pd_event notifier (registered via extcon) to schedule dp->event_work, which probes the hardware, updates the connection state, and calls drm_bridge_hpd_notify(). Without an alternative HPD mechanism when extcon is absent, won't hotplug events be silently ignored, causing displays to only be detected if plugged in during boot or resume? > port->event_nb.notifier_call = cdn_dp_pd_event; > ret = devm_extcon_register_notifier(dp->dev, port->extcon, > EXTCON_DISP_DP, [ ... ] > @@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev) > PTR_ERR(phy) == -EPROBE_DEFER) > return -EPROBE_DEFER; > > - if (IS_ERR(extcon) || IS_ERR(phy)) > + if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV) > continue; [Severity: High] Does evaluating PTR_ERR(extcon) != -ENODEV without first checking if extcon is an error pointer break initialization for valid extcon devices? When a valid extcon device is successfully found, extcon is a valid kernel pointer. Casting this pointer to long via PTR_ERR() yields a memory address that is not equal to -ENODEV. Since the condition incorrectly evaluates to true, won't the loop continue and skip port allocation for all valid extcon devices, breaking DisplayPort on existing platforms that use extcon? > > port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); > if (!port) > return -ENOMEM; > > - port->extcon = extcon; > + port->extcon = IS_ERR(extcon) ? NULL : extcon; > port->phy = phy; > port->dp = dp; > port->id = i; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=4 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon @ 2026-07-17 7:56 ` sashiko-bot 0 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:56 UTC (permalink / raw) To: Chaoyi Chen; +Cc: olteanv, linux-phy, vkoul, dri-devel, neil.armstrong Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Incorrect error checking logic in `cdn_dp_probe` skips port initialization for valid extcon devices, breaking DisplayPort for legacy boards. - [High] Removing the `extcon` notifier without providing an alternative mechanism completely breaks hotplug detection for Type-C connections without an extcon device. -- --- Patch [4]: [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon --- commit 2789d7ccb0bb45f82d439538cf9aeaac1da9c5e9 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/rockchip: cdn-dp: Support handle lane info without extcon This patch adds support for getting PHY lane info without the help of extcon. There is no extcon needed if the Type-C controller is present. The extcon device should still be supported if the Type-C controller is not present. > diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c > index 190cce9530c82..74e1f5422a6e2 100644 > --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c > +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c [ ... ] > @@ -1027,6 +1033,9 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) > for (i = 0; i < dp->ports; i++) { > port = dp->port[i]; > > + if (!port->extcon) > + continue; > + [Severity: High] Does skipping the notifier registration when extcon is absent leave the driver without any hotplug detection mechanism? The driver relies on the cdn_dp_pd_event notifier (registered via extcon) to schedule dp->event_work, which probes the hardware, updates the connection state, and calls drm_bridge_hpd_notify(). Without an alternative HPD mechanism when extcon is absent, won't hotplug events be silently ignored, causing displays to only be detected if plugged in during boot or resume? > port->event_nb.notifier_call = cdn_dp_pd_event; > ret = devm_extcon_register_notifier(dp->dev, port->extcon, > EXTCON_DISP_DP, [ ... ] > @@ -1119,14 +1128,14 @@ static int cdn_dp_probe(struct platform_device *pdev) > PTR_ERR(phy) == -EPROBE_DEFER) > return -EPROBE_DEFER; > > - if (IS_ERR(extcon) || IS_ERR(phy)) > + if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV) > continue; [Severity: High] Does evaluating PTR_ERR(extcon) != -ENODEV without first checking if extcon is an error pointer break initialization for valid extcon devices? When a valid extcon device is successfully found, extcon is a valid kernel pointer. Casting this pointer to long via PTR_ERR() yields a memory address that is not equal to -ENODEV. Since the condition incorrectly evaluates to true, won't the loop continue and skip port allocation for all valid extcon devices, breaking DisplayPort on existing platforms that use extcon? > > port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); > if (!port) > return -ENOMEM; > > - port->extcon = extcon; > + port->extcon = IS_ERR(extcon) ? NULL : extcon; > port->phy = phy; > port->dp = dp; > port->id = i; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=4 ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection 2026-07-17 7:23 ` Chaoyi Chen (?) @ 2026-07-17 7:23 ` Chaoyi Chen -1 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The RK3399 has two USB/DP combo PHY and one CDN-DP controller. And the CDN-DP can be switched to output to one of the PHYs. If both ports are plugged into DP, DP will select the first port for output. This patch adds support for multiple bridges, enabling users to flexibly select the output port. For each PHY port, a separate encoder and bridge are registered. The change is based on the DRM AUX HPD bridge, rather than the extcon approach. This requires the DT to correctly describe the connections between the first bridge in bridge chain and DP controller. For example, the bridge chain may be like this: PHY aux birdge -> fsa4480 analog audio switch bridge -> onnn,nb7vpq904m USB reminder bridge -> USB-C controller AUX HPD bridge In this case, the connection relationships among the PHY aux bridge and the DP contorller need to be described in DT. In addition, the cdn_dp_parse_next_bridge_dt() will parses it and determines whether to register one or two bridges. Since there is only one DP controller, only one of the PHY ports can output at a time. The key is how to switch between different PHYs, which is handled by cdn_dp_switch_port() and cdn_dp_enable(). There are two cases: 1. Neither bridge is enabled. In this case, both bridges can independently read the EDID, and the PHY port may switch before reading the EDID. 2. One bridge is already enabled. In this case, other bridges are not allowed to read the EDID. So we will try to return the cached EDID. Since the scenario of two ports plug in at the same time is rare, I don't have a board which support two TypeC connector to test this. Therefore, I tested forced switching on a single PHY port, as well as output using a fake PHY port alongside a real PHY port. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> --- Changes in v3: - Remove now-redundant call to drm_connector_attach_encoder() Changes in v2: - Fix the check logic for dp->active_port (0 is valid). - Fix the uninitialized prev_port. - Remove duplicate logic from cdn_dp_switch_port(). - Fix the prototype of cdn_dp_bridge_hpd_notify(). - Properly release connector->fwnode. - Properly release next_bridge. --- drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/rockchip/cdn-dp-core.c | 333 ++++++++++++++++++++----- drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +- 3 files changed, 294 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index e7f49fe845ea..ce70f91c13e6 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -60,6 +60,7 @@ config ROCKCHIP_CDN_DP select DRM_DISPLAY_HELPER select DRM_BRIDGE_CONNECTOR select DRM_DISPLAY_DP_HELPER + select DRM_AUX_HPD_BRIDGE help This selects support for Rockchip SoC specific extensions for the cdn DP driver. If you want to enable Dp on diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 74e1f5422a6e..5e513f6930cb 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -28,16 +28,17 @@ #include "cdn-dp-core.h" #include "cdn-dp-reg.h" -static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge) +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, + struct cdn_dp_port *port); + +static inline struct cdn_dp_bridge *bridge_to_dp_bridge(struct drm_bridge *bridge) { - return container_of(bridge, struct cdn_dp_device, bridge); + return container_of(bridge, struct cdn_dp_bridge, bridge); } -static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder) +static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge) { - struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); - - return container_of(rkencoder, struct cdn_dp_device, encoder); + return bridge_to_dp_bridge(bridge)->parent; } #define GRF_SOC_CON9 0x6224 @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count) static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp) { struct cdn_dp_port *port; - int i, lanes; + int i, lanes[MAX_PHY]; for (i = 0; i < dp->ports; i++) { port = dp->port[i]; - lanes = cdn_dp_get_port_lanes(port); - if (lanes) + lanes[i] = cdn_dp_get_port_lanes(port); + if (!dp->next_bridge_valid) return port; } + + if (dp->next_bridge_valid) { + /* If more than one port is available, pick the last active port */ + if (dp->active_port >= 0 && lanes[dp->active_port]) + return dp->port[dp->active_port]; + + /* If the last active port is not available, pick an available port in order */ + for (i = 0; i < dp->bridge_count; i++) { + if (lanes[i]) + return dp->port[i]; + } + } + return NULL; } @@ -254,12 +268,45 @@ static const struct drm_edid * cdn_dp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { struct cdn_dp_device *dp = bridge_to_dp(bridge); - const struct drm_edid *drm_edid; + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); + struct cdn_dp_port *port = dp->port[dp_bridge->id]; + struct cdn_dp_port *prev_port = NULL; + const struct drm_edid *drm_edid = NULL; + int i, ret; mutex_lock(&dp->lock); + + /* More than one port is available */ + if (dp->bridge_count > 1 && !port->phy_enabled) { + for (i = 0; i < dp->bridge_count; i++) { + /* Another port already enable */ + if (dp->bridge_list[i] != dp_bridge && dp->bridge_list[i]->enabled) + goto get_cache; + /* Find already enabled port */ + if (dp->port[i]->phy_enabled) + prev_port = dp->port[i]; + } + + /* Switch to current port */ + if (prev_port) { + ret = cdn_dp_switch_port(dp, prev_port, port); + if (ret) + goto get_cache; + } + } + drm_edid = drm_edid_read_custom(connector, cdn_dp_get_edid_block, dp); + /* replace edid cache */ + if (dp->edid_cache[dp_bridge->id]) + drm_edid_free(dp->edid_cache[dp_bridge->id]); + dp->edid_cache[dp_bridge->id] = drm_edid_dup(drm_edid); + mutex_unlock(&dp->lock); + return drm_edid; +get_cache: + drm_edid = drm_edid_dup(dp->edid_cache[dp_bridge->id]); + mutex_unlock(&dp->lock); return drm_edid; } @@ -268,12 +315,13 @@ cdn_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *display_info, const struct drm_display_mode *mode) { + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); struct cdn_dp_device *dp = bridge_to_dp(bridge); u32 requested, actual, rate, sink_max, source_max = 0; u8 lanes, bpc; /* If DP is disconnected, every mode is invalid */ - if (!dp->connected) + if (!dp_bridge->connected || !dp->connected) return MODE_BAD; switch (display_info->bpc) { @@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp) return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes)); } +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, + struct cdn_dp_port *port) +{ + int ret; + + ret = cdn_dp_disable_phy(dp, prev_port); + if (ret) + goto out; + ret = cdn_dp_enable_phy(dp, port); + if (ret) + goto out; + + ret = cdn_dp_get_sink_capability(dp); + if (ret) { + cdn_dp_disable_phy(dp, port); + goto out; + } + + dp->lanes = port->lanes; + + if (!cdn_dp_check_link_status(dp)) { + dev_info(dp->dev, "Connected with sink; re-train link\n"); + + ret = cdn_dp_train_link(dp); + if (ret) { + dev_err(dp->dev, "Training link failed: %d\n", ret); + goto out; + } + + ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE); + if (ret) { + dev_err(dp->dev, "Failed to idle video %d\n", ret); + goto out; + } + + ret = cdn_dp_config_video(dp); + if (ret) + dev_err(dp->dev, "Failed to configure video: %d\n", ret); + } + +out: + return ret; +} + static void cdn_dp_display_info_update(struct cdn_dp_device *dp, struct drm_display_info *display_info) { @@ -572,6 +664,7 @@ static void cdn_dp_display_info_update(struct cdn_dp_device *dp, static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct cdn_dp_device *dp = bridge_to_dp(bridge); + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); struct drm_connector *connector; int ret, val; @@ -581,7 +674,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at cdn_dp_display_info_update(dp, &connector->display_info); - ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp->encoder.encoder); + ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp_bridge->encoder.encoder); if (ret < 0) { DRM_DEV_ERROR(dp->dev, "Could not get vop id, %d", ret); return; @@ -600,6 +693,9 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at mutex_lock(&dp->lock); + if (dp->next_bridge_valid) + dp->active_port = dp_bridge->id; + ret = cdn_dp_enable(dp); if (ret) { DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n", @@ -632,6 +728,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at goto out; } + dp_bridge->enabled = true; out: mutex_unlock(&dp->lock); } @@ -639,9 +736,11 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at static void cdn_dp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct cdn_dp_device *dp = bridge_to_dp(bridge); + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); int ret; mutex_lock(&dp->lock); + dp_bridge->enabled = false; if (dp->active) { ret = cdn_dp_disable(dp); @@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge, return ret; } +static void cdn_dp_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); + struct cdn_dp_device *dp = bridge_to_dp(bridge); + + dp->bridge_list[dp_bridge->id]->connected = status == connector_status_connected; + schedule_work(&dp->event_work); +} + static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -838,6 +948,7 @@ static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { .atomic_disable = cdn_dp_bridge_atomic_disable, .mode_valid = cdn_dp_bridge_mode_valid, .mode_set = cdn_dp_bridge_mode_set, + .hpd_notify = cdn_dp_bridge_hpd_notify, .dp_audio_prepare = cdn_dp_audio_prepare, .dp_audio_mute_stream = cdn_dp_audio_mute_stream, @@ -886,7 +997,8 @@ static void cdn_dp_pd_event_work(struct work_struct *work) { struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device, event_work); - int ret; + bool connected; + int i, ret; mutex_lock(&dp->lock); @@ -945,9 +1057,12 @@ static void cdn_dp_pd_event_work(struct work_struct *work) out: mutex_unlock(&dp->lock); - drm_bridge_hpd_notify(&dp->bridge, - dp->connected ? connector_status_connected - : connector_status_disconnected); + for (i = 0; i < dp->bridge_count; i++) { + connected = dp->connected && dp->bridge_list[i]->connected; + drm_bridge_hpd_notify(&dp->bridge_list[i]->bridge, + connected ? connector_status_connected + : connector_status_disconnected); + } } static int cdn_dp_pd_event(struct notifier_block *nb, @@ -967,28 +1082,16 @@ static int cdn_dp_pd_event(struct notifier_block *nb, return NOTIFY_DONE; } -static int cdn_dp_bind(struct device *dev, struct device *master, void *data) +static int cdn_bridge_add(struct device *dev, + struct drm_bridge *bridge, + struct drm_bridge *next_bridge, + struct drm_encoder *encoder) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder; + struct drm_device *drm_dev = dp->drm_dev; + struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL; struct drm_connector *connector; - struct cdn_dp_port *port; - struct drm_device *drm_dev = data; - int ret, i; - - ret = cdn_dp_parse_dt(dp); - if (ret < 0) - return ret; - - dp->drm_dev = drm_dev; - dp->connected = false; - dp->active = false; - dp->active_port = -1; - dp->fw_loaded = false; - - INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); - - encoder = &dp->encoder.encoder; + int ret; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); @@ -1003,26 +1106,35 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs); - dp->bridge.ops = - DRM_BRIDGE_OP_DETECT | - DRM_BRIDGE_OP_EDID | - DRM_BRIDGE_OP_HPD | - DRM_BRIDGE_OP_DP_AUDIO; - dp->bridge.of_node = dp->dev->of_node; - dp->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; - dp->bridge.hdmi_audio_dev = dp->dev; - dp->bridge.hdmi_audio_max_i2s_playback_channels = 8; - dp->bridge.hdmi_audio_spdif_playback = 1; - dp->bridge.hdmi_audio_dai_port = -1; - - ret = devm_drm_bridge_add(dev, &dp->bridge); + bridge->ops = + DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_DP_AUDIO; + bridge->of_node = dp->dev->of_node; + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; + bridge->hdmi_audio_dev = dp->dev; + bridge->hdmi_audio_max_i2s_playback_channels = 8; + bridge->hdmi_audio_spdif_playback = 1; + bridge->hdmi_audio_dai_port = -1; + + ret = devm_drm_bridge_add(dev, bridge); if (ret) return ret; - ret = drm_bridge_attach(encoder, &dp->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return ret; + if (next_bridge) { + ret = drm_bridge_attach(encoder, next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + last_bridge = drm_bridge_chain_get_last_bridge(bridge->encoder); + } + connector = drm_bridge_connector_init(drm_dev, encoder); if (IS_ERR(connector)) { ret = PTR_ERR(connector); @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) return ret; } + if (last_bridge) { + fwnode_handle_put(connector->fwnode); + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node)); + } + + return 0; +} + +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp) +{ + struct device_node *np = dp->dev->of_node; + struct device_node *port __free(device_node) = of_graph_get_port_by_id(np, 1); + struct drm_bridge *bridge; + int count = 0; + int ret = 0; + int i; + + /* If device use extcon, do not use hpd bridge */ + for (i = 0; i < dp->ports; i++) { + if (dp->port[i]->extcon) { + dp->bridge_count = 1; + return 0; + } + } + + /* One endpoint may correspond to one next bridge. */ + for_each_of_graph_port_endpoint(port, dp_ep) { + struct device_node *next_bridge_node __free(device_node) = + of_graph_get_remote_port_parent(dp_ep); + + bridge = of_drm_find_bridge(next_bridge_node); + if (!bridge) { + ret = -EPROBE_DEFER; + goto out; + } + + dp->next_bridge_valid = true; + dp->next_bridge_list[count] = drm_bridge_get(bridge); + count++; + } + +out: + dp->bridge_count = count ? count : 1; + return ret; +} + +static int cdn_dp_bind(struct device *dev, struct device *master, void *data) +{ + struct cdn_dp_device *dp = dev_get_drvdata(dev); + struct drm_bridge *bridge, *next_bridge; + struct drm_encoder *encoder; + struct cdn_dp_port *port; + struct drm_device *drm_dev = data; + struct cdn_dp_bridge *dp_bridge; + int ret, i; + + ret = cdn_dp_parse_dt(dp); + if (ret < 0) + return ret; + + ret = cdn_dp_parse_next_bridge_dt(dp); + if (ret) + goto out; + + dp->drm_dev = drm_dev; + dp->connected = false; + dp->active = false; + dp->active_port = -1; + dp->fw_loaded = false; + + for (i = 0; i < dp->bridge_count; i++) { + dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge, + &cdn_dp_bridge_funcs); + if (IS_ERR(dp_bridge)) { + ret = PTR_ERR(dp_bridge); + goto out; + } + dp_bridge->id = i; + dp_bridge->parent = dp; + if (!dp->next_bridge_valid) + dp_bridge->connected = true; + dp->bridge_list[i] = dp_bridge; + } + + for (i = 0; i < dp->bridge_count; i++) { + encoder = &dp->bridge_list[i]->encoder.encoder; + bridge = &dp->bridge_list[i]->bridge; + next_bridge = dp->next_bridge_list[i]; + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder); + if (ret) + goto out; + } + + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); + for (i = 0; i < dp->ports; i++) { port = dp->port[i]; @@ -1043,7 +1250,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) if (ret) { DRM_DEV_ERROR(dev, "register EXTCON_DISP_DP notifier err\n"); - return ret; + goto out; } } @@ -1052,15 +1259,29 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) schedule_work(&dp->event_work); return 0; + +out: + for (i = 0; i < MAX_PHY; i++) + drm_bridge_put(dp->next_bridge_list[i]); + + return ret; } static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dp->encoder.encoder; + struct drm_encoder *encoder; + int i; cancel_work_sync(&dp->event_work); - encoder->funcs->destroy(encoder); + for (i = 0; i < dp->bridge_count; i++) { + encoder = &dp->bridge_list[i]->encoder.encoder; + encoder->funcs->destroy(encoder); + drm_bridge_put(dp->next_bridge_list[i]); + } + + for (i = 0; i < MAX_PHY; i++) + drm_edid_free(dp->edid_cache[i]); pm_runtime_disable(dev); if (dp->fw_loaded) @@ -1111,10 +1332,10 @@ static int cdn_dp_probe(struct platform_device *pdev) int ret; int i; - dp = devm_drm_bridge_alloc(dev, struct cdn_dp_device, bridge, - &cdn_dp_bridge_funcs); - if (IS_ERR(dp)) - return PTR_ERR(dp); + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + dp->dev = dev; match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node); diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h index e9c30b9fd543..c10e423bbf06 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.h +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h @@ -38,6 +38,8 @@ enum vic_pxl_encoding_format { Y_ONLY = 0x10, }; +struct cdn_dp_device; + struct video_info { bool h_sync_polarity; bool v_sync_polarity; @@ -63,16 +65,28 @@ struct cdn_dp_port { u8 id; }; +struct cdn_dp_bridge { + struct cdn_dp_device *parent; + struct drm_bridge bridge; + struct rockchip_encoder encoder; + bool connected; + bool enabled; + int id; +}; + struct cdn_dp_device { struct device *dev; struct drm_device *drm_dev; - struct drm_bridge bridge; - struct rockchip_encoder encoder; + int bridge_count; + struct cdn_dp_bridge *bridge_list[MAX_PHY]; + struct drm_bridge *next_bridge_list[MAX_PHY]; + const struct drm_edid *edid_cache[MAX_PHY]; struct drm_display_mode mode; struct platform_device *audio_pdev; struct work_struct event_work; struct mutex lock; + bool next_bridge_valid; bool connected; bool active; bool suspended; -- 2.53.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The RK3399 has two USB/DP combo PHY and one CDN-DP controller. And the CDN-DP can be switched to output to one of the PHYs. If both ports are plugged into DP, DP will select the first port for output. This patch adds support for multiple bridges, enabling users to flexibly select the output port. For each PHY port, a separate encoder and bridge are registered. The change is based on the DRM AUX HPD bridge, rather than the extcon approach. This requires the DT to correctly describe the connections between the first bridge in bridge chain and DP controller. For example, the bridge chain may be like this: PHY aux birdge -> fsa4480 analog audio switch bridge -> onnn,nb7vpq904m USB reminder bridge -> USB-C controller AUX HPD bridge In this case, the connection relationships among the PHY aux bridge and the DP contorller need to be described in DT. In addition, the cdn_dp_parse_next_bridge_dt() will parses it and determines whether to register one or two bridges. Since there is only one DP controller, only one of the PHY ports can output at a time. The key is how to switch between different PHYs, which is handled by cdn_dp_switch_port() and cdn_dp_enable(). There are two cases: 1. Neither bridge is enabled. In this case, both bridges can independently read the EDID, and the PHY port may switch before reading the EDID. 2. One bridge is already enabled. In this case, other bridges are not allowed to read the EDID. So we will try to return the cached EDID. Since the scenario of two ports plug in at the same time is rare, I don't have a board which support two TypeC connector to test this. Therefore, I tested forced switching on a single PHY port, as well as output using a fake PHY port alongside a real PHY port. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> --- Changes in v3: - Remove now-redundant call to drm_connector_attach_encoder() Changes in v2: - Fix the check logic for dp->active_port (0 is valid). - Fix the uninitialized prev_port. - Remove duplicate logic from cdn_dp_switch_port(). - Fix the prototype of cdn_dp_bridge_hpd_notify(). - Properly release connector->fwnode. - Properly release next_bridge. --- drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/rockchip/cdn-dp-core.c | 333 ++++++++++++++++++++----- drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +- 3 files changed, 294 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index e7f49fe845ea..ce70f91c13e6 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -60,6 +60,7 @@ config ROCKCHIP_CDN_DP select DRM_DISPLAY_HELPER select DRM_BRIDGE_CONNECTOR select DRM_DISPLAY_DP_HELPER + select DRM_AUX_HPD_BRIDGE help This selects support for Rockchip SoC specific extensions for the cdn DP driver. If you want to enable Dp on diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 74e1f5422a6e..5e513f6930cb 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -28,16 +28,17 @@ #include "cdn-dp-core.h" #include "cdn-dp-reg.h" -static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge) +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, + struct cdn_dp_port *port); + +static inline struct cdn_dp_bridge *bridge_to_dp_bridge(struct drm_bridge *bridge) { - return container_of(bridge, struct cdn_dp_device, bridge); + return container_of(bridge, struct cdn_dp_bridge, bridge); } -static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder) +static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge) { - struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); - - return container_of(rkencoder, struct cdn_dp_device, encoder); + return bridge_to_dp_bridge(bridge)->parent; } #define GRF_SOC_CON9 0x6224 @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count) static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp) { struct cdn_dp_port *port; - int i, lanes; + int i, lanes[MAX_PHY]; for (i = 0; i < dp->ports; i++) { port = dp->port[i]; - lanes = cdn_dp_get_port_lanes(port); - if (lanes) + lanes[i] = cdn_dp_get_port_lanes(port); + if (!dp->next_bridge_valid) return port; } + + if (dp->next_bridge_valid) { + /* If more than one port is available, pick the last active port */ + if (dp->active_port >= 0 && lanes[dp->active_port]) + return dp->port[dp->active_port]; + + /* If the last active port is not available, pick an available port in order */ + for (i = 0; i < dp->bridge_count; i++) { + if (lanes[i]) + return dp->port[i]; + } + } + return NULL; } @@ -254,12 +268,45 @@ static const struct drm_edid * cdn_dp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { struct cdn_dp_device *dp = bridge_to_dp(bridge); - const struct drm_edid *drm_edid; + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); + struct cdn_dp_port *port = dp->port[dp_bridge->id]; + struct cdn_dp_port *prev_port = NULL; + const struct drm_edid *drm_edid = NULL; + int i, ret; mutex_lock(&dp->lock); + + /* More than one port is available */ + if (dp->bridge_count > 1 && !port->phy_enabled) { + for (i = 0; i < dp->bridge_count; i++) { + /* Another port already enable */ + if (dp->bridge_list[i] != dp_bridge && dp->bridge_list[i]->enabled) + goto get_cache; + /* Find already enabled port */ + if (dp->port[i]->phy_enabled) + prev_port = dp->port[i]; + } + + /* Switch to current port */ + if (prev_port) { + ret = cdn_dp_switch_port(dp, prev_port, port); + if (ret) + goto get_cache; + } + } + drm_edid = drm_edid_read_custom(connector, cdn_dp_get_edid_block, dp); + /* replace edid cache */ + if (dp->edid_cache[dp_bridge->id]) + drm_edid_free(dp->edid_cache[dp_bridge->id]); + dp->edid_cache[dp_bridge->id] = drm_edid_dup(drm_edid); + mutex_unlock(&dp->lock); + return drm_edid; +get_cache: + drm_edid = drm_edid_dup(dp->edid_cache[dp_bridge->id]); + mutex_unlock(&dp->lock); return drm_edid; } @@ -268,12 +315,13 @@ cdn_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *display_info, const struct drm_display_mode *mode) { + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); struct cdn_dp_device *dp = bridge_to_dp(bridge); u32 requested, actual, rate, sink_max, source_max = 0; u8 lanes, bpc; /* If DP is disconnected, every mode is invalid */ - if (!dp->connected) + if (!dp_bridge->connected || !dp->connected) return MODE_BAD; switch (display_info->bpc) { @@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp) return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes)); } +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, + struct cdn_dp_port *port) +{ + int ret; + + ret = cdn_dp_disable_phy(dp, prev_port); + if (ret) + goto out; + ret = cdn_dp_enable_phy(dp, port); + if (ret) + goto out; + + ret = cdn_dp_get_sink_capability(dp); + if (ret) { + cdn_dp_disable_phy(dp, port); + goto out; + } + + dp->lanes = port->lanes; + + if (!cdn_dp_check_link_status(dp)) { + dev_info(dp->dev, "Connected with sink; re-train link\n"); + + ret = cdn_dp_train_link(dp); + if (ret) { + dev_err(dp->dev, "Training link failed: %d\n", ret); + goto out; + } + + ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE); + if (ret) { + dev_err(dp->dev, "Failed to idle video %d\n", ret); + goto out; + } + + ret = cdn_dp_config_video(dp); + if (ret) + dev_err(dp->dev, "Failed to configure video: %d\n", ret); + } + +out: + return ret; +} + static void cdn_dp_display_info_update(struct cdn_dp_device *dp, struct drm_display_info *display_info) { @@ -572,6 +664,7 @@ static void cdn_dp_display_info_update(struct cdn_dp_device *dp, static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct cdn_dp_device *dp = bridge_to_dp(bridge); + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); struct drm_connector *connector; int ret, val; @@ -581,7 +674,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at cdn_dp_display_info_update(dp, &connector->display_info); - ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp->encoder.encoder); + ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp_bridge->encoder.encoder); if (ret < 0) { DRM_DEV_ERROR(dp->dev, "Could not get vop id, %d", ret); return; @@ -600,6 +693,9 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at mutex_lock(&dp->lock); + if (dp->next_bridge_valid) + dp->active_port = dp_bridge->id; + ret = cdn_dp_enable(dp); if (ret) { DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n", @@ -632,6 +728,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at goto out; } + dp_bridge->enabled = true; out: mutex_unlock(&dp->lock); } @@ -639,9 +736,11 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at static void cdn_dp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct cdn_dp_device *dp = bridge_to_dp(bridge); + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); int ret; mutex_lock(&dp->lock); + dp_bridge->enabled = false; if (dp->active) { ret = cdn_dp_disable(dp); @@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge, return ret; } +static void cdn_dp_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); + struct cdn_dp_device *dp = bridge_to_dp(bridge); + + dp->bridge_list[dp_bridge->id]->connected = status == connector_status_connected; + schedule_work(&dp->event_work); +} + static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -838,6 +948,7 @@ static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { .atomic_disable = cdn_dp_bridge_atomic_disable, .mode_valid = cdn_dp_bridge_mode_valid, .mode_set = cdn_dp_bridge_mode_set, + .hpd_notify = cdn_dp_bridge_hpd_notify, .dp_audio_prepare = cdn_dp_audio_prepare, .dp_audio_mute_stream = cdn_dp_audio_mute_stream, @@ -886,7 +997,8 @@ static void cdn_dp_pd_event_work(struct work_struct *work) { struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device, event_work); - int ret; + bool connected; + int i, ret; mutex_lock(&dp->lock); @@ -945,9 +1057,12 @@ static void cdn_dp_pd_event_work(struct work_struct *work) out: mutex_unlock(&dp->lock); - drm_bridge_hpd_notify(&dp->bridge, - dp->connected ? connector_status_connected - : connector_status_disconnected); + for (i = 0; i < dp->bridge_count; i++) { + connected = dp->connected && dp->bridge_list[i]->connected; + drm_bridge_hpd_notify(&dp->bridge_list[i]->bridge, + connected ? connector_status_connected + : connector_status_disconnected); + } } static int cdn_dp_pd_event(struct notifier_block *nb, @@ -967,28 +1082,16 @@ static int cdn_dp_pd_event(struct notifier_block *nb, return NOTIFY_DONE; } -static int cdn_dp_bind(struct device *dev, struct device *master, void *data) +static int cdn_bridge_add(struct device *dev, + struct drm_bridge *bridge, + struct drm_bridge *next_bridge, + struct drm_encoder *encoder) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder; + struct drm_device *drm_dev = dp->drm_dev; + struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL; struct drm_connector *connector; - struct cdn_dp_port *port; - struct drm_device *drm_dev = data; - int ret, i; - - ret = cdn_dp_parse_dt(dp); - if (ret < 0) - return ret; - - dp->drm_dev = drm_dev; - dp->connected = false; - dp->active = false; - dp->active_port = -1; - dp->fw_loaded = false; - - INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); - - encoder = &dp->encoder.encoder; + int ret; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); @@ -1003,26 +1106,35 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs); - dp->bridge.ops = - DRM_BRIDGE_OP_DETECT | - DRM_BRIDGE_OP_EDID | - DRM_BRIDGE_OP_HPD | - DRM_BRIDGE_OP_DP_AUDIO; - dp->bridge.of_node = dp->dev->of_node; - dp->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; - dp->bridge.hdmi_audio_dev = dp->dev; - dp->bridge.hdmi_audio_max_i2s_playback_channels = 8; - dp->bridge.hdmi_audio_spdif_playback = 1; - dp->bridge.hdmi_audio_dai_port = -1; - - ret = devm_drm_bridge_add(dev, &dp->bridge); + bridge->ops = + DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_DP_AUDIO; + bridge->of_node = dp->dev->of_node; + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; + bridge->hdmi_audio_dev = dp->dev; + bridge->hdmi_audio_max_i2s_playback_channels = 8; + bridge->hdmi_audio_spdif_playback = 1; + bridge->hdmi_audio_dai_port = -1; + + ret = devm_drm_bridge_add(dev, bridge); if (ret) return ret; - ret = drm_bridge_attach(encoder, &dp->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return ret; + if (next_bridge) { + ret = drm_bridge_attach(encoder, next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + last_bridge = drm_bridge_chain_get_last_bridge(bridge->encoder); + } + connector = drm_bridge_connector_init(drm_dev, encoder); if (IS_ERR(connector)) { ret = PTR_ERR(connector); @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) return ret; } + if (last_bridge) { + fwnode_handle_put(connector->fwnode); + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node)); + } + + return 0; +} + +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp) +{ + struct device_node *np = dp->dev->of_node; + struct device_node *port __free(device_node) = of_graph_get_port_by_id(np, 1); + struct drm_bridge *bridge; + int count = 0; + int ret = 0; + int i; + + /* If device use extcon, do not use hpd bridge */ + for (i = 0; i < dp->ports; i++) { + if (dp->port[i]->extcon) { + dp->bridge_count = 1; + return 0; + } + } + + /* One endpoint may correspond to one next bridge. */ + for_each_of_graph_port_endpoint(port, dp_ep) { + struct device_node *next_bridge_node __free(device_node) = + of_graph_get_remote_port_parent(dp_ep); + + bridge = of_drm_find_bridge(next_bridge_node); + if (!bridge) { + ret = -EPROBE_DEFER; + goto out; + } + + dp->next_bridge_valid = true; + dp->next_bridge_list[count] = drm_bridge_get(bridge); + count++; + } + +out: + dp->bridge_count = count ? count : 1; + return ret; +} + +static int cdn_dp_bind(struct device *dev, struct device *master, void *data) +{ + struct cdn_dp_device *dp = dev_get_drvdata(dev); + struct drm_bridge *bridge, *next_bridge; + struct drm_encoder *encoder; + struct cdn_dp_port *port; + struct drm_device *drm_dev = data; + struct cdn_dp_bridge *dp_bridge; + int ret, i; + + ret = cdn_dp_parse_dt(dp); + if (ret < 0) + return ret; + + ret = cdn_dp_parse_next_bridge_dt(dp); + if (ret) + goto out; + + dp->drm_dev = drm_dev; + dp->connected = false; + dp->active = false; + dp->active_port = -1; + dp->fw_loaded = false; + + for (i = 0; i < dp->bridge_count; i++) { + dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge, + &cdn_dp_bridge_funcs); + if (IS_ERR(dp_bridge)) { + ret = PTR_ERR(dp_bridge); + goto out; + } + dp_bridge->id = i; + dp_bridge->parent = dp; + if (!dp->next_bridge_valid) + dp_bridge->connected = true; + dp->bridge_list[i] = dp_bridge; + } + + for (i = 0; i < dp->bridge_count; i++) { + encoder = &dp->bridge_list[i]->encoder.encoder; + bridge = &dp->bridge_list[i]->bridge; + next_bridge = dp->next_bridge_list[i]; + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder); + if (ret) + goto out; + } + + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); + for (i = 0; i < dp->ports; i++) { port = dp->port[i]; @@ -1043,7 +1250,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) if (ret) { DRM_DEV_ERROR(dev, "register EXTCON_DISP_DP notifier err\n"); - return ret; + goto out; } } @@ -1052,15 +1259,29 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) schedule_work(&dp->event_work); return 0; + +out: + for (i = 0; i < MAX_PHY; i++) + drm_bridge_put(dp->next_bridge_list[i]); + + return ret; } static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dp->encoder.encoder; + struct drm_encoder *encoder; + int i; cancel_work_sync(&dp->event_work); - encoder->funcs->destroy(encoder); + for (i = 0; i < dp->bridge_count; i++) { + encoder = &dp->bridge_list[i]->encoder.encoder; + encoder->funcs->destroy(encoder); + drm_bridge_put(dp->next_bridge_list[i]); + } + + for (i = 0; i < MAX_PHY; i++) + drm_edid_free(dp->edid_cache[i]); pm_runtime_disable(dev); if (dp->fw_loaded) @@ -1111,10 +1332,10 @@ static int cdn_dp_probe(struct platform_device *pdev) int ret; int i; - dp = devm_drm_bridge_alloc(dev, struct cdn_dp_device, bridge, - &cdn_dp_bridge_funcs); - if (IS_ERR(dp)) - return PTR_ERR(dp); + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + dp->dev = dev; match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node); diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h index e9c30b9fd543..c10e423bbf06 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.h +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h @@ -38,6 +38,8 @@ enum vic_pxl_encoding_format { Y_ONLY = 0x10, }; +struct cdn_dp_device; + struct video_info { bool h_sync_polarity; bool v_sync_polarity; @@ -63,16 +65,28 @@ struct cdn_dp_port { u8 id; }; +struct cdn_dp_bridge { + struct cdn_dp_device *parent; + struct drm_bridge bridge; + struct rockchip_encoder encoder; + bool connected; + bool enabled; + int id; +}; + struct cdn_dp_device { struct device *dev; struct drm_device *drm_dev; - struct drm_bridge bridge; - struct rockchip_encoder encoder; + int bridge_count; + struct cdn_dp_bridge *bridge_list[MAX_PHY]; + struct drm_bridge *next_bridge_list[MAX_PHY]; + const struct drm_edid *edid_cache[MAX_PHY]; struct drm_display_mode mode; struct platform_device *audio_pdev; struct work_struct event_work; struct mutex lock; + bool next_bridge_valid; bool connected; bool active; bool suspended; -- 2.53.0 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection @ 2026-07-17 7:23 ` Chaoyi Chen 0 siblings, 0 replies; 28+ messages in thread From: Chaoyi Chen @ 2026-07-17 7:23 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Sandy Huang, Heiko Stübner, Andy Yan, Vinod Koul, Chaoyi Chen, Sebastian Reichel, Nicolas Frattaroli Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip, linux-phy From: Chaoyi Chen <chaoyi.chen@rock-chips.com> The RK3399 has two USB/DP combo PHY and one CDN-DP controller. And the CDN-DP can be switched to output to one of the PHYs. If both ports are plugged into DP, DP will select the first port for output. This patch adds support for multiple bridges, enabling users to flexibly select the output port. For each PHY port, a separate encoder and bridge are registered. The change is based on the DRM AUX HPD bridge, rather than the extcon approach. This requires the DT to correctly describe the connections between the first bridge in bridge chain and DP controller. For example, the bridge chain may be like this: PHY aux birdge -> fsa4480 analog audio switch bridge -> onnn,nb7vpq904m USB reminder bridge -> USB-C controller AUX HPD bridge In this case, the connection relationships among the PHY aux bridge and the DP contorller need to be described in DT. In addition, the cdn_dp_parse_next_bridge_dt() will parses it and determines whether to register one or two bridges. Since there is only one DP controller, only one of the PHY ports can output at a time. The key is how to switch between different PHYs, which is handled by cdn_dp_switch_port() and cdn_dp_enable(). There are two cases: 1. Neither bridge is enabled. In this case, both bridges can independently read the EDID, and the PHY port may switch before reading the EDID. 2. One bridge is already enabled. In this case, other bridges are not allowed to read the EDID. So we will try to return the cached EDID. Since the scenario of two ports plug in at the same time is rare, I don't have a board which support two TypeC connector to test this. Therefore, I tested forced switching on a single PHY port, as well as output using a fake PHY port alongside a real PHY port. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> --- Changes in v3: - Remove now-redundant call to drm_connector_attach_encoder() Changes in v2: - Fix the check logic for dp->active_port (0 is valid). - Fix the uninitialized prev_port. - Remove duplicate logic from cdn_dp_switch_port(). - Fix the prototype of cdn_dp_bridge_hpd_notify(). - Properly release connector->fwnode. - Properly release next_bridge. --- drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/rockchip/cdn-dp-core.c | 333 ++++++++++++++++++++----- drivers/gpu/drm/rockchip/cdn-dp-core.h | 18 +- 3 files changed, 294 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index e7f49fe845ea..ce70f91c13e6 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -60,6 +60,7 @@ config ROCKCHIP_CDN_DP select DRM_DISPLAY_HELPER select DRM_BRIDGE_CONNECTOR select DRM_DISPLAY_DP_HELPER + select DRM_AUX_HPD_BRIDGE help This selects support for Rockchip SoC specific extensions for the cdn DP driver. If you want to enable Dp on diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 74e1f5422a6e..5e513f6930cb 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -28,16 +28,17 @@ #include "cdn-dp-core.h" #include "cdn-dp-reg.h" -static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge) +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, + struct cdn_dp_port *port); + +static inline struct cdn_dp_bridge *bridge_to_dp_bridge(struct drm_bridge *bridge) { - return container_of(bridge, struct cdn_dp_device, bridge); + return container_of(bridge, struct cdn_dp_bridge, bridge); } -static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder) +static inline struct cdn_dp_device *bridge_to_dp(struct drm_bridge *bridge) { - struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); - - return container_of(rkencoder, struct cdn_dp_device, encoder); + return bridge_to_dp_bridge(bridge)->parent; } #define GRF_SOC_CON9 0x6224 @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count) static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp) { struct cdn_dp_port *port; - int i, lanes; + int i, lanes[MAX_PHY]; for (i = 0; i < dp->ports; i++) { port = dp->port[i]; - lanes = cdn_dp_get_port_lanes(port); - if (lanes) + lanes[i] = cdn_dp_get_port_lanes(port); + if (!dp->next_bridge_valid) return port; } + + if (dp->next_bridge_valid) { + /* If more than one port is available, pick the last active port */ + if (dp->active_port >= 0 && lanes[dp->active_port]) + return dp->port[dp->active_port]; + + /* If the last active port is not available, pick an available port in order */ + for (i = 0; i < dp->bridge_count; i++) { + if (lanes[i]) + return dp->port[i]; + } + } + return NULL; } @@ -254,12 +268,45 @@ static const struct drm_edid * cdn_dp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { struct cdn_dp_device *dp = bridge_to_dp(bridge); - const struct drm_edid *drm_edid; + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); + struct cdn_dp_port *port = dp->port[dp_bridge->id]; + struct cdn_dp_port *prev_port = NULL; + const struct drm_edid *drm_edid = NULL; + int i, ret; mutex_lock(&dp->lock); + + /* More than one port is available */ + if (dp->bridge_count > 1 && !port->phy_enabled) { + for (i = 0; i < dp->bridge_count; i++) { + /* Another port already enable */ + if (dp->bridge_list[i] != dp_bridge && dp->bridge_list[i]->enabled) + goto get_cache; + /* Find already enabled port */ + if (dp->port[i]->phy_enabled) + prev_port = dp->port[i]; + } + + /* Switch to current port */ + if (prev_port) { + ret = cdn_dp_switch_port(dp, prev_port, port); + if (ret) + goto get_cache; + } + } + drm_edid = drm_edid_read_custom(connector, cdn_dp_get_edid_block, dp); + /* replace edid cache */ + if (dp->edid_cache[dp_bridge->id]) + drm_edid_free(dp->edid_cache[dp_bridge->id]); + dp->edid_cache[dp_bridge->id] = drm_edid_dup(drm_edid); + mutex_unlock(&dp->lock); + return drm_edid; +get_cache: + drm_edid = drm_edid_dup(dp->edid_cache[dp_bridge->id]); + mutex_unlock(&dp->lock); return drm_edid; } @@ -268,12 +315,13 @@ cdn_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *display_info, const struct drm_display_mode *mode) { + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); struct cdn_dp_device *dp = bridge_to_dp(bridge); u32 requested, actual, rate, sink_max, source_max = 0; u8 lanes, bpc; /* If DP is disconnected, every mode is invalid */ - if (!dp->connected) + if (!dp_bridge->connected || !dp->connected) return MODE_BAD; switch (display_info->bpc) { @@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp) return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes)); } +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, + struct cdn_dp_port *port) +{ + int ret; + + ret = cdn_dp_disable_phy(dp, prev_port); + if (ret) + goto out; + ret = cdn_dp_enable_phy(dp, port); + if (ret) + goto out; + + ret = cdn_dp_get_sink_capability(dp); + if (ret) { + cdn_dp_disable_phy(dp, port); + goto out; + } + + dp->lanes = port->lanes; + + if (!cdn_dp_check_link_status(dp)) { + dev_info(dp->dev, "Connected with sink; re-train link\n"); + + ret = cdn_dp_train_link(dp); + if (ret) { + dev_err(dp->dev, "Training link failed: %d\n", ret); + goto out; + } + + ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE); + if (ret) { + dev_err(dp->dev, "Failed to idle video %d\n", ret); + goto out; + } + + ret = cdn_dp_config_video(dp); + if (ret) + dev_err(dp->dev, "Failed to configure video: %d\n", ret); + } + +out: + return ret; +} + static void cdn_dp_display_info_update(struct cdn_dp_device *dp, struct drm_display_info *display_info) { @@ -572,6 +664,7 @@ static void cdn_dp_display_info_update(struct cdn_dp_device *dp, static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct cdn_dp_device *dp = bridge_to_dp(bridge); + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); struct drm_connector *connector; int ret, val; @@ -581,7 +674,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at cdn_dp_display_info_update(dp, &connector->display_info); - ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp->encoder.encoder); + ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, &dp_bridge->encoder.encoder); if (ret < 0) { DRM_DEV_ERROR(dp->dev, "Could not get vop id, %d", ret); return; @@ -600,6 +693,9 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at mutex_lock(&dp->lock); + if (dp->next_bridge_valid) + dp->active_port = dp_bridge->id; + ret = cdn_dp_enable(dp); if (ret) { DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n", @@ -632,6 +728,7 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at goto out; } + dp_bridge->enabled = true; out: mutex_unlock(&dp->lock); } @@ -639,9 +736,11 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at static void cdn_dp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct cdn_dp_device *dp = bridge_to_dp(bridge); + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); int ret; mutex_lock(&dp->lock); + dp_bridge->enabled = false; if (dp->active) { ret = cdn_dp_disable(dp); @@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge, return ret; } +static void cdn_dp_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge); + struct cdn_dp_device *dp = bridge_to_dp(bridge); + + dp->bridge_list[dp_bridge->id]->connected = status == connector_status_connected; + schedule_work(&dp->event_work); +} + static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -838,6 +948,7 @@ static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { .atomic_disable = cdn_dp_bridge_atomic_disable, .mode_valid = cdn_dp_bridge_mode_valid, .mode_set = cdn_dp_bridge_mode_set, + .hpd_notify = cdn_dp_bridge_hpd_notify, .dp_audio_prepare = cdn_dp_audio_prepare, .dp_audio_mute_stream = cdn_dp_audio_mute_stream, @@ -886,7 +997,8 @@ static void cdn_dp_pd_event_work(struct work_struct *work) { struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device, event_work); - int ret; + bool connected; + int i, ret; mutex_lock(&dp->lock); @@ -945,9 +1057,12 @@ static void cdn_dp_pd_event_work(struct work_struct *work) out: mutex_unlock(&dp->lock); - drm_bridge_hpd_notify(&dp->bridge, - dp->connected ? connector_status_connected - : connector_status_disconnected); + for (i = 0; i < dp->bridge_count; i++) { + connected = dp->connected && dp->bridge_list[i]->connected; + drm_bridge_hpd_notify(&dp->bridge_list[i]->bridge, + connected ? connector_status_connected + : connector_status_disconnected); + } } static int cdn_dp_pd_event(struct notifier_block *nb, @@ -967,28 +1082,16 @@ static int cdn_dp_pd_event(struct notifier_block *nb, return NOTIFY_DONE; } -static int cdn_dp_bind(struct device *dev, struct device *master, void *data) +static int cdn_bridge_add(struct device *dev, + struct drm_bridge *bridge, + struct drm_bridge *next_bridge, + struct drm_encoder *encoder) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder; + struct drm_device *drm_dev = dp->drm_dev; + struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL; struct drm_connector *connector; - struct cdn_dp_port *port; - struct drm_device *drm_dev = data; - int ret, i; - - ret = cdn_dp_parse_dt(dp); - if (ret < 0) - return ret; - - dp->drm_dev = drm_dev; - dp->connected = false; - dp->active = false; - dp->active_port = -1; - dp->fw_loaded = false; - - INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); - - encoder = &dp->encoder.encoder; + int ret; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); @@ -1003,26 +1106,35 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs); - dp->bridge.ops = - DRM_BRIDGE_OP_DETECT | - DRM_BRIDGE_OP_EDID | - DRM_BRIDGE_OP_HPD | - DRM_BRIDGE_OP_DP_AUDIO; - dp->bridge.of_node = dp->dev->of_node; - dp->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; - dp->bridge.hdmi_audio_dev = dp->dev; - dp->bridge.hdmi_audio_max_i2s_playback_channels = 8; - dp->bridge.hdmi_audio_spdif_playback = 1; - dp->bridge.hdmi_audio_dai_port = -1; - - ret = devm_drm_bridge_add(dev, &dp->bridge); + bridge->ops = + DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_DP_AUDIO; + bridge->of_node = dp->dev->of_node; + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; + bridge->hdmi_audio_dev = dp->dev; + bridge->hdmi_audio_max_i2s_playback_channels = 8; + bridge->hdmi_audio_spdif_playback = 1; + bridge->hdmi_audio_dai_port = -1; + + ret = devm_drm_bridge_add(dev, bridge); if (ret) return ret; - ret = drm_bridge_attach(encoder, &dp->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return ret; + if (next_bridge) { + ret = drm_bridge_attach(encoder, next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + last_bridge = drm_bridge_chain_get_last_bridge(bridge->encoder); + } + connector = drm_bridge_connector_init(drm_dev, encoder); if (IS_ERR(connector)) { ret = PTR_ERR(connector); @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) return ret; } + if (last_bridge) { + fwnode_handle_put(connector->fwnode); + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node)); + } + + return 0; +} + +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp) +{ + struct device_node *np = dp->dev->of_node; + struct device_node *port __free(device_node) = of_graph_get_port_by_id(np, 1); + struct drm_bridge *bridge; + int count = 0; + int ret = 0; + int i; + + /* If device use extcon, do not use hpd bridge */ + for (i = 0; i < dp->ports; i++) { + if (dp->port[i]->extcon) { + dp->bridge_count = 1; + return 0; + } + } + + /* One endpoint may correspond to one next bridge. */ + for_each_of_graph_port_endpoint(port, dp_ep) { + struct device_node *next_bridge_node __free(device_node) = + of_graph_get_remote_port_parent(dp_ep); + + bridge = of_drm_find_bridge(next_bridge_node); + if (!bridge) { + ret = -EPROBE_DEFER; + goto out; + } + + dp->next_bridge_valid = true; + dp->next_bridge_list[count] = drm_bridge_get(bridge); + count++; + } + +out: + dp->bridge_count = count ? count : 1; + return ret; +} + +static int cdn_dp_bind(struct device *dev, struct device *master, void *data) +{ + struct cdn_dp_device *dp = dev_get_drvdata(dev); + struct drm_bridge *bridge, *next_bridge; + struct drm_encoder *encoder; + struct cdn_dp_port *port; + struct drm_device *drm_dev = data; + struct cdn_dp_bridge *dp_bridge; + int ret, i; + + ret = cdn_dp_parse_dt(dp); + if (ret < 0) + return ret; + + ret = cdn_dp_parse_next_bridge_dt(dp); + if (ret) + goto out; + + dp->drm_dev = drm_dev; + dp->connected = false; + dp->active = false; + dp->active_port = -1; + dp->fw_loaded = false; + + for (i = 0; i < dp->bridge_count; i++) { + dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge, + &cdn_dp_bridge_funcs); + if (IS_ERR(dp_bridge)) { + ret = PTR_ERR(dp_bridge); + goto out; + } + dp_bridge->id = i; + dp_bridge->parent = dp; + if (!dp->next_bridge_valid) + dp_bridge->connected = true; + dp->bridge_list[i] = dp_bridge; + } + + for (i = 0; i < dp->bridge_count; i++) { + encoder = &dp->bridge_list[i]->encoder.encoder; + bridge = &dp->bridge_list[i]->bridge; + next_bridge = dp->next_bridge_list[i]; + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder); + if (ret) + goto out; + } + + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); + for (i = 0; i < dp->ports; i++) { port = dp->port[i]; @@ -1043,7 +1250,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) if (ret) { DRM_DEV_ERROR(dev, "register EXTCON_DISP_DP notifier err\n"); - return ret; + goto out; } } @@ -1052,15 +1259,29 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) schedule_work(&dp->event_work); return 0; + +out: + for (i = 0; i < MAX_PHY; i++) + drm_bridge_put(dp->next_bridge_list[i]); + + return ret; } static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dp->encoder.encoder; + struct drm_encoder *encoder; + int i; cancel_work_sync(&dp->event_work); - encoder->funcs->destroy(encoder); + for (i = 0; i < dp->bridge_count; i++) { + encoder = &dp->bridge_list[i]->encoder.encoder; + encoder->funcs->destroy(encoder); + drm_bridge_put(dp->next_bridge_list[i]); + } + + for (i = 0; i < MAX_PHY; i++) + drm_edid_free(dp->edid_cache[i]); pm_runtime_disable(dev); if (dp->fw_loaded) @@ -1111,10 +1332,10 @@ static int cdn_dp_probe(struct platform_device *pdev) int ret; int i; - dp = devm_drm_bridge_alloc(dev, struct cdn_dp_device, bridge, - &cdn_dp_bridge_funcs); - if (IS_ERR(dp)) - return PTR_ERR(dp); + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + dp->dev = dev; match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node); diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h index e9c30b9fd543..c10e423bbf06 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.h +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h @@ -38,6 +38,8 @@ enum vic_pxl_encoding_format { Y_ONLY = 0x10, }; +struct cdn_dp_device; + struct video_info { bool h_sync_polarity; bool v_sync_polarity; @@ -63,16 +65,28 @@ struct cdn_dp_port { u8 id; }; +struct cdn_dp_bridge { + struct cdn_dp_device *parent; + struct drm_bridge bridge; + struct rockchip_encoder encoder; + bool connected; + bool enabled; + int id; +}; + struct cdn_dp_device { struct device *dev; struct drm_device *drm_dev; - struct drm_bridge bridge; - struct rockchip_encoder encoder; + int bridge_count; + struct cdn_dp_bridge *bridge_list[MAX_PHY]; + struct drm_bridge *next_bridge_list[MAX_PHY]; + const struct drm_edid *edid_cache[MAX_PHY]; struct drm_display_mode mode; struct platform_device *audio_pdev; struct work_struct event_work; struct mutex lock; + bool next_bridge_valid; bool connected; bool active; bool suspended; -- 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] 28+ messages in thread
* Re: [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection 2026-07-17 7:23 ` Chaoyi Chen @ 2026-07-17 7:38 ` sashiko-bot -1 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:38 UTC (permalink / raw) To: Chaoyi Chen; +Cc: dri-devel, olteanv, vkoul, linux-phy, neil.armstrong Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: New issues: - [High] Buffer overflow when parsing device tree endpoints due to missing bounds check. - [High] Broken hotplug detection for legacy extcon configurations due to unconditional port return. - [High] NULL pointer dereference and uninitialized memory read if DT endpoints exceed valid PHYs. - [High] Port switching during EDID read always fails because `cdn_dp_config_video` is called with a zero clock. - [High] Stale pointers in persistent arrays cause double-free and use-after-free on component re-bind. - [High] Workqueue `dp->event_work` is scheduled before being initialized, leading to a NULL pointer dereference. - [Medium] Multiple encoders share a single hardware DP controller but lack an `atomic_check` callback to prevent simultaneous activation. Pre-existing issues: - [High] Using `devm_` APIs in the component `bind` callback causes severe resource leaks and active stale notifiers after `unbind`. -- --- Patch [5]: [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection --- commit a0d8b7a8a3a01d68ddf971ecfdb92ccb11c60b09 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection This patch adds support for multiple bridges to allow selecting between different PHY ports on the RK3399 CDN-DP controller. It registers a separate encoder and bridge for each PHY port and parses the DT to handle bridge chain configurations. > diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c > index 74e1f5422a6e2..5e513f6930cb9 100644 > --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c > +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c [ ... ] > @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count) > static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp) > { > struct cdn_dp_port *port; > - int i, lanes; > + int i, lanes[MAX_PHY]; > > for (i = 0; i < dp->ports; i++) { > port = dp->port[i]; > - lanes = cdn_dp_get_port_lanes(port); > - if (lanes) > + lanes[i] = cdn_dp_get_port_lanes(port); > + if (!dp->next_bridge_valid) > return port; [Severity: High] Does this break hotplug detection for legacy extcon configurations? By removing the check for lanes > 0, this will unconditionally return the first port regardless of actual connection status. > } > + > + if (dp->next_bridge_valid) { > + /* If more than one port is available, pick the last active port */ > + if (dp->active_port >= 0 && lanes[dp->active_port]) > + return dp->port[dp->active_port]; > + > + /* If the last active port is not available, pick an available port in order */ > + for (i = 0; i < dp->bridge_count; i++) { > + if (lanes[i]) > + return dp->port[i]; > + } > + } [Severity: High] Could this lead to an uninitialized memory read and NULL pointer dereference? If the device tree provides more endpoints than probed PHYs, dp->bridge_count will exceed dp->ports. This loop will access uninitialized stack memory in lanes[i] and potentially return a NULL dp->port[i], causing a crash later in cdn_dp_bridge_edid_read() when checking port->phy_enabled. [ ... ] > @@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp) > +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, > + struct cdn_dp_port *port) > +{ [ ... ] > + if (!cdn_dp_check_link_status(dp)) { > + dev_info(dp->dev, "Connected with sink; re-train link\n"); > + > + ret = cdn_dp_train_link(dp); > + if (ret) { > + dev_err(dp->dev, "Training link failed: %d\n", ret); > + goto out; > + } > + > + ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE); > + if (ret) { > + dev_err(dp->dev, "Failed to idle video %d\n", ret); > + goto out; > + } > + > + ret = cdn_dp_config_video(dp); > + if (ret) > + dev_err(dp->dev, "Failed to configure video: %d\n", ret); > + } [Severity: High] Will this port switch fail consistently during EDID read operations? When cdn_dp_bridge_edid_read() calls this function, dp->mode.clock is typically 0. A zero clock will cause cdn_dp_config_video() to spin until timeout and return -EINVAL, thus aborting the EDID read. [ ... ] > @@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge, > static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { > .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > .atomic_reset = drm_atomic_helper_bridge_reset, > .detect = cdn_dp_bridge_detect, > .edid_read = cdn_dp_bridge_edid_read, > .atomic_enable = cdn_dp_bridge_atomic_enable, > .atomic_disable = cdn_dp_bridge_atomic_disable, > .mode_valid = cdn_dp_bridge_mode_valid, > .mode_set = cdn_dp_bridge_mode_set, > + .hpd_notify = cdn_dp_bridge_hpd_notify, [Severity: Medium] Should there be an atomic_check callback here to prevent simultaneous activation of multiple CRTCs? Since all registered encoders share the same possible_crtcs mask for a single underlying DP controller, userspace could potentially enable multiple displays at once, leading to hardware register corruption. [ ... ] > @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) > +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp) > +{ [ ... ] > + for_each_of_graph_port_endpoint(port, dp_ep) { > + struct device_node *next_bridge_node __free(device_node) = > + of_graph_get_remote_port_parent(dp_ep); > + > + bridge = of_drm_find_bridge(next_bridge_node); > + if (!bridge) { > + ret = -EPROBE_DEFER; > + goto out; > + } > + > + dp->next_bridge_valid = true; > + dp->next_bridge_list[count] = drm_bridge_get(bridge); > + count++; > + } [Severity: High] Could this code overflow the dp->next_bridge_list[] array? The count variable is incremented for each endpoint and used as an index, but it is never checked against MAX_PHY. [ ... ] > @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) > + for (i = 0; i < dp->bridge_count; i++) { > + dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge, > + &cdn_dp_bridge_funcs); [Severity: High] This is a pre-existing issue, but does using devm_ APIs inside a component bind callback cause memory leaks and use-after-free crashes? Component unbind does not release devm_ resources, meaning structures like dp_bridge and the extcon notifiers remain active after unbind and will leak on subsequent re-binds. [ ... ] > + for (i = 0; i < dp->bridge_count; i++) { > + encoder = &dp->bridge_list[i]->encoder.encoder; > + bridge = &dp->bridge_list[i]->bridge; > + next_bridge = dp->next_bridge_list[i]; > + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder); > + if (ret) > + goto out; > + } > + > + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); [Severity: High] Can this sequence cause a NULL pointer dereference? By registering the bridges via cdn_bridge_add() before calling INIT_WORK, an early HPD event can trigger cdn_dp_bridge_hpd_notify() and attempt to schedule the uninitialized dp->event_work queue. [ ... ] > +out: > + for (i = 0; i < MAX_PHY; i++) > + drm_bridge_put(dp->next_bridge_list[i]); > + > + return ret; > } > > static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) > { > struct cdn_dp_device *dp = dev_get_drvdata(dev); > - struct drm_encoder *encoder = &dp->encoder.encoder; > + struct drm_encoder *encoder; > + int i; > > cancel_work_sync(&dp->event_work); > - encoder->funcs->destroy(encoder); > + for (i = 0; i < dp->bridge_count; i++) { > + encoder = &dp->bridge_list[i]->encoder.encoder; > + encoder->funcs->destroy(encoder); > + drm_bridge_put(dp->next_bridge_list[i]); > + } > + > + for (i = 0; i < MAX_PHY; i++) > + drm_edid_free(dp->edid_cache[i]); [Severity: High] Does this leave dangling pointers in persistent arrays across bind/unbind cycles? Because dp is allocated in probe, freeing dp->next_bridge_list and dp->edid_cache here without setting them to NULL can lead to double-frees or use-after-free issues if the component is rebound. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=5 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection @ 2026-07-17 7:38 ` sashiko-bot 0 siblings, 0 replies; 28+ messages in thread From: sashiko-bot @ 2026-07-17 7:38 UTC (permalink / raw) To: Chaoyi Chen; +Cc: dri-devel, olteanv, vkoul, linux-phy, neil.armstrong Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: New issues: - [High] Buffer overflow when parsing device tree endpoints due to missing bounds check. - [High] Broken hotplug detection for legacy extcon configurations due to unconditional port return. - [High] NULL pointer dereference and uninitialized memory read if DT endpoints exceed valid PHYs. - [High] Port switching during EDID read always fails because `cdn_dp_config_video` is called with a zero clock. - [High] Stale pointers in persistent arrays cause double-free and use-after-free on component re-bind. - [High] Workqueue `dp->event_work` is scheduled before being initialized, leading to a NULL pointer dereference. - [Medium] Multiple encoders share a single hardware DP controller but lack an `atomic_check` callback to prevent simultaneous activation. Pre-existing issues: - [High] Using `devm_` APIs in the component `bind` callback causes severe resource leaks and active stale notifiers after `unbind`. -- --- Patch [5]: [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection --- commit a0d8b7a8a3a01d68ddf971ecfdb92ccb11c60b09 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection This patch adds support for multiple bridges to allow selecting between different PHY ports on the RK3399 CDN-DP controller. It registers a separate encoder and bridge for each PHY port and parses the DT to handle bridge chain configurations. > diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c > index 74e1f5422a6e2..5e513f6930cb9 100644 > --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c > +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c [ ... ] > @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count) > static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp) > { > struct cdn_dp_port *port; > - int i, lanes; > + int i, lanes[MAX_PHY]; > > for (i = 0; i < dp->ports; i++) { > port = dp->port[i]; > - lanes = cdn_dp_get_port_lanes(port); > - if (lanes) > + lanes[i] = cdn_dp_get_port_lanes(port); > + if (!dp->next_bridge_valid) > return port; [Severity: High] Does this break hotplug detection for legacy extcon configurations? By removing the check for lanes > 0, this will unconditionally return the first port regardless of actual connection status. > } > + > + if (dp->next_bridge_valid) { > + /* If more than one port is available, pick the last active port */ > + if (dp->active_port >= 0 && lanes[dp->active_port]) > + return dp->port[dp->active_port]; > + > + /* If the last active port is not available, pick an available port in order */ > + for (i = 0; i < dp->bridge_count; i++) { > + if (lanes[i]) > + return dp->port[i]; > + } > + } [Severity: High] Could this lead to an uninitialized memory read and NULL pointer dereference? If the device tree provides more endpoints than probed PHYs, dp->bridge_count will exceed dp->ports. This loop will access uninitialized stack memory in lanes[i] and potentially return a NULL dp->port[i], causing a crash later in cdn_dp_bridge_edid_read() when checking port->phy_enabled. [ ... ] > @@ -551,6 +599,50 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp) > +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port, > + struct cdn_dp_port *port) > +{ [ ... ] > + if (!cdn_dp_check_link_status(dp)) { > + dev_info(dp->dev, "Connected with sink; re-train link\n"); > + > + ret = cdn_dp_train_link(dp); > + if (ret) { > + dev_err(dp->dev, "Training link failed: %d\n", ret); > + goto out; > + } > + > + ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE); > + if (ret) { > + dev_err(dp->dev, "Failed to idle video %d\n", ret); > + goto out; > + } > + > + ret = cdn_dp_config_video(dp); > + if (ret) > + dev_err(dp->dev, "Failed to configure video: %d\n", ret); > + } [Severity: High] Will this port switch fail consistently during EDID read operations? When cdn_dp_bridge_edid_read() calls this function, dp->mode.clock is typically 0. A zero clock will cause cdn_dp_config_video() to spin until timeout and return -EINVAL, thus aborting the EDID read. [ ... ] > @@ -828,6 +927,17 @@ static int cdn_dp_audio_mute_stream(struct drm_bridge *bridge, > static const struct drm_bridge_funcs cdn_dp_bridge_funcs = { > .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > .atomic_reset = drm_atomic_helper_bridge_reset, > .detect = cdn_dp_bridge_detect, > .edid_read = cdn_dp_bridge_edid_read, > .atomic_enable = cdn_dp_bridge_atomic_enable, > .atomic_disable = cdn_dp_bridge_atomic_disable, > .mode_valid = cdn_dp_bridge_mode_valid, > .mode_set = cdn_dp_bridge_mode_set, > + .hpd_notify = cdn_dp_bridge_hpd_notify, [Severity: Medium] Should there be an atomic_check callback here to prevent simultaneous activation of multiple CRTCs? Since all registered encoders share the same possible_crtcs mask for a single underlying DP controller, userspace could potentially enable multiple displays at once, leading to hardware register corruption. [ ... ] > @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) > +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp) > +{ [ ... ] > + for_each_of_graph_port_endpoint(port, dp_ep) { > + struct device_node *next_bridge_node __free(device_node) = > + of_graph_get_remote_port_parent(dp_ep); > + > + bridge = of_drm_find_bridge(next_bridge_node); > + if (!bridge) { > + ret = -EPROBE_DEFER; > + goto out; > + } > + > + dp->next_bridge_valid = true; > + dp->next_bridge_list[count] = drm_bridge_get(bridge); > + count++; > + } [Severity: High] Could this code overflow the dp->next_bridge_list[] array? The count variable is incremented for each endpoint and used as an index, but it is never checked against MAX_PHY. [ ... ] > @@ -1030,6 +1142,101 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) > + for (i = 0; i < dp->bridge_count; i++) { > + dp_bridge = devm_drm_bridge_alloc(dev, struct cdn_dp_bridge, bridge, > + &cdn_dp_bridge_funcs); [Severity: High] This is a pre-existing issue, but does using devm_ APIs inside a component bind callback cause memory leaks and use-after-free crashes? Component unbind does not release devm_ resources, meaning structures like dp_bridge and the extcon notifiers remain active after unbind and will leak on subsequent re-binds. [ ... ] > + for (i = 0; i < dp->bridge_count; i++) { > + encoder = &dp->bridge_list[i]->encoder.encoder; > + bridge = &dp->bridge_list[i]->bridge; > + next_bridge = dp->next_bridge_list[i]; > + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder); > + if (ret) > + goto out; > + } > + > + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); [Severity: High] Can this sequence cause a NULL pointer dereference? By registering the bridges via cdn_bridge_add() before calling INIT_WORK, an early HPD event can trigger cdn_dp_bridge_hpd_notify() and attempt to schedule the uninitialized dp->event_work queue. [ ... ] > +out: > + for (i = 0; i < MAX_PHY; i++) > + drm_bridge_put(dp->next_bridge_list[i]); > + > + return ret; > } > > static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) > { > struct cdn_dp_device *dp = dev_get_drvdata(dev); > - struct drm_encoder *encoder = &dp->encoder.encoder; > + struct drm_encoder *encoder; > + int i; > > cancel_work_sync(&dp->event_work); > - encoder->funcs->destroy(encoder); > + for (i = 0; i < dp->bridge_count; i++) { > + encoder = &dp->bridge_list[i]->encoder.encoder; > + encoder->funcs->destroy(encoder); > + drm_bridge_put(dp->next_bridge_list[i]); > + } > + > + for (i = 0; i < MAX_PHY; i++) > + drm_edid_free(dp->edid_cache[i]); [Severity: High] Does this leave dangling pointers in persistent arrays across bind/unbind cycles? Because dp is allocated in probe, freeing dp->next_bridge_list and dp->edid_cache here without setting them to NULL can lead to double-frees or use-after-free issues if the component is rebound. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260717072323.96-1-kernel@airkyi.com?part=5 ^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-07-17 7:56 UTC | newest] Thread overview: 28+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-17 7:23 [PATCH v3 0/5] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` [PATCH v3 1/5] " Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:36 ` sashiko-bot 2026-07-17 7:36 ` sashiko-bot 2026-07-17 7:23 ` [PATCH v3 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:33 ` sashiko-bot 2026-07-17 7:33 ` sashiko-bot 2026-07-17 7:23 ` [PATCH v3 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:39 ` sashiko-bot 2026-07-17 7:39 ` sashiko-bot 2026-07-17 7:23 ` [PATCH v3 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:56 ` sashiko-bot 2026-07-17 7:56 ` sashiko-bot 2026-07-17 7:23 ` [PATCH v3 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:23 ` Chaoyi Chen 2026-07-17 7:38 ` sashiko-bot 2026-07-17 7:38 ` sashiko-bot
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.