* [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB
@ 2026-07-27 6:44 Chaoyi Chen
2026-07-27 6:44 ` [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Chaoyi Chen @ 2026-07-27 6:44 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,
Heikki Krogerus
Cc: linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
[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 v4:
- Link to v3: https://lore.kernel.org/all/20260717072323.96-1-kernel@airkyi.com/
- Scan the entire typec_bus and attempt to register the hpd bridge,
so as not to miss devices that were already added during initialization.
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 | 64 ++++
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, 428 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] 11+ messages in thread
* [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
2026-07-27 6:44 [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB Chaoyi Chen
@ 2026-07-27 6:44 ` Chaoyi Chen
2026-07-27 6:58 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Chaoyi Chen @ 2026-07-27 6:44 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,
Heikki Krogerus
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>
---
Changes in v4:
- Scan the entire typec_bus and attempt to register the hpd bridge,
so as not to miss devices that were already added during initialization.
(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 | 64 +++++++++++++++++++
3 files changed, 75 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..43af3ea20f20
--- /dev/null
+++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
@@ -0,0 +1,64 @@
+// 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 int check_device_already_added(struct device *dev, void *data)
+{
+ drm_typec_bus_event(NULL, BUS_NOTIFY_ADD_DEVICE, dev);
+ return 0;
+}
+
+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);
+ /*
+ * Before module initialization, some devices may have already been added.
+ * Register the HPD bridge for these devices.
+ */
+ bus_for_each_dev(&typec_bus, NULL, NULL, check_device_already_added);
+ 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] 11+ messages in thread
* [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
2026-07-27 6:44 [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB Chaoyi Chen
2026-07-27 6:44 ` [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
@ 2026-07-27 6:44 ` Chaoyi Chen
2026-07-27 6:53 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
` (2 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Chaoyi Chen @ 2026-07-27 6:44 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,
Heikki Krogerus
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] 11+ messages in thread
* [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
2026-07-27 6:44 [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB Chaoyi Chen
2026-07-27 6:44 ` [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-07-27 6:44 ` [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
@ 2026-07-27 6:44 ` Chaoyi Chen
2026-07-27 7:01 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
2026-07-27 6:44 ` [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
4 siblings, 1 reply; 11+ messages in thread
From: Chaoyi Chen @ 2026-07-27 6:44 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,
Heikki Krogerus
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] 11+ messages in thread
* [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon
2026-07-27 6:44 [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB Chaoyi Chen
` (2 preceding siblings ...)
2026-07-27 6:44 ` [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
@ 2026-07-27 6:44 ` Chaoyi Chen
2026-07-27 6:55 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
4 siblings, 1 reply; 11+ messages in thread
From: Chaoyi Chen @ 2026-07-27 6:44 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,
Heikki Krogerus
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] 11+ messages in thread
* [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
2026-07-27 6:44 [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB Chaoyi Chen
` (3 preceding siblings ...)
2026-07-27 6:44 ` [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
@ 2026-07-27 6:44 ` Chaoyi Chen
2026-07-27 6:59 ` sashiko-bot
4 siblings, 1 reply; 11+ messages in thread
From: Chaoyi Chen @ 2026-07-27 6:44 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,
Heikki Krogerus
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>
---
(no changes since v4)
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] 11+ messages in thread
* Re: [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
2026-07-27 6:44 ` [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
@ 2026-07-27 6:53 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-27 6:53 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: dri-devel, olteanv, linux-phy, vkoul, neil.armstrong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing of_node_get() when setting the device node from `np` in `drm_aux_bridge_register_from_node()` leads to a reference count underflow, as cleanup and release paths unconditionally drop the reference.
--
--- Patch [2]: [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() ---
commit d7b1eb76e37cb91bfdbe4b86349165417e109776
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/bridge: aux: Add drm_aux_bridge_register_from_node()
This commit introduces drm_aux_bridge_register_from_node() to allow
specifying the of_node corresponding to the bridge when registering
an auxiliary bridge device.
> diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c
> index 169939d233102..2b48612d6d1ba 100644
> --- a/drivers/gpu/drm/bridge/aux-bridge.c
> +++ b/drivers/gpu/drm/bridge/aux-bridge.c
[ ... ]
> @@ -63,7 +64,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]
When np is provided, device_set_node() is used. Unlike
device_set_of_node_from_dev(), device_set_node() does not appear to
increment the reference count of the target node.
> + else
> + device_set_of_node_from_dev(&adev->dev, parent);
>
> ret = auxiliary_device_init(adev);
> if (ret) {
Could this lead to a reference count underflow for the provided np?
The error path here unconditionally calls of_node_put(adev->dev.of_node).
Additionally, the drm_aux_bridge_release() callback also unconditionally
drops a reference to the device's of_node when the device is destroyed.
Since device_set_node() doesn't take an initial reference on np, will
these put operations result in a use-after-free for the device tree node?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727064408.101-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] 11+ messages in thread
* Re: [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon
2026-07-27 6:44 ` [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
@ 2026-07-27 6:55 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-27 6:55 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: vkoul, neil.armstrong, dri-devel, linux-phy, olteanv
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The error-handling condition `PTR_ERR(extcon) != -ENODEV` incorrectly evaluates to true for all valid extcon pointers, completely skipping port initialization for platforms with an extcon.
Pre-existing issues:
- [High] The driver incorrectly uses `devm_` APIs inside a component `bind` function, leading to lingering notifiers and use-after-free upon component unbind.
- [High] `cdn_dp_pd_event_work` drops and reacquires a lock during execution but fails to re-evaluate the hardware's suspend state, potentially accessing powered-down hardware.
--
--- Patch [4]: [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon ---
commit 7d9954b4a176d603a3c1df5fb3c1b4c9ddaa5b21
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.
If a Type-C controller is present, the lane info can be obtained from the PHY
instead of extcon. The extcon device is still 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 68aa66915e8ac..1c1c1116b5ebe 100644
> --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
[ ... ]
> @@ -1030,6 +1036,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,
> &port->event_nb);
[Severity: High]
This isn't a bug introduced by this patch, but does using devm_ APIs in a
component bind function lead to lingering notifiers?
When the DRM master is unbound, cdn_dp_unbind() is called. However, resources
allocated with devm_ (like this extcon notifier and the DRM bridge) are tied
to the platform device's lifetime, not the component.
If a user unbinds the DRM master device via sysfs, and then plugs or unplugs
the DisplayPort cable, could the active notifier fire and schedule the driver's
workqueue on destroyed state, causing a use-after-free?
[ ... ]
> @@ -1122,14 +1131,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)
[Severity: High]
Does this error-handling condition evaluate to true for all valid extcon
pointers?
When the driver probes on a platform with an extcon,
extcon_get_edev_by_phandle() returns a valid pointer. This condition then
checks if the valid pointer's address is not equal to -ENODEV, which is always
true.
Could this skip port initialization for all valid extcon devices, leaving
dp->ports = 0 and causing the driver to unconditionally fail with -EINVAL?
> continue;
>
> port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
[Severity: High]
This is a pre-existing issue, but is there a race condition between the
workqueue and system suspend?
In cdn_dp_pd_event_work(), dp->lock is held and dp->suspended is verified.
Then, cdn_dp_request_firmware() is called:
drivers/gpu/drm/rockchip/cdn-dp-core.c:cdn_dp_request_firmware() {
...
/* Drop the lock before getting the firmware to avoid blocking boot */
mutex_unlock(&dp->lock);
...
}
During this window, if the system suspends, cdn_dp_suspend() acquires
dp->lock, sets dp->suspended = true, and powers off the hardware. When
cdn_dp_request_firmware() finishes, it reacquires the lock and returns to
cdn_dp_pd_event_work().
Execution then resumes without rechecking if the system suspended in the
meantime:
drivers/gpu/drm/rockchip/cdn-dp-core.c:cdn_dp_pd_event_work() {
...
ret = cdn_dp_request_firmware(dp);
if (ret)
goto out;
dp->connected = true;
if (!cdn_dp_connected_port(dp)) {
...
} else if (!dp->active) {
...
ret = cdn_dp_enable(dp);
...
}
Could this cause the worker thread to call cdn_dp_enable() and access hardware
registers that have been powered down?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727064408.101-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] 11+ messages in thread
* Re: [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge
2026-07-27 6:44 ` [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
@ 2026-07-27 6:58 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-27 6:58 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: olteanv, linux-phy, dri-devel, neil.armstrong, vkoul
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Module load/unload cycles and dynamic Type-C port removals leak devm-managed bridges, resulting in duplicates and potential UAF.
- [High] A race condition between bus notifier registration and bus iteration causes duplicate DP HPD bridge registrations.
- [High] Unconditional bridge registration creates duplicate bridges for controllers that already register their own HPD bridge.
--
--- Patch [1]: [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge ---
commit 8ca96a8d5692c2b3ff148f7a6803323281488779
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/bridge: Implement generic USB Type-C DP HPD bridge
This commit 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
> index 0000000000000..43af3ea20f201
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
[ ... ]
> +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;
[Severity: High]
Does this code leak devm-managed bridges when a Type-C altmode device is
dynamically removed?
It looks like BUS_NOTIFY_DEL_DEVICE is not handled here, so the bridge might
linger and pin of_node references indefinitely after port removal.
> +
> + /*
> + * 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]
What happens if the USB-C controller driver (like fusb302 or qcom_pmic_typec)
has already explicitly registered its own HPD bridge?
Will this unconditional registration create a second duplicate bridge? If so,
could this break of_drm_find_bridge() resolution, causing the display driver
to miss hotplug events fired on the controller's own bridge?
Additionally, because drm_dp_hpd_bridge_register() ties the devm allocation
to the external USB-C controller (alt->dev.parent->parent), will unloading
this module leave those devres allocations behind? Reloading the module might
then register duplicates.
> +
> + return NOTIFY_OK;
> +}
[ ... ]
> +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void)
> +{
> + bus_register_notifier(&typec_bus, &drm_typec_event_nb);
> + /*
> + * Before module initialization, some devices may have already been added.
> + * Register the HPD bridge for these devices.
> + */
> + bus_for_each_dev(&typec_bus, NULL, NULL, check_device_already_added);
[Severity: High]
Is there a race condition between the notifier registration and the bus
iteration here?
If a Type-C altmode device is added concurrently (for example, via an async
probe of the controller) right after bus_register_notifier() but before or
during the bus_for_each_dev() iteration, could it trigger both the notifier
and the check_device_already_added callback?
Would this result in duplicate HPD bridges being registered for a single
Type-C port?
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727064408.101-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] 11+ messages in thread
* Re: [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
2026-07-27 6:44 ` [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
@ 2026-07-27 6:59 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-27 6:59 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: neil.armstrong, vkoul, olteanv, linux-phy, dri-devel
Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
New issues:
- [High] `cdn_dp_connected_port` unconditionally returns the first port on legacy or extcon devices, breaking hotplug detection.
- [High] The driver erroneously skips valid `extcon` devices during probe due to a flawed error pointer check.
- [High] `cdn_dp_switch_port` performs full video configuration during an EDID read, breaking port switching and EDID detection.
- [High] Multiple encoders are registered without an `atomic_check` callback to enforce mutual exclusion, enabling hardware corruption.
- [High] Dangling pointers in `dp->next_bridge_list` cause a double-put Use-After-Free during component rebinds or deferred probes.
- [High] Dangling pointers in `dp->edid_cache` cause a double-free memory corruption during component rebinds.
- [High] Lack of bounds validation between DT endpoints (`bridge_count`) and probed PHYs (`ports`) leads to NULL pointer dereferences and uninitialized memory access.
- [High] `cdn_dp_bridge_atomic_enable` bypasses PHY switching if the CDN-DP controller is already active.
- [High] Failed PHY switching in `cdn_dp_switch_port` leaves the device in an inconsistent state.
- [Medium] Out-of-bounds write in `cdn_dp_parse_next_bridge_dt` when parsing device tree endpoints.
- [Medium] Data race on the `connected` flag of the bridges.
Pre-existing issues:
- [Critical] `devm_` allocations inside the component `bind` callback cause resource leaks, duplicate registrations, and Use-After-Free vulnerabilities.
--
--- Patch [5]: [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection ---
commit ae89dd4402c4e98ab965c5bd29f2eb19fa5d48bc
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
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.
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> index 1c1c1116b5ebe..251e76692eb63 100644
> --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
[ ... ]
> @@ -191,14 +192,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]
What happens here for legacy or extcon devices where `next_bridge_valid` is
false?
If `!dp->next_bridge_valid` evaluates to true, the loop immediately returns
`dp->port[0]` on the very first iteration. This seems to completely ignore
the physical connection state in `lanes[i]`, always falsely reporting the port
as connected, which could break DP hotplug detection entirely.
> }
> +
> + if (dp->next_bridge_valid) {
[ ... ]
> @@ -253,12 +267,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];
[Severity: High]
Is it possible for a device tree to define more endpoints than probed PHYs?
There doesn't seem to be a bounds check ensuring `dp->bridge_count` is less
than or equal to `dp->ports`. If a misconfiguration causes `dp_bridge->id`
to index beyond `dp->ports`, `dp->port[dp_bridge->id]` would return NULL.
This would cause a kernel panic when checking `!port->phy_enabled` below.
> + 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;
> + }
> + }
[Severity: High]
Are we trying to perform full video configuration during an EDID read?
When switching the PHY, `cdn_dp_switch_port` internally calls
`cdn_dp_config_video(dp)`. However, during an EDID read operation, no display
mode is committed yet, meaning `mode->clock` may be 0. Since
`cdn_dp_config_video` uses `mode->clock` in a timing loop, this will fail.
This forces `cdn_dp_switch_port` to fail, resulting in a fallback to a
stale or NULL EDID cache, completely breaking output detection on secondary
ports.
> +
> 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]);
[Severity: High]
Could this result in a double-free on a dangling pointer?
In `cdn_dp_unbind()`, the cached EDIDs are freed but the pointers in
`dp->edid_cache` are not set to NULL. Since the `dp` context survives a
component unbind/rebind cycle, if the component is rebound, this check will
evaluate to true and attempt to free the dangling pointer again.
> + dp->edid_cache[dp_bridge->id] = drm_edid_dup(drm_edid);
> +
> mutex_unlock(&dp->lock);
> + return drm_edid;
[ ... ]
> @@ -550,6 +598,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;
> + }
[Severity: High]
Does this error path leave the driver in an inconsistent state?
If `cdn_dp_get_sink_capability` fails, the code disables both the previous
and target PHYs and returns an error. However, it fails to update the
controller's internal state machine, leaving `dp->active` as true.
Because `dp->active` is true but no PHY is actually enabled, any subsequent
hardware operations will assume the hardware is active, potentially leading
to bus errors or hardware timeouts.
> +
> + dp->lanes = port->lanes;
> +
> + if (!cdn_dp_check_link_status(dp)) {
[ ... ]
> @@ -599,6 +692,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);
[Severity: High]
Will `cdn_dp_enable` actually switch the PHY if one is already active?
The driver sets `dp->active_port = dp_bridge->id` and calls `cdn_dp_enable(dp)`.
However, `cdn_dp_enable` contains an early return if `dp->active` is already
true.
If Port A is active and an atomic commit requests enabling Port B,
`cdn_dp_enable` will return immediately without powering on Port B's PHY.
Subsequent video configuration and link training commands will then be sent
while the target PHY is powered off.
> if (ret) {
> DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n",
[ ... ]
> @@ -831,6 +930,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;
[Severity: Medium]
Could this assignment cause a data race?
This writes to `dp->bridge_list[dp_bridge->id]->connected` without holding
`dp->lock`. Concurrently, this flag is read by `cdn_dp_pd_event_work` inside
a loop (after releasing `dp->lock`), and by `cdn_dp_bridge_mode_valid`
without holding any locks. This unsynchronized access might lead to stale reads
across cores.
> + 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,
> @@ -841,6 +951,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,
[Severity: High]
Should this bridge implement an `atomic_check` callback to enforce mutual
exclusion?
The RK3399 CDN-DP controller can only drive one port at a time. By registering
multiple encoders without an `atomic_check` to prevent both outputs from being
enabled simultaneously, userspace could commit an atomic state that enables both.
This would cause concurrent calls to `cdn_dp_bridge_atomic_enable`, overwriting
shared registers and leading to hardware corruption or kernel timeouts.
>
> .dp_audio_prepare = cdn_dp_audio_prepare,
> .dp_audio_mute_stream = cdn_dp_audio_mute_stream,
[ ... ]
> @@ -1033,6 +1145,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++;
[Severity: Medium]
What prevents an out-of-bounds write here if there are more than 2 endpoints?
The `dp->next_bridge_list` array is statically sized to `MAX_PHY` (2). The loop
blindly increments `count` and writes to the array without bounds checking. A
misconfigured device tree could easily overflow this array.
> + }
> +
> +out:
> + dp->bridge_count = count ? count : 1;
> + return ret;
> +}
> +
> +static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
> +{
[ ... ]
> +
> + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
> +
> for (i = 0; i < dp->ports; i++) {
> port = dp->port[i];
>
> @@ -1046,7 +1253,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;
> }
> }
[Severity: Critical]
This is a pre-existing issue, but won't these `devm_` allocations cause memory
leaks and Use-After-Free during component unbind?
The driver uses `devm_drm_bridge_alloc`, `devm_drm_bridge_add`, and
`devm_extcon_register_notifier` inside `cdn_dp_bind`. These tie resources to
the platform device (`dp->dev`), not the component lifecycle.
Upon unbind, these allocations aren't freed, leaking memory on every rebind.
Critically, the extcon notifier is never unregistered. If an event fires after
unbind, `cdn_dp_pd_event_work` is executed, discovering `dp->fw_loaded == true`
and attempting to dereference `dp->fw->data` which was released in `unbind`,
leading to a kernel panic.
>
> @@ -1055,15 +1262,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]);
[Severity: High]
Does leaving these pointers in the array cause a Use-After-Free on rebind?
The array elements aren't set to NULL after calling `drm_bridge_put()`. Since
`dp` persists across component bind/unbind cycles, a subsequent `bind` failure
would iterate over this array and put the dangling pointers a second time,
corrupting the bridge refcounts.
> +
> + 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]
Same as above, should `dp->edid_cache[i]` and `dp->next_bridge_list[i]` be
cleared to NULL after freeing?
These dangling pointers will cause double-frees during a subsequent bind cycle
or EDID read if the component is rebound.
>
> pm_runtime_disable(dev);
> if (dp->fw_loaded)
> @@ -1114,10 +1335,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);
[Severity: High]
This isn't a bug introduced by this patch, but does the probe fail to
recognize valid extcon devices?
Just below this in `cdn_dp_probe`, there is a check:
if (IS_ERR(phy) || PTR_ERR(extcon) != -ENODEV)
continue;
If `extcon_get_edev_by_phandle()` successfully returns a valid pointer, its
address is evaluated against `-ENODEV`, which is always true. This skips the
valid extcon port and leaves `dp->ports` as 0, failing the probe with `-EINVAL`.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727064408.101-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] 11+ messages in thread
* Re: [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
2026-07-27 6:44 ` [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
@ 2026-07-27 7:01 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-27 7:01 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: dri-devel, vkoul, neil.armstrong, linux-phy, olteanv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing of_node_get() causes refcount underflow (double-put) and potential use-after-free of the device node.
--
--- Patch [3]: [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge ---
commit 01ab2260169a9897f73395bd29f9342205424c05
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
This commit uses the DRM_AUX_BRIDGE helper to create a transparent DRM
bridge device for the display port on Rockchip Type-C PHYs.
> 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);
[Severity: High]
Does this code need to call of_node_get(child_np) before passing it to
drm_aux_bridge_register_from_node()?
The iterator macro for_each_available_child_of_node() holds a reference
during the loop that it automatically drops when advancing. However, the
auxiliary device created by drm_aux_bridge_register_from_node() will
unconditionally call of_node_put() in its release function.
If we don't increment the reference count here, couldn't this result in a
refcount underflow and use-after-free when the bridge is unregistered or
destroyed?
> + } 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);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727064408.101-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] 11+ messages in thread
end of thread, other threads:[~2026-07-27 7:01 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27 6:44 [PATCH v4 0/5] This series is split from the v15 "Add Type-C DP support for RK3399 EVB Chaoyi Chen
2026-07-27 6:44 ` [PATCH v4 1/5] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-07-27 6:58 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 2/5] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
2026-07-27 6:53 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 3/5] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
2026-07-27 7:01 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 4/5] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
2026-07-27 6:55 ` sashiko-bot
2026-07-27 6:44 ` [PATCH v4 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
2026-07-27 6:59 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox