* [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge
@ 2026-08-04 7:07 Chaoyi Chen
2026-08-04 7:07 ` [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge() Chaoyi Chen
` (6 more replies)
0 siblings, 7 replies; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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 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 new API to check whether a DisplayPort HPD bridge has
already been registered(Sebastian).
Patch 2 adds generic USB Type-C DP HPD bridge (Dmitry, Heikki).
Patch 3 adds module soft depend for USB Type-C DP HPD bridge(Sebastian).
Patch 4 adds new API drm_aux_bridge_register_from_node() (Neil).
Patch 5 adds DRM AUX bridge support for RK3399 USBDP PHY (Neil).
Patch 6 drops CDN-DP's extcon dependency when Type-C is present (Dmitry).
Patch 7 adds multiple bridges to support PHY port selection (Dmitry, Luca).
Changes in v6:
- Link to v5: https://lore.kernel.org/all/20260730013349.51-1-kernel@airkyi.com/
- Avoid duplicate registration of the same DisplayPort HPD bridge.
Changes in v5:
- Link to v4: https://lore.kernel.org/all/20260727064408.101-1-kernel@airkyi.com/
- Add module soft depend for USB Type-C DP HPD bridge.
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 (7):
drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge()
drm/bridge: Implement generic USB Type-C DP HPD bridge
drm/display: Add soft depend for aux-hpd-typec-dp-bridge module
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 +-
drivers/gpu/drm/bridge/aux-hpd-bridge.c | 42 +-
.../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 67 ++++
.../gpu/drm/display/drm_display_helper_mod.c | 1 +
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 | 12 +
12 files changed, 479 insertions(+), 72 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
--
2.53.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge()
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-06 17:56 ` Sebastian Reichel
2026-08-04 7:07 ` [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
` (5 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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>
Add a new API to check whether a DisplayPort HPD bridge has already
been registered. This helps avoid duplicate registration of the same
HPD bridge, although the current framework allows doing so.
Suggested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
---
drivers/gpu/drm/bridge/aux-hpd-bridge.c | 42 ++++++++++++++++++++++++-
include/drm/bridge/aux-bridge.h | 6 ++++
2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c
index f02a38a2638a..a56c88eba005 100644
--- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c
+++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c
@@ -12,6 +12,8 @@
#include <drm/drm_bridge.h>
#include <drm/bridge/aux-bridge.h>
+#define DRM_AUX_HPD_BRIDGE_NAME "dp_hpd_bridge"
+
static DEFINE_IDA(drm_aux_hpd_bridge_ida);
struct drm_aux_hpd_bridge_data {
@@ -36,6 +38,44 @@ static void drm_aux_hpd_bridge_free_adev(void *_adev)
auxiliary_device_uninit(_adev);
}
+static int hpd_bridge_match(struct device *dev, const void *data)
+{
+ const struct device_node *np = data;
+ struct auxiliary_device *adev;
+
+ if (!dev_is_auxiliary(dev))
+ return 0;
+
+ adev = to_auxiliary_dev(dev);
+ if (strcmp(adev->name, DRM_AUX_HPD_BRIDGE_NAME))
+ return 0;
+
+ return adev->dev.platform_data == np;
+}
+
+/**
+ * drm_dev_has_dp_hpd_bridge - check whether a HPD DisplayPort bridge is registered
+ * @parent: device instance providing this bridge
+ * @np: device node pointer corresponding to this bridge instance
+ *
+ * Walk the children of @parent and check whether a HPD DisplayPort bridge for
+ * the given @np has already been registered via devm_drm_dp_hpd_bridge_add().
+ *
+ * Return: true if a HPD bridge for @parent / @np already exists, false otherwise
+ */
+bool drm_dev_has_dp_hpd_bridge(struct device *parent, struct device_node *np)
+{
+ struct device *child;
+
+ child = device_find_child(parent, np, hpd_bridge_match);
+ if (child) {
+ put_device(child);
+ return true;
+ }
+ return false;
+}
+EXPORT_SYMBOL_GPL(drm_dev_has_dp_hpd_bridge);
+
/**
* devm_drm_dp_hpd_bridge_alloc - allocate a HPD DisplayPort bridge
* @parent: device instance providing this bridge
@@ -63,7 +103,7 @@ struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, str
}
adev->id = ret;
- adev->name = "dp_hpd_bridge";
+ adev->name = DRM_AUX_HPD_BRIDGE_NAME;
adev->dev.parent = parent;
adev->dev.release = drm_aux_hpd_bridge_release;
adev->dev.platform_data = of_node_get(np);
diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h
index c2f5a855512f..cca07a8e2d45 100644
--- a/include/drm/bridge/aux-bridge.h
+++ b/include/drm/bridge/aux-bridge.h
@@ -25,6 +25,7 @@ struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, str
int devm_drm_dp_hpd_bridge_add(struct device *dev, struct auxiliary_device *adev);
struct device *drm_dp_hpd_bridge_register(struct device *parent,
struct device_node *np);
+bool drm_dev_has_dp_hpd_bridge(struct device *parent, struct device_node *np);
void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status);
#else
static inline struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent,
@@ -44,6 +45,11 @@ static inline struct device *drm_dp_hpd_bridge_register(struct device *parent,
return NULL;
}
+static inline bool drm_dev_has_dp_hpd_bridge(struct device *parent, struct device_node *np)
+{
+ return false;
+}
+
static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status)
{
}
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-08-04 7:07 ` [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge() Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-04 7:25 ` sashiko-bot
2026-08-06 18:00 ` Sebastian Reichel
2026-08-04 7:07 ` [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module Chaoyi Chen
` (4 subsequent siblings)
6 siblings, 2 replies; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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 v6:
- Avoid duplicate registration of the same DisplayPort HPD bridge.
(no changes since v5)
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 | 67 +++++++++++++++++++
3 files changed, 78 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..682bc192e093
--- /dev/null
+++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
@@ -0,0 +1,67 @@
+// 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);
+ struct device_node *np;
+
+ 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) {
+ np = to_of_node(alt->dev.parent->fwnode);
+ if (!drm_dev_has_dp_hpd_bridge(alt->dev.parent->parent, np))
+ drm_dp_hpd_bridge_register(alt->dev.parent->parent, np);
+ }
+
+ 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
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-08-04 7:07 ` [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge() Chaoyi Chen
2026-08-04 7:07 ` [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-04 7:19 ` sashiko-bot
2026-08-06 18:01 ` Sebastian Reichel
2026-08-04 7:07 ` [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
` (3 subsequent siblings)
6 siblings, 2 replies; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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 aux-hpd-typec-dp-bridge module serves as a generic TypeC
DisplayPort HPD bridge and is not required by any other
module. Therefore, it will not be auto-loaded.
Given that the drm_display_helper module houses DisplayPort-related
helper code, add a MODULE_SOFTDEP() within it to suggest loading the
aux-hpd-typec-dp-bridge module beforehand.
Suggested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
---
(no changes since v6)
---
drivers/gpu/drm/display/drm_display_helper_mod.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/display/drm_display_helper_mod.c b/drivers/gpu/drm/display/drm_display_helper_mod.c
index d8a6e6228773..f0152d6b0b2d 100644
--- a/drivers/gpu/drm/display/drm_display_helper_mod.c
+++ b/drivers/gpu/drm/display/drm_display_helper_mod.c
@@ -18,5 +18,6 @@ static void __exit drm_display_helper_module_exit(void)
drm_dp_aux_dev_exit();
}
+MODULE_SOFTDEP("pre: aux-hpd-typec-dp-bridge");
module_init(drm_display_helper_module_init);
module_exit(drm_display_helper_module_exit);
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
` (2 preceding siblings ...)
2026-08-04 7:07 ` [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-04 7:22 ` sashiko-bot
2026-08-04 7:07 ` [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
` (2 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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 cca07a8e2d45..67f74ed287d9 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] 18+ messages in thread
* [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
` (3 preceding siblings ...)
2026-08-04 7:07 ` [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-04 7:32 ` sashiko-bot
2026-08-06 15:52 ` Vinod Koul
2026-08-04 7:07 ` [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
2026-08-04 7:07 ` [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
6 siblings, 2 replies; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
` (4 preceding siblings ...)
2026-08-04 7:07 ` [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-04 7:35 ` sashiko-bot
2026-08-04 7:07 ` [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
6 siblings, 1 reply; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
` (5 preceding siblings ...)
2026-08-04 7:07 ` [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
@ 2026-08-04 7:07 ` Chaoyi Chen
2026-08-04 7:38 ` sashiko-bot
6 siblings, 1 reply; 18+ messages in thread
From: Chaoyi Chen @ 2026-08-04 7:07 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
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module
2026-08-04 7:07 ` [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module Chaoyi Chen
@ 2026-08-04 7:19 ` sashiko-bot
2026-08-06 18:01 ` Sebastian Reichel
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-04 7:19 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: neil.armstrong, olteanv, linux-phy, vkoul, dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `MODULE_SOFTDEP` approach fails to load the `aux-hpd-typec-dp-bridge` module if `CONFIG_DRM_DISPLAY_HELPER` is built into the kernel (`=y`) and `CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE` is built as a module (`=m`).
- [Medium] Adding a soft dependency on `aux-hpd-typec-dp-bridge` to a core DRM helper unconditionally forces the loading of the USB Type-C subsystem on almost any system with a display, causing significant module bloat.
--
--- Patch [3]: [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module ---
commit f6b97c0e7a5ceab0d443dbadd5246c5cc93f2ec7
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/display: Add soft depend for aux-hpd-typec-dp-bridge module
This patch adds a soft dependency on the aux-hpd-typec-dp-bridge module to
the core drm_display_helper module. It attempts to ensure the bridge module
is auto-loaded before the display helper.
> diff --git a/drivers/gpu/drm/display/drm_display_helper_mod.c b/drivers/gpu/drm/display/drm_display_helper_mod.c
> index d8a6e62287736..f0152d6b0b2d7 100644
> --- a/drivers/gpu/drm/display/drm_display_helper_mod.c
> +++ b/drivers/gpu/drm/display/drm_display_helper_mod.c
[ ... ]
> @@ -18,5 +18,6 @@ static void __exit drm_display_helper_module_exit(void)
> drm_dp_aux_dev_exit();
> }
>
> +MODULE_SOFTDEP("pre: aux-hpd-typec-dp-bridge");
[Severity: Medium]
Does this soft dependency unconditionally pull the entire USB Type-C subsystem
into memory on almost every system that loads a standard DRM driver?
Since aux-hpd-typec-dp-bridge depends on typec.ko, it seems this will cause
unnecessary memory bloat on systems without Type-C displays, simply because
their DRM driver (like amdgpu, i915, or nouveau) uses drm_display_helper.
[Severity: High]
Will this approach fail to auto-load the bridge if CONFIG_DRM_DISPLAY_HELPER
is built into the kernel (=y) and CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE is built
as a module (=m)?
If drm_display_helper is built-in, modprobe is never invoked for it during
boot, which means the MODULE_SOFTDEP directive is ignored. Because the
aux-hpd-typec-dp-bridge lacks a MODULE_DEVICE_TABLE or udev loading
mechanism, wouldn't it fail to load entirely in this configuration, silently
breaking Type-C DisplayPort hotplug detection?
> module_init(drm_display_helper_module_init);
> module_exit(drm_display_helper_module_exit);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804070730.68-1-kernel@airkyi.com?part=3
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node()
2026-08-04 7:07 ` [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
@ 2026-08-04 7:22 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-04 7:22 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: dri-devel, linux-phy, olteanv, vkoul, neil.armstrong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] drm_aux_bridge_register_from_node() assigns the provided device node without taking a reference, leading to a reference count underflow when the device is released or initialization fails.
--
--- Patch [4]: [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node() ---
commit 589f6bc20e86e41fcd3fb17c01ef52cecefa8a82
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, extending the
existing drm_aux_bridge_register() functionality.
> 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 code need to take a reference on np before setting it?
Looking at device_set_of_node_from_dev(), it correctly acquires a reference
using of_node_get(). However, device_set_node() only assigns the pointer
without taking a reference.
If initialization fails, the error path unconditionally drops a reference:
ret = auxiliary_device_init(adev);
if (ret) {
of_node_put(adev->dev.of_node);
...
Additionally, drm_aux_bridge_release() unconditionally calls of_node_put()
when the device is released.
Could assigning the node here without of_node_get() cause a reference count
underflow and a potential use-after-free for the provided node?
> + else
> + device_set_of_node_from_dev(&adev->dev, parent);
>
> ret = auxiliary_device_init(adev);
> if (ret) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804070730.68-1-kernel@airkyi.com?part=4
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge
2026-08-04 7:07 ` [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
@ 2026-08-04 7:25 ` sashiko-bot
2026-08-06 18:00 ` Sebastian Reichel
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-04 7:25 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: linux-phy, dri-devel, vkoul, neil.armstrong, olteanv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Type-C DP HPD bridges are erroneously shared across multiple ports on non-DeviceTree (e.g., ACPI) systems due to NULL `of_node` matching.
- [Medium] Double registration of the DP HPD bridge due to a TOCTOU race condition between module initialization and concurrent device addition.
--
--- Patch [2]: [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge ---
commit da360ed1d856c20444642867c8a203f9c48362b1
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 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.
> 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..682bc192e0934
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
> @@ -0,0 +1,67 @@
> +// 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);
> + struct device_node *np;
> +
> + 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) {
> + np = to_of_node(alt->dev.parent->fwnode);
> + if (!drm_dev_has_dp_hpd_bridge(alt->dev.parent->parent, np))
> + drm_dp_hpd_bridge_register(alt->dev.parent->parent, np);
[Severity: High]
On non-DeviceTree systems (like ACPI platforms), will this fail to register
subsequent DP ports on a single USB-C controller?
Since to_of_node() evaluates to NULL for non-OF systems, np will be NULL.
When the first port registers the bridge, it uses NULL as the device node.
When the second port is processed, drm_dev_has_dp_hpd_bridge() will search
for an existing bridge. The underlying hpd_bridge_match() function checks:
return adev->dev.platform_data == np;
Since adev->dev.platform_data is NULL and np is NULL, this returns true,
incorrectly concluding that the second port's bridge is already registered.
Could this silently break hotplug detection for those additional ports?
> + }
> +
> + 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 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: Medium]
Could this setup a race condition resulting in a double registration of the
DP HPD bridge?
If a device is hotplugged precisely during module loading, the notifier
runs on the hotplug thread, and bus_for_each_dev() might visit the same
device on the init thread. Both threads would evaluate
!drm_dev_has_dp_hpd_bridge() as true in drm_typec_bus_event() since it is
a lockless check.
Would this cause drm_dp_hpd_bridge_register() to be called twice for the
same USB-C controller port?
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804070730.68-1-kernel@airkyi.com?part=2
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
2026-08-04 7:07 ` [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
@ 2026-08-04 7:32 ` sashiko-bot
2026-08-06 15:52 ` Vinod Koul
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-04 7:32 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: neil.armstrong, dri-devel, linux-phy, olteanv, vkoul
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Passing `child_np` to `drm_aux_bridge_register_from_node()` without acquiring an additional reference leads to a refcount underflow and potential double-free.
- [Medium] The result of `devm_phy_create()` is not checked immediately, leading to potential error shadowing and broken probe deferral.
--
--- Patch [5]: [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge ---
commit ed030870fca01a8999b3193c38becaaeb4276a91
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);
[Severity: Medium]
Should we check if devm_phy_create() failed before registering the bridge?
If devm_phy_create() fails with an error like -EPROBE_DEFER, but
drm_aux_bridge_register_from_node() also fails, the code below checks 'ret'
first and returns that error. This could shadow the -EPROBE_DEFER from the
PHY creation and prevent the driver from properly probing again.
[Severity: High]
Does this code need to call of_node_get() on child_np before passing it here?
The for_each_available_child_of_node() loop only holds a reference to child_np
during the current iteration, dropping it when advancing to the next node.
However, drm_aux_bridge_register_from_node() assigns the node to the bridge
device without incrementing the reference count, and later calls of_node_put()
in its release callback.
Will this lead to a refcount underflow and use-after-free of the node memory
once the loop advances?
> + } 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]
If drm_aux_bridge_register_from_node() fails, does this cause a double-free
of the OF node?
Because the loop reference was passed directly to the bridge registration
without incrementing the reference count, and the bridge already calls
of_node_put() on its own internal error path, calling of_node_put() here
again will drop the reference count a second time.
> + return ret;
> + }
> +
> if (IS_ERR(phy)) {
> dev_err(dev, "failed to create phy: %pOFn\n",
> child_np);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804070730.68-1-kernel@airkyi.com?part=5
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon
2026-08-04 7:07 ` [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
@ 2026-08-04 7:35 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-04 7:35 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: olteanv, linux-phy, dri-devel, neil.armstrong, vkoul
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Logic error in pointer error checking breaks probe for all existing boards using extcon.
- [High] Missing hotplug detection (HPD) mechanism when extcon is absent.
--
--- Patch [6]: [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon ---
commit 2ec5f110f1557ebaec82cbe6b39d4e2ab7f4a88f
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
drm/rockchip: cdn-dp: Support handle lane info without extcon
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.
> 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 break hotplug
detection?
The driver relies on cdn_dp_pd_event_work() to transition dp->connected to
true, load firmware, and train the link. Without the extcon notifier,
event_work is never scheduled on hotplug.
Since cdn_dp_bridge_detect() simply returns the static dp->connected state:
drivers/gpu/drm/rockchip/cdn-dp-core.c:cdn_dp_bridge_detect() {
mutex_lock(&dp->lock);
if (dp->connected)
status = connector_status_connected;
mutex_unlock(&dp->lock);
...
}
Could KMS polling always see the port as disconnected when hardware is
configured without an extcon device?
> 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: Critical]
Will this condition accidentally skip port initialization for all valid extcon
pointers?
If extcon is a valid pointer, PTR_ERR(extcon) evaluates to its memory address,
which will never equal -ENODEV. This causes the condition to evaluate to true,
triggering the continue statement and bypassing port setup completely for any
hardware that has an extcon device.
> 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;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804070730.68-1-kernel@airkyi.com?part=6
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
2026-08-04 7:07 ` [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
@ 2026-08-04 7:38 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-04 7:38 UTC (permalink / raw)
To: Chaoyi Chen; +Cc: linux-phy, neil.armstrong, vkoul, dri-devel, olteanv
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
New issues:
- [High] `cdn_dp_connected_port` unconditionally returns `dp->port[0]` for extcon setups, completely breaking Hot-Plug Detection.
- [High] A mismatch between the number of PHYs (`dp->ports`) and DT endpoints (`dp->bridge_count`) causes uninitialized stack reads and NULL pointer dereferences.
- [High] Out-of-bounds array write in `cdn_dp_parse_next_bridge_dt` when a device tree has more than two endpoints.
- [High] The error path in `cdn_dp_bind()` and successful path in `cdn_dp_unbind()` do not clear `dp->next_bridge_list` pointers after dropping references, leading to a Double-Put / Use-After-Free.
- [High] Double-free of EDID cache pointers across component unbind/rebind cycles.
- [High] Missing mutual exclusion in `atomic_check` allows simultaneous enablement of multiple bridges sharing a single DP controller.
- [High] Using `devm_drm_bridge_alloc()` and `devm_drm_bridge_add()` inside the component `bind` callback leads to memory leaks and dangling global DRM objects on unbind/rebind cycles.
- [High] `cdn_dp_switch_port` unconditionally programs video timings during EDID reads, causing failures when no valid mode is set.
- [High] Failure to clean up a successfully initialized `drm_encoder` in `cdn_bridge_add` error paths leads to Use-After-Free.
- [Medium] `INIT_WORK` is called after exposing the bridge to external subsystems, allowing an early HPD notification to schedule uninitialized work.
Pre-existing issues:
- [High] Using `devm_extcon_register_notifier` inside a component `bind` function causes notifier chain corruption and UAF on rebind.
--
--- Patch [7]: [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection ---
commit 47d7fbb7737dc45e037477d5f8fc38f597fe33c9
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 in the Rockchip CDN-DP controller
to allow users to flexibly select the output port between two USB/DP combo
PHYs. It introduces independent encoders and bridges for each PHY port based
on the DRM AUX HPD bridge, requiring proper DT configuration.
[ ... ]
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> --- 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 early return unconditionally bypass the connection state evaluation
for extcon setups?
If !dp->next_bridge_valid evaluates to true, it looks like this will return
dp->port[0] on the first iteration without verifying whether the lanes
indicate an active connection, which would break hot-plug detection for
extcon-based devices.
> }
> +
> + 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 loop read uninitialized stack memory?
If the device tree defines more bridge endpoints in dp->bridge_count than the
number of physical PHY ports in dp->ports, this loop iterates past the
initialized bounds of the lanes array.
> + }
> +
> 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];
[Severity: High]
Could this also lead to a NULL pointer dereference?
If dp_bridge->id exceeds the configured PHY ports, dp->port will be NULL.
The subsequent check for port->phy_enabled below will then dereference this
NULL pointer.
> + struct cdn_dp_port *prev_port = NULL;
> + const struct drm_edid *drm_edid = NULL;
[ ... ]
> +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
> + struct cdn_dp_port *port)
> +{
> + int ret;
[ ... ]
> + 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);
[Severity: High]
Will this video configuration fail when called from the EDID read path?
cdn_dp_switch_port() is invoked from cdn_dp_bridge_edid_read() where no
valid display mode has been established yet. Since dp->mode.clock is likely
zero at this point, cdn_dp_config_video() might fail with an invalid argument
or divide-by-zero, which would break the port switch entirely.
> + if (ret)
> + dev_err(dp->dev, "Failed to configure video: %d\n", ret);
> + }
> +
> +out:
> + return ret;
> +}
[ ... ]
> @@ -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);
[Severity: High]
Is there missing mutual exclusion to prevent simultaneous enablement of
multiple bridges sharing this single DP controller?
Since the driver creates multiple DRM connectors (one per PHY port) that
share a single physical DP hardware block, without an atomic_check to ensure
only one connector is active at a time, userspace might try to simultaneously
enable CRTCs on multiple connectors. Both could race to program the shared
dp->regs and GRF registers.
> if (ret) {
> DRM_DEV_ERROR(dp->dev, "Failed to enable bridge %d\n",
[ ... ]
> -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);
[Severity: High]
Will using devres-managed memory allocation and registration inside a
component bind callback cause memory leaks?
Because cdn_dp_bind() is a component bind callback, the devm lifecycle
is tied to the parent device which was bound during probe. When the
component is unbound, the devm cleanup does not fire. Upon rebinding,
new memory is allocated and new bridges are added to the global list,
leaking the old ones and leaving dangling objects.
This applies to devm_drm_bridge_alloc() usage below as well.
> if (ret)
> return ret;
[ ... ]
> +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;
[ ... ]
> + /* 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: High]
Can this loop overflow the next_bridge_list array?
If the device tree specifies more than MAX_PHY (2) endpoints, this
unconditionally increments count and writes past the bounds of the array,
which would corrupt the cdn_dp_device structure.
> + }
> +
> +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);
[ ... ]
> + 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);
[Severity: High]
Does this error path leave an initialized encoder hanging without cleanup?
If cdn_bridge_add() succeeds at drm_simple_encoder_init() but fails on a
subsequent step like drm_bridge_connector_init(), it returns an error but
leaves the encoder initialized and attached to the drm_dev.
Additionally, if the loop here fails on the second iteration and jumps to
out, it frees resources but leaves the first successfully initialized encoder
still attached to the drm_dev. When the DRM core later traverses its encoder
lists, this could result in a use-after-free.
> + if (ret)
> + goto out;
> + }
> +
> + INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
[Severity: Medium]
Is it safe to initialize this work struct after the bridge has been exposed
to external subsystems?
The bridges are created and attached to the global chain just prior to this,
making them visible. If an external bridge or connected sink fires an HPD
notification immediately, cdn_dp_bridge_hpd_notify() could execute and call
schedule_work() before INIT_WORK() has run, potentially causing a crash.
> 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;
[Severity: High]
This is a pre-existing issue, but does using devm_extcon_register_notifier()
in a component bind callback cause notifier chain corruption?
Similar to the bridge devm issue, since the device isn't torn down on
component unbind, the devres cleanup never fires. On a subsequent re-bind,
it registers the same notifier block again, which corrupts the linked list
of the extcon notifier chain.
> }
> }
>
> @@ -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]);
[Severity: High]
Could this error path cause a double-put or use-after-free on bind retries?
The dp structure is allocated in probe, so it persists across component
bind/unbind cycles. When dropping references here, the pointers in
dp->next_bridge_list are not set to NULL. If a subsequent bind retry fails,
this error path might execute again and call drm_bridge_put() on the already
freed pointers.
> +
> + 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 stale EDID pointers in the cache?
This frees the cached EDIDs but fails to set the pointers to NULL. Because
the dp context persists across component unbind/rebind, a subsequent EDID
read via cdn_dp_bridge_edid_read() might attempt to free the same stale
pointer again before replacing it, corrupting memory.
>
> pm_runtime_disable(dev);
> if (dp->fw_loaded)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804070730.68-1-kernel@airkyi.com?part=7
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge
2026-08-04 7:07 ` [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
2026-08-04 7:32 ` sashiko-bot
@ 2026-08-06 15:52 ` Vinod Koul
1 sibling, 0 replies; 18+ messages in thread
From: Vinod Koul @ 2026-08-06 15:52 UTC (permalink / raw)
To: Chaoyi Chen
Cc: 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, Chaoyi Chen,
Sebastian Reichel, Nicolas Frattaroli, Heikki Krogerus,
linux-kernel, dri-devel, linux-arm-kernel, linux-rockchip,
linux-phy
On 04-08-26, 15:07, Chaoyi Chen wrote:
> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>
> Using the DRM_AUX_BRIDGE helper to create the transparent DRM bridge
> device.
Acked-by: Vinod Koul <vkoul@kernel.org>
--
~Vinod
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge()
2026-08-04 7:07 ` [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge() Chaoyi Chen
@ 2026-08-06 17:56 ` Sebastian Reichel
0 siblings, 0 replies; 18+ messages in thread
From: Sebastian Reichel @ 2026-08-06 17:56 UTC (permalink / raw)
To: Chaoyi Chen
Cc: 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, Nicolas Frattaroli, Heikki Krogerus, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy
[-- Attachment #1: Type: text/plain, Size: 4542 bytes --]
Hi,
On Tue, Aug 04, 2026 at 03:07:24PM +0800, Chaoyi Chen wrote:
> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>
> Add a new API to check whether a DisplayPort HPD bridge has already
> been registered. This helps avoid duplicate registration of the same
> HPD bridge, although the current framework allows doing so.
>
> Suggested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
> Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> ---
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
I still see the bridge registered twice for fusb302. Apparently the
the new notifier is faster than the explicit registration in the
driver. The code added here works though; reloading
aux_hpd_typec_dp_bridge does not register more and more bridges :)
Greetings,
-- Sebastian
> drivers/gpu/drm/bridge/aux-hpd-bridge.c | 42 ++++++++++++++++++++++++-
> include/drm/bridge/aux-bridge.h | 6 ++++
> 2 files changed, 47 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c
> index f02a38a2638a..a56c88eba005 100644
> --- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c
> +++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c
> @@ -12,6 +12,8 @@
> #include <drm/drm_bridge.h>
> #include <drm/bridge/aux-bridge.h>
>
> +#define DRM_AUX_HPD_BRIDGE_NAME "dp_hpd_bridge"
> +
> static DEFINE_IDA(drm_aux_hpd_bridge_ida);
>
> struct drm_aux_hpd_bridge_data {
> @@ -36,6 +38,44 @@ static void drm_aux_hpd_bridge_free_adev(void *_adev)
> auxiliary_device_uninit(_adev);
> }
>
> +static int hpd_bridge_match(struct device *dev, const void *data)
> +{
> + const struct device_node *np = data;
> + struct auxiliary_device *adev;
> +
> + if (!dev_is_auxiliary(dev))
> + return 0;
> +
> + adev = to_auxiliary_dev(dev);
> + if (strcmp(adev->name, DRM_AUX_HPD_BRIDGE_NAME))
> + return 0;
> +
> + return adev->dev.platform_data == np;
> +}
> +
> +/**
> + * drm_dev_has_dp_hpd_bridge - check whether a HPD DisplayPort bridge is registered
> + * @parent: device instance providing this bridge
> + * @np: device node pointer corresponding to this bridge instance
> + *
> + * Walk the children of @parent and check whether a HPD DisplayPort bridge for
> + * the given @np has already been registered via devm_drm_dp_hpd_bridge_add().
> + *
> + * Return: true if a HPD bridge for @parent / @np already exists, false otherwise
> + */
> +bool drm_dev_has_dp_hpd_bridge(struct device *parent, struct device_node *np)
> +{
> + struct device *child;
> +
> + child = device_find_child(parent, np, hpd_bridge_match);
> + if (child) {
> + put_device(child);
> + return true;
> + }
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(drm_dev_has_dp_hpd_bridge);
> +
> /**
> * devm_drm_dp_hpd_bridge_alloc - allocate a HPD DisplayPort bridge
> * @parent: device instance providing this bridge
> @@ -63,7 +103,7 @@ struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, str
> }
>
> adev->id = ret;
> - adev->name = "dp_hpd_bridge";
> + adev->name = DRM_AUX_HPD_BRIDGE_NAME;
> adev->dev.parent = parent;
> adev->dev.release = drm_aux_hpd_bridge_release;
> adev->dev.platform_data = of_node_get(np);
> diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h
> index c2f5a855512f..cca07a8e2d45 100644
> --- a/include/drm/bridge/aux-bridge.h
> +++ b/include/drm/bridge/aux-bridge.h
> @@ -25,6 +25,7 @@ struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, str
> int devm_drm_dp_hpd_bridge_add(struct device *dev, struct auxiliary_device *adev);
> struct device *drm_dp_hpd_bridge_register(struct device *parent,
> struct device_node *np);
> +bool drm_dev_has_dp_hpd_bridge(struct device *parent, struct device_node *np);
> void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status);
> #else
> static inline struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent,
> @@ -44,6 +45,11 @@ static inline struct device *drm_dp_hpd_bridge_register(struct device *parent,
> return NULL;
> }
>
> +static inline bool drm_dev_has_dp_hpd_bridge(struct device *parent, struct device_node *np)
> +{
> + return false;
> +}
> +
> static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status)
> {
> }
> --
> 2.53.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge
2026-08-04 7:07 ` [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-08-04 7:25 ` sashiko-bot
@ 2026-08-06 18:00 ` Sebastian Reichel
1 sibling, 0 replies; 18+ messages in thread
From: Sebastian Reichel @ 2026-08-06 18:00 UTC (permalink / raw)
To: Chaoyi Chen
Cc: 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, Nicolas Frattaroli, Heikki Krogerus, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy
[-- Attachment #1: Type: text/plain, Size: 6200 bytes --]
Hi,
On Tue, Aug 04, 2026 at 03:07:25PM +0800, Chaoyi Chen wrote:
> 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>
> ---
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
As mentioned in previous patch, 1x duplication still happens for
drivers that currently do a manual HPD bridge registration. IMHO
this is fine as the manual registration can be removed as a
follow-up anyways.
Greetings,
-- Sebastian
> Changes in v6:
> - Avoid duplicate registration of the same DisplayPort HPD bridge.
>
> (no changes since v5)
>
> 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 | 67 +++++++++++++++++++
> 3 files changed, 78 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..682bc192e093
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
> @@ -0,0 +1,67 @@
> +// 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);
> + struct device_node *np;
> +
> + 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) {
> + np = to_of_node(alt->dev.parent->fwnode);
> + if (!drm_dev_has_dp_hpd_bridge(alt->dev.parent->parent, np))
> + drm_dp_hpd_bridge_register(alt->dev.parent->parent, np);
> + }
> +
> + 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
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module
2026-08-04 7:07 ` [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module Chaoyi Chen
2026-08-04 7:19 ` sashiko-bot
@ 2026-08-06 18:01 ` Sebastian Reichel
1 sibling, 0 replies; 18+ messages in thread
From: Sebastian Reichel @ 2026-08-06 18:01 UTC (permalink / raw)
To: Chaoyi Chen
Cc: 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, Nicolas Frattaroli, Heikki Krogerus, linux-kernel,
dri-devel, linux-arm-kernel, linux-rockchip, linux-phy
[-- Attachment #1: Type: text/plain, Size: 1565 bytes --]
Hi,
On Tue, Aug 04, 2026 at 03:07:26PM +0800, Chaoyi Chen wrote:
> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>
> The aux-hpd-typec-dp-bridge module serves as a generic TypeC
> DisplayPort HPD bridge and is not required by any other
> module. Therefore, it will not be auto-loaded.
>
> Given that the drm_display_helper module houses DisplayPort-related
> helper code, add a MODULE_SOFTDEP() within it to suggest loading the
> aux-hpd-typec-dp-bridge module beforehand.
>
> Suggested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
> Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> ---
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Booting defconfig without manual HPD bridge registration works now :)
Greetings,
-- Sebastian
>
> (no changes since v6)
>
> ---
> drivers/gpu/drm/display/drm_display_helper_mod.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/gpu/drm/display/drm_display_helper_mod.c b/drivers/gpu/drm/display/drm_display_helper_mod.c
> index d8a6e6228773..f0152d6b0b2d 100644
> --- a/drivers/gpu/drm/display/drm_display_helper_mod.c
> +++ b/drivers/gpu/drm/display/drm_display_helper_mod.c
> @@ -18,5 +18,6 @@ static void __exit drm_display_helper_module_exit(void)
> drm_dp_aux_dev_exit();
> }
>
> +MODULE_SOFTDEP("pre: aux-hpd-typec-dp-bridge");
> module_init(drm_display_helper_module_init);
> module_exit(drm_display_helper_module_exit);
> --
> 2.53.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-08-06 18:01 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04 7:07 [PATCH v6 0/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-08-04 7:07 ` [PATCH v6 1/7] drm/bridge: aux-hpd-bridge: Add drm_dev_has_dp_hpd_bridge() Chaoyi Chen
2026-08-06 17:56 ` Sebastian Reichel
2026-08-04 7:07 ` [PATCH v6 2/7] drm/bridge: Implement generic USB Type-C DP HPD bridge Chaoyi Chen
2026-08-04 7:25 ` sashiko-bot
2026-08-06 18:00 ` Sebastian Reichel
2026-08-04 7:07 ` [PATCH v6 3/7] drm/display: Add soft depend for aux-hpd-typec-dp-bridge module Chaoyi Chen
2026-08-04 7:19 ` sashiko-bot
2026-08-06 18:01 ` Sebastian Reichel
2026-08-04 7:07 ` [PATCH v6 4/7] drm/bridge: aux: Add drm_aux_bridge_register_from_node() Chaoyi Chen
2026-08-04 7:22 ` sashiko-bot
2026-08-04 7:07 ` [PATCH v6 5/7] phy: rockchip: phy-rockchip-typec: Add DRM AUX bridge Chaoyi Chen
2026-08-04 7:32 ` sashiko-bot
2026-08-06 15:52 ` Vinod Koul
2026-08-04 7:07 ` [PATCH v6 6/7] drm/rockchip: cdn-dp: Support handle lane info without extcon Chaoyi Chen
2026-08-04 7:35 ` sashiko-bot
2026-08-04 7:07 ` [PATCH v6 7/7] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection Chaoyi Chen
2026-08-04 7:38 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox