* [PATCH v18 1/7] phy: rockchip: usbdp: Track PHY power state
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:10 ` sashiko-bot
2026-10-21 18:05 ` [PATCH v18 2/7] phy: rockchip: usbdp: Add reset hook support for dwc3 Sebastian Reichel
` (5 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel
Track PHY state requested by software for DP and USB3 individually
in preparation to rework and fix up power state handling.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index e748acdd2d31..cb1f455accf8 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -175,6 +175,7 @@ struct rk_udphy {
bool phy_needs_reinit;
u8 mode;
u8 status;
+ u8 sw_mode; /* modes currently requested */
/* utilized for USB */
bool hs; /* flag for high-speed */
@@ -1051,11 +1052,15 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy)
int ret;
scoped_guard(mutex, &udphy->mutex) {
+ udphy->sw_mode |= UDPHY_MODE_DP;
+
phy_set_bus_width(phy, udphy->dp_lanes);
ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP);
- if (ret)
+ if (ret) {
+ udphy->sw_mode &= ~UDPHY_MODE_DP;
return ret;
+ }
rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes);
@@ -1078,6 +1083,8 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy)
guard(mutex)(&udphy->mutex);
+ udphy->sw_mode &= ~UDPHY_MODE_DP;
+
rk_udphy_dp_lane_enable(udphy, 0);
rk_udphy_power_off(udphy, UDPHY_MODE_DP);
@@ -1286,16 +1293,25 @@ static const struct phy_ops rk_udphy_dp_phy_ops = {
static int rk_udphy_usb3_phy_init(struct phy *phy)
{
struct rk_udphy *udphy = phy_get_drvdata(phy);
+ int ret;
guard(mutex)(&udphy->mutex);
+ udphy->sw_mode |= UDPHY_MODE_USB;
+
/* DP only or high-speed, disable U3 port */
if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) {
rk_udphy_u3_port_disable(udphy, true);
return 0;
}
- return rk_udphy_power_on(udphy, UDPHY_MODE_USB);
+ ret = rk_udphy_power_on(udphy, UDPHY_MODE_USB);
+ if (ret) {
+ udphy->sw_mode &= ~UDPHY_MODE_USB;
+ return ret;
+ }
+
+ return 0;
}
static int rk_udphy_usb3_phy_exit(struct phy *phy)
@@ -1304,6 +1320,8 @@ static int rk_udphy_usb3_phy_exit(struct phy *phy)
guard(mutex)(&udphy->mutex);
+ udphy->sw_mode &= ~UDPHY_MODE_USB;
+
/* DP only or high-speed */
if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs)
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v18 2/7] phy: rockchip: usbdp: Add reset hook support for dwc3
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
2026-10-21 18:05 ` [PATCH v18 1/7] phy: rockchip: usbdp: Track PHY power state Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:20 ` sashiko-bot
2026-10-21 18:05 ` [PATCH v18 3/7] usb: dwc3: rockchip: introduce glue driver Sebastian Reichel
` (4 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel
When TCPM (Type-C Port Manager) triggers a orientation or mux
change, the PHY needs to reset. This results in the PHY no longer
providing a clock to the USB controller. If the USB controller
registers are accessed without the clock, SErrors are generated.
This can be avoided by switching the clock provider for the USB
controller. But after switching the clock, the USB PHY sometimes does
not succeed to lock its "cmn ana lcpll" PLLs again. I suspect this is
due to a clock glitch and after some debugging figured out that this
can be avoided by disabling the USB controller's PIPE interface while
the PHY reset is ongoing.
Implement an API that allows the USBDP to actively request a reset
from DWC3 as a quirk.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 60 +++++++++++++++++++++++++++++--
include/linux/phy/rockchip-usbdp.h | 31 ++++++++++++++++
2 files changed, 89 insertions(+), 2 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index cb1f455accf8..6fb6b0c0860b 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -19,6 +19,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/phy/phy.h>
+#include <linux/phy/rockchip-usbdp.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
@@ -179,6 +180,9 @@ struct rk_udphy {
/* utilized for USB */
bool hs; /* flag for high-speed */
+ void (*dwc3_reset_handler)(void *data, int port, bool pre);
+ void *dwc3_reset_data;
+ int dwc3_reset_port;
/* utilized for DP */
struct gpio_desc *sbu1_dc_gpio;
@@ -995,6 +999,46 @@ static int rk_udphy_parse_dt(struct rk_udphy *udphy)
return rk_udphy_reset_init(udphy, dev);
}
+static void rk_udphy_run_reset_handler(struct rk_udphy *udphy, bool pre)
+{
+ if (!udphy->dwc3_reset_handler)
+ return;
+
+ if (!(udphy->sw_mode & UDPHY_MODE_USB))
+ return;
+
+ if (udphy->hs)
+ return;
+
+ udphy->dwc3_reset_handler(udphy->dwc3_reset_data, udphy->dwc3_reset_port, pre);
+}
+
+void rk_udphy_unregister_reset_handler(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+
+ guard(mutex)(&udphy->mutex);
+
+ udphy->dwc3_reset_handler = NULL;
+ udphy->dwc3_reset_data = NULL;
+ udphy->dwc3_reset_port = 0;
+}
+EXPORT_SYMBOL(rk_udphy_unregister_reset_handler);
+
+void rk_udphy_register_reset_handler(struct phy *phy,
+ void (*reset_handler)(void *data, int port, bool pre),
+ void *data, int port)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+
+ guard(mutex)(&udphy->mutex);
+
+ udphy->dwc3_reset_handler = reset_handler;
+ udphy->dwc3_reset_data = data;
+ udphy->dwc3_reset_port = port;
+}
+EXPORT_SYMBOL(rk_udphy_register_reset_handler);
+
static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode)
{
int ret;
@@ -1005,24 +1049,36 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode)
}
if (udphy->status == UDPHY_MODE_NONE) {
+ rk_udphy_run_reset_handler(udphy, true);
+
ret = rk_udphy_setup(udphy);
- if (ret)
+ if (ret) {
+ rk_udphy_run_reset_handler(udphy, false);
return ret;
+ }
if (!udphy->hs && udphy->mode & UDPHY_MODE_USB)
rk_udphy_u3_port_disable(udphy, false);
udphy->phy_needs_reinit = false;
+
+ rk_udphy_run_reset_handler(udphy, false);
} else if (udphy->phy_needs_reinit) {
+ rk_udphy_run_reset_handler(udphy, true);
+
rk_udphy_u3_port_disable(udphy, true);
udelay(10);
ret = rk_udphy_init(udphy);
- if (ret)
+ if (ret) {
+ rk_udphy_run_reset_handler(udphy, false);
return ret;
+ }
if (!udphy->hs && udphy->mode & UDPHY_MODE_USB)
rk_udphy_u3_port_disable(udphy, false);
udphy->phy_needs_reinit = false;
+
+ rk_udphy_run_reset_handler(udphy, false);
}
udphy->status |= mode;
diff --git a/include/linux/phy/rockchip-usbdp.h b/include/linux/phy/rockchip-usbdp.h
new file mode 100644
index 000000000000..44e0bc7d1d40
--- /dev/null
+++ b/include/linux/phy/rockchip-usbdp.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Rockchip USBDP Combo PHY with Samsung IP block driver
+ *
+ * Copyright (C) 2026 Collabora Ltd
+ */
+
+#ifndef __ROCKCHIP_USBDP
+#define __ROCKCHIP_USBDP
+
+#include <linux/phy/phy.h>
+
+#if IS_ENABLED(CONFIG_PHY_ROCKCHIP_USBDP)
+
+void rk_udphy_unregister_reset_handler(struct phy *phy);
+void rk_udphy_register_reset_handler(struct phy *phy,
+ void (*reset_handler)(void *data, int port, bool pre),
+ void *data, int port);
+
+#else
+
+static inline void rk_udphy_unregister_reset_handler(struct phy *phy)
+{ }
+static inline void rk_udphy_register_reset_handler(struct phy *phy,
+ void (*reset_handler)(void *data, int port, bool pre),
+ void *data, int port)
+{ }
+
+#endif
+
+#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v18 3/7] usb: dwc3: rockchip: introduce glue driver
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
2026-10-21 18:05 ` [PATCH v18 1/7] phy: rockchip: usbdp: Track PHY power state Sebastian Reichel
2026-10-21 18:05 ` [PATCH v18 2/7] phy: rockchip: usbdp: Add reset hook support for dwc3 Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:12 ` sashiko-bot
2026-10-21 18:05 ` [PATCH v18 4/7] usb: dwc3: core: add post PHY registration hook for platform glue Sebastian Reichel
` (3 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel
Introduce Rockchip specific glue code for the Synopsys DWC3 USB driver.
For now this handles things identical to the default glue.
After this change the glue driver is mandatory for Rockchip RK3588 and
RK3576; it does not make sense to fallback to the core driver as the
whole purpose of the glue driver is fixing race conditions resulting in
SErrors and non-functional USB.
Tested-by: Igor Paunovic <royalnet026@gmail.com> # Orange Pi 5 Plus
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/dwc3/Kconfig | 11 ++++
drivers/usb/dwc3/Makefile | 1 +
drivers/usb/dwc3/core.c | 15 ++++++
drivers/usb/dwc3/dwc3-rockchip.c | 106 +++++++++++++++++++++++++++++++++++++++
4 files changed, 133 insertions(+)
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 18169727a413..3c120ea9746d 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -190,6 +190,17 @@ config USB_DWC3_OCTEON
Only the host mode is currently supported.
Say 'Y' or 'M' here if you have one such device.
+config USB_DWC3_ROCKCHIP
+ tristate "Rockchip DWC3 Platform Driver"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on OF
+ default USB_DWC3
+ help
+ Rockchip SoCs with DesignWare Core USB3 IP inside,
+ and IP Core configured for USB 2.0 and USB 3.0 in host
+ or dual-role mode.
+ Say 'Y' or 'M' if you have such device.
+
config USB_DWC3_RTK
tristate "Realtek DWC3 Platform Driver"
depends on OF && ARCH_REALTEK
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index f37971197203..444e7e7f34b2 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o
obj-$(CONFIG_USB_DWC3_IMX) += dwc3-imx.o
obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o
obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o
+obj-$(CONFIG_USB_DWC3_ROCKCHIP) += dwc3-rockchip.o
obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o
obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o
obj-$(CONFIG_USB_DWC3_GOOGLE) += dwc3-google.o
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index fd5c2cd36c59..4b7132887a03 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -2448,11 +2448,26 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
}
EXPORT_SYMBOL_GPL(dwc3_core_probe);
+/*
+ * List of compatibles, which have "synopsys,dwc3" as a fallback
+ * compatible, but have a vendor specific glue driver that should
+ * be used instead of this one.
+ */
+static const char *const dwc3_compatible_blocklist[] = {
+ "rockchip,rk3588-dwc3",
+ "rockchip,rk3576-dwc3",
+};
+
static int dwc3_probe(struct platform_device *pdev)
{
struct dwc3_probe_data probe_data = {};
struct resource *res;
struct dwc3 *dwc;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dwc3_compatible_blocklist); i++)
+ if (device_is_compatible(&pdev->dev, dwc3_compatible_blocklist[i]))
+ return -ENODEV;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
new file mode 100644
index 000000000000..4129aefd6a8b
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-rockchip.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026, Collabora Ltd. */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include "glue.h"
+
+struct dwc3_rockchip {
+ struct dwc3 dwc;
+};
+
+static int dwc3_rockchip_probe(struct platform_device *pdev)
+{
+ struct dwc3_probe_data probe_data = {};
+ struct resource *res;
+ struct dwc3_rockchip *dwc_rk;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "missing memory resource\n");
+ return -ENODEV;
+ }
+
+ dwc_rk = devm_kzalloc(&pdev->dev, sizeof(*dwc_rk), GFP_KERNEL);
+ if (!dwc_rk)
+ return -ENOMEM;
+
+ dwc_rk->dwc.dev = &pdev->dev;
+ dwc_rk->dwc.glue_ops = NULL;
+
+ probe_data.dwc = &dwc_rk->dwc;
+ probe_data.res = res;
+ probe_data.properties = DWC3_DEFAULT_PROPERTIES;
+
+ return dwc3_core_probe(&probe_data);
+}
+
+static void dwc3_rockchip_remove(struct platform_device *pdev)
+{
+ dwc3_core_remove(platform_get_drvdata(pdev));
+}
+
+static int dwc3_rockchip_runtime_suspend(struct device *dev)
+{
+ return dwc3_runtime_suspend(dev_get_drvdata(dev));
+}
+
+static int dwc3_rockchip_runtime_resume(struct device *dev)
+{
+ return dwc3_runtime_resume(dev_get_drvdata(dev));
+}
+
+static int dwc3_rockchip_runtime_idle(struct device *dev)
+{
+ return dwc3_runtime_idle(dev_get_drvdata(dev));
+}
+
+static int dwc3_rockchip_suspend(struct device *dev)
+{
+ return dwc3_pm_suspend(dev_get_drvdata(dev));
+}
+
+static int dwc3_rockchip_resume(struct device *dev)
+{
+ return dwc3_pm_resume(dev_get_drvdata(dev));
+}
+
+static void dwc3_rockchip_complete(struct device *dev)
+{
+ dwc3_pm_complete(dev_get_drvdata(dev));
+}
+
+static int dwc3_rockchip_prepare(struct device *dev)
+{
+ return dwc3_pm_prepare(dev_get_drvdata(dev));
+}
+
+static const struct dev_pm_ops dwc3_rockchip_dev_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(dwc3_rockchip_suspend, dwc3_rockchip_resume)
+ RUNTIME_PM_OPS(dwc3_rockchip_runtime_suspend, dwc3_rockchip_runtime_resume,
+ dwc3_rockchip_runtime_idle)
+ .complete = pm_sleep_ptr(dwc3_rockchip_complete),
+ .prepare = pm_sleep_ptr(dwc3_rockchip_prepare),
+};
+
+static const struct of_device_id dwc3_rockchip_of_match[] = {
+ { .compatible = "rockchip,rk3588-dwc3" },
+ { .compatible = "rockchip,rk3576-dwc3" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dwc3_rockchip_of_match);
+
+static struct platform_driver dwc3_rockchip_driver = {
+ .probe = dwc3_rockchip_probe,
+ .remove = dwc3_rockchip_remove,
+ .driver = {
+ .name = "dwc3-rockchip",
+ .pm = pm_ptr(&dwc3_rockchip_dev_pm_ops),
+ .of_match_table = dwc3_rockchip_of_match,
+ },
+};
+
+module_platform_driver(dwc3_rockchip_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DesignWare DWC3 Rockchip Glue Driver");
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v18 4/7] usb: dwc3: core: add post PHY registration hook for platform glue
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
` (2 preceding siblings ...)
2026-10-21 18:05 ` [PATCH v18 3/7] usb: dwc3: rockchip: introduce glue driver Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:10 ` sashiko-bot
2026-10-21 18:05 ` [PATCH v18 5/7] usb: dwc3: rockchip: add USBDP PHY reset quirk Sebastian Reichel
` (2 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel,
Krishna Kurapati
Add support for custom PHY handling steps in platform glue code
by adding a post registration hook. This will be used for handling
PHY reset notifications on the Rockchip platform.
Tested-by: Igor Paunovic <royalnet026@gmail.com> # Orange Pi 5 Plus
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Reviewed-by: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/dwc3/core.c | 2 +-
drivers/usb/dwc3/core.h | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 4b7132887a03..daf5aec07fbf 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1613,7 +1613,7 @@ static int dwc3_core_get_phy(struct dwc3 *dwc)
}
}
- return 0;
+ return dwc3_post_phy_registration(dwc);
}
static int dwc3_core_init_mode(struct dwc3 *dwc)
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 608daeb7ef10..e9d8df0a298e 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -995,10 +995,12 @@ struct dwc3_scratchpad_array {
* need to be passed on to glue layer
* @pre_set_role: Notify glue of role switch notifications
* @pre_run_stop: Notify run stop enable/disable information to glue
+ * @post_phy_registration: Called directly after PHY got registered
*/
struct dwc3_glue_ops {
void (*pre_set_role)(struct dwc3 *dwc, enum usb_role role);
void (*pre_run_stop)(struct dwc3 *dwc, bool is_on);
+ int (*post_phy_registration)(struct dwc3 *dwc);
};
/**
@@ -1655,6 +1657,13 @@ static inline void dwc3_pre_run_stop(struct dwc3 *dwc, bool is_on)
dwc->glue_ops->pre_run_stop(dwc, is_on);
}
+static inline int dwc3_post_phy_registration(struct dwc3 *dwc)
+{
+ if (dwc->glue_ops && dwc->glue_ops->post_phy_registration)
+ return dwc->glue_ops->post_phy_registration(dwc);
+ return 0;
+}
+
#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_host_init(struct dwc3 *dwc);
void dwc3_host_exit(struct dwc3 *dwc);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v18 5/7] usb: dwc3: rockchip: add USBDP PHY reset quirk
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
` (3 preceding siblings ...)
2026-10-21 18:05 ` [PATCH v18 4/7] usb: dwc3: core: add post PHY registration hook for platform glue Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:35 ` sashiko-bot
2026-10-21 18:05 ` [PATCH v18 6/7] usb: gadget: define stub for usb_udc_vbus_handler Sebastian Reichel
2026-10-21 18:05 ` [PATCH v18 7/7] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel
On recent Rockchip platforms (at least RK3588 & RK3576), DWC3 IP is used
with a USBDP PHY providing USB3 and DP. This PHY needs to be reset when
the mode changes, which may happen when plugging in different USB-C
devices.
If the USBDP PHY resets with the DWC3 IP running, its internal state
corrupts resulting in the USBDP PHY not being able to lock some PLL
clocks, which effectively renders USB3 unusable.
To fix the issue this adds a Rockchip USBDP specific quirk, which will
assert PHYSOFTRST before the actual PHY is disabled and will deassert it
once the PHY returns.
Note, that the same platforms also use DWC3 with a different PHY
(rockchip-naneng-combphy), so the PHY calls are protected by a
driver name check.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/dwc3/Kconfig | 1 +
drivers/usb/dwc3/dwc3-rockchip.c | 96 +++++++++++++++++++++++++++++++++++++++-
drivers/usb/dwc3/trace.c | 3 ++
3 files changed, 99 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 3c120ea9746d..c86c1ac293bf 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -193,6 +193,7 @@ config USB_DWC3_OCTEON
config USB_DWC3_ROCKCHIP
tristate "Rockchip DWC3 Platform Driver"
depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on PHY_ROCKCHIP_USBDP || !PHY_ROCKCHIP_USBDP
depends on OF
default USB_DWC3
help
diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
index 4129aefd6a8b..75e647e04cc5 100644
--- a/drivers/usb/dwc3/dwc3-rockchip.c
+++ b/drivers/usb/dwc3/dwc3-rockchip.c
@@ -2,11 +2,105 @@
/* Copyright (c) 2026, Collabora Ltd. */
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/phy/rockchip-usbdp.h>
#include <linux/pm_runtime.h>
#include "glue.h"
+#include "io.h"
struct dwc3_rockchip {
struct dwc3 dwc;
+ u8 phy_reset_active;
+};
+
+static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
+{
+ struct dwc3 *dwc = &dwc_rk->dwc;
+ u32 reg;
+
+ /*
+ * If the device is runtime suspended, there is nothing to do and RPM
+ * resume will do the necessary things. There is no need for a RPM
+ * reference either, since DWC3 runtime suspend/resume will call phy
+ * init/exit and that is synchronized against ongoing resets.
+ */
+ if (!pm_runtime_active(dwc->dev))
+ return;
+
+ /*
+ * Assert USB3 PHY soft reset within DWC3 before the external
+ * PHY resets. This disconnects the PIPE interface, preventing
+ * the DWC3 from interfering with PHY reinitialization and
+ * avoiding LCPLL lock failures.
+ */
+ scoped_guard(spinlock_irqsave, &dwc->lock) {
+ dwc_rk->phy_reset_active |= BIT(port);
+ reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(port));
+ reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
+ dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
+ }
+}
+
+static void dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
+{
+ struct dwc3 *dwc = &dwc_rk->dwc;
+ u32 reg;
+
+ scoped_guard(spinlock_irqsave, &dwc->lock) {
+ if (!(dwc_rk->phy_reset_active & BIT(port)))
+ return;
+
+ dwc_rk->phy_reset_active &= ~BIT(port);
+
+ /* Restore normal operation */
+ reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(port));
+ reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
+ dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
+ }
+}
+
+static void dwc3_rockchip_phy_reset(void *data, int port, bool pre)
+{
+ struct dwc3_rockchip *dwc_rk = data;
+
+ if (pre)
+ dwc3_rockchip_phy_pre_reset(dwc_rk, port);
+ else
+ dwc3_rockchip_phy_post_reset(dwc_rk, port);
+}
+
+static void dwc3_rk_phy_unregister_notifiers(void *data)
+{
+ struct dwc3_rockchip *dwc_rk = data;
+ struct dwc3 *dwc = &dwc_rk->dwc;
+ int i;
+
+ for (i = 0; i < dwc->num_usb3_ports; i++) {
+ struct phy *phy = dwc->usb3_generic_phy[i];
+
+ if (phy && !strcmp(dev_driver_string(phy->dev.parent), "rockchip-usbdp-phy"))
+ rk_udphy_unregister_reset_handler(phy);
+ }
+
+ WARN_ON(dwc_rk->phy_reset_active);
+}
+
+static int dwc3_rk_phy_register_reset_handler(struct dwc3 *dwc)
+{
+ struct dwc3_rockchip *dwc_rk = container_of(dwc, struct dwc3_rockchip, dwc);
+ int i;
+
+ for (i = 0; i < dwc->num_usb3_ports; i++) {
+ struct phy *phy = dwc->usb3_generic_phy[i];
+
+ if (phy && !strcmp(dev_driver_string(phy->dev.parent), "rockchip-usbdp-phy"))
+ rk_udphy_register_reset_handler(phy, dwc3_rockchip_phy_reset, dwc_rk, i);
+ }
+
+ return devm_add_action_or_reset(dwc->dev, dwc3_rk_phy_unregister_notifiers, dwc_rk);
+}
+
+static struct dwc3_glue_ops dwc3_rockchip_glue_ops = {
+ .post_phy_registration = dwc3_rk_phy_register_reset_handler,
};
static int dwc3_rockchip_probe(struct platform_device *pdev)
@@ -26,7 +120,7 @@ static int dwc3_rockchip_probe(struct platform_device *pdev)
return -ENOMEM;
dwc_rk->dwc.dev = &pdev->dev;
- dwc_rk->dwc.glue_ops = NULL;
+ dwc_rk->dwc.glue_ops = &dwc3_rockchip_glue_ops;
probe_data.dwc = &dwc_rk->dwc;
probe_data.res = res;
diff --git a/drivers/usb/dwc3/trace.c b/drivers/usb/dwc3/trace.c
index 088995885678..8c4e2a7b142e 100644
--- a/drivers/usb/dwc3/trace.c
+++ b/drivers/usb/dwc3/trace.c
@@ -9,3 +9,6 @@
#define CREATE_TRACE_POINTS
#include "trace.h"
+
+EXPORT_TRACEPOINT_SYMBOL_GPL(dwc3_readl);
+EXPORT_TRACEPOINT_SYMBOL_GPL(dwc3_writel);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v18 5/7] usb: dwc3: rockchip: add USBDP PHY reset quirk
2026-10-21 18:05 ` [PATCH v18 5/7] usb: dwc3: rockchip: add USBDP PHY reset quirk Sebastian Reichel
@ 2026-10-21 18:35 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-21 18:35 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: linux-usb, linux-phy, vkoul, neil.armstrong, olteanv
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] usb: dwc3: rockchip: dwc3_rockchip_phy_pre_reset() skips RPM_RESUMING state
- [High] usb: dwc3: rockchip: unsynchronized runtime PM in dwc3_rockchip_phy_reset()
- [Medium] usb: dwc3: rockchip: race between PHY reset quirk and __dwc3_set_mode()
--
commit 175128dd0be21c712a951377a4fee0fe496c4cf7
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
usb: dwc3: rockchip: add USBDP PHY reset quirk
This commit adds a reset handler for the Rockchip USBDP PHY in the Rockchip
DWC3 glue driver to assert and deassert DWC3_GUSB3PIPECTL_PHYSOFTRST around
external PHY resets, preventing DWC3 PIPE traffic from interfering with PHY
PLL locking. It also exports the dwc3_readl and dwc3_writel tracepoint
symbols for use by the glue driver.
> diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
> index 4129aefd6a8b0..75e647e04cc52 100644
> --- a/drivers/usb/dwc3/dwc3-rockchip.c
> +++ b/drivers/usb/dwc3/dwc3-rockchip.c
> @@ -2,11 +2,105 @@
> /* Copyright (c) 2026, Collabora Ltd. */
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/phy/rockchip-usbdp.h>
> #include <linux/pm_runtime.h>
> #include "glue.h"
> +#include "io.h"
>
> struct dwc3_rockchip {
> struct dwc3 dwc;
> + u8 phy_reset_active;
> +};
> +
> +static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
> +{
> + struct dwc3 *dwc = &dwc_rk->dwc;
> + u32 reg;
> +
> + /*
> + * If the device is runtime suspended, there is nothing to do and RPM
> + * resume will do the necessary things. There is no need for a RPM
> + * reference either, since DWC3 runtime suspend/resume will call phy
> + * init/exit and that is synchronized against ongoing resets.
> + */
> + if (!pm_runtime_active(dwc->dev))
> + return;
[Severity: High]
Does checking pm_runtime_active(dwc->dev) here miss the case where DWC3 is in
the middle of runtime resume or runtime suspend?
In drivers/base/power/runtime.c:rpm_resume(), the PM core sets
dev->power.runtime_status to RPM_RESUMING before invoking the driver's
runtime_resume callback, and only updates it to RPM_ACTIVE after the callback
returns:
drivers/base/power/runtime.c:rpm_resume() {
...
__update_runtime_status(dev, RPM_RESUMING);
callback = RPM_GET_CALLBACK(dev, runtime_resume);
dev_pm_disable_wake_irq_check(dev, false);
retval = rpm_callback(callback, dev);
...
}
Meanwhile, include/linux/pm_runtime.h:pm_runtime_active() only returns true
when runtime_status is RPM_ACTIVE (or runtime PM is disabled):
static inline bool pm_runtime_active(struct device *dev)
{
return dev->power.runtime_status == RPM_ACTIVE
|| dev->power.disable_depth;
}
During runtime resume in DWC3_GCTL_PRTCAP_DEVICE mode, the call chain is:
dwc3_rockchip_runtime_resume()
-> dwc3_runtime_resume()
-> dwc3_resume_common()
-> dwc3_core_init_for_resume()
-> reset_control_deassert(dwc->reset)
-> dwc3_clk_enable(dwc)
-> dwc3_core_init(dwc)
-> dwc3_phy_setup(dwc)
-> dwc3_phy_init(dwc)
-> phy_init()
-> rk_udphy_usb3_phy_init()
-> rk_udphy_power_on()
-> rk_udphy_run_reset_handler(udphy, true)
-> dwc3_rockchip_phy_pre_reset()
At that point, DWC3 reset has been deasserted, clocks are enabled, and
dwc3_phy_setup() has configured DWC3_GUSB3PIPECTL(0), but
dev->power.runtime_status is still RPM_RESUMING. When rk_udphy_power_on()
runs the reset handler (because udphy->status == UDPHY_MODE_NONE or
udphy->phy_needs_reinit is true after a USB-C reconnect or orientation
change), won't dwc3_rockchip_phy_pre_reset() see !pm_runtime_active(dwc->dev)
and return early without asserting DWC3_GUSB3PIPECTL_PHYSOFTRST, causing
rk_udphy_status_check() to fail LCPLL lock and failing dwc3_runtime_resume()
(as well as skipping the VBUS notification in the follow-up commit "usb:
dwc3: rockchip: fix USB-C reconnect in gadget mode")?
Similarly, during runtime suspend, rpm_suspend() sets runtime_status to
RPM_SUSPENDING before invoking the driver callback. While
dwc3_suspend_common() is executing dwc3_gadget_suspend(), synchronize_irq(),
or waiting on udphy->mutex in dwc3_phy_exit() before clocks are disabled and
reset is asserted, wouldn't a concurrent PHY reset via
rk_udphy_dp_phy_power_on() also see !pm_runtime_active(dwc->dev) and skip
asserting DWC3_GUSB3PIPECTL_PHYSOFTRST while DWC3 is still running?
> +
> + /*
> + * Assert USB3 PHY soft reset within DWC3 before the external
> + * PHY resets. This disconnects the PIPE interface, preventing
> + * the DWC3 from interfering with PHY reinitialization and
> + * avoiding LCPLL lock failures.
> + */
> + scoped_guard(spinlock_irqsave, &dwc->lock) {
> + dwc_rk->phy_reset_active |= BIT(port);
> + reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(port));
> + reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
> + dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
> + }
> +}
[Severity: Medium]
Can the PHY reset callbacks race with DWC3 role switching in
__dwc3_set_mode()?
When a USB-C device with DisplayPort AltMode is plugged in, unplugged, or
undergoes a role swap, tcpm_mux_set() updates the mux/orientation state
(setting udphy->phy_needs_reinit = true in rk_udphy_orien_sw_set() or
rk_udphy_typec_mux_set()) and calls usb_role_switch_set_role() ->
dwc3_usb_role_switch_set() -> dwc3_set_mode(), queuing __dwc3_set_mode() on
system_freezable_wq. Concurrently, DP HPD/link enablement invokes
phy_power_on(dp->phy) -> rk_udphy_dp_phy_power_on() -> rk_udphy_power_on()
under udphy->mutex.
First, __dwc3_set_mode() calls dwc3_host_exit(), dwc3_gadget_exit(), and
dwc3_host_init() -> dwc3_xhci_plat_start(), all of which call
dwc3_enable_susphy() without holding dwc->lock:
drivers/usb/dwc3/core.c:dwc3_enable_susphy() {
...
for (i = 0; i < dwc->num_usb3_ports; i++) {
reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(i));
if (enable && !dwc->dis_u3_susphy_quirk)
reg |= DWC3_GUSB3PIPECTL_SUSPHY;
else
reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
dwc3_writel(dwc, DWC3_GUSB3PIPECTL(i), reg);
}
}
Could an unlocked read-modify-write in dwc3_enable_susphy() interleave with
dwc3_rockchip_phy_pre_reset() or dwc3_rockchip_phy_post_reset() and clear
DWC3_GUSB3PIPECTL_PHYSOFTRST during the PHY reset or re-assert it after
dwc3_rockchip_phy_post_reset() clears it?
Second, between dwc3_rockchip_phy_pre_reset() and
dwc3_rockchip_phy_post_reset(), rk_udphy_init() sleeps in usleep_range() and
polls PLL status in regmap_read_poll_timeout() while holding only
udphy->mutex. Since __dwc3_set_mode() holds only dwc->mutex, can
__dwc3_set_mode() assert and deassert DWC3_GCTL_CORESOFTRESET during the PHY
reset window:
drivers/usb/dwc3/core.c:__dwc3_set_mode() {
...
reg = dwc3_readl(dwc, DWC3_GCTL);
reg |= DWC3_GCTL_CORESOFTRESET;
dwc3_writel(dwc, DWC3_GCTL, reg);
msleep(100);
reg = dwc3_readl(dwc, DWC3_GCTL);
reg &= ~DWC3_GCTL_CORESOFTRESET;
dwc3_writel(dwc, DWC3_GCTL, reg);
...
}
resetting DWC3 global registers and clearing DWC3_GUSB3PIPECTL_PHYSOFTRST
while the USBDP PHY reset is still in progress?
> +
> +static void dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
> +{
> + struct dwc3 *dwc = &dwc_rk->dwc;
> + u32 reg;
> +
> + scoped_guard(spinlock_irqsave, &dwc->lock) {
> + if (!(dwc_rk->phy_reset_active & BIT(port)))
> + return;
> +
> + dwc_rk->phy_reset_active &= ~BIT(port);
> +
> + /* Restore normal operation */
> + reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(port));
> + reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
> + dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
> + }
> +}
[Severity: High]
Is it safe to rely on DWC3 runtime suspend/resume calling phy_init() and
phy_exit() instead of holding a runtime PM reference across the PHY reset?
In drivers/usb/dwc3/core.c, dwc3_suspend_common() and dwc3_resume_common()
only call dwc3_core_exit() (which calls dwc3_phy_exit() -> phy_exit()) and
dwc3_core_init_for_resume() (which calls dwc3_core_init() -> dwc3_phy_init()
-> phy_init()) during runtime PM when in DWC3_GCTL_PRTCAP_DEVICE mode. In
DWC3_GCTL_PRTCAP_HOST and DWC3_GCTL_PRTCAP_OTG modes, runtime suspend and
resume skip dwc3_core_exit() and dwc3_core_init_for_resume():
drivers/usb/dwc3/core.c:dwc3_suspend_common() {
...
case DWC3_GCTL_PRTCAP_HOST:
if (!PMSG_IS_AUTO(msg) &&
(!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) {
dwc3_core_exit(dwc);
break;
}
...
case DWC3_GCTL_PRTCAP_OTG:
/* do nothing during runtime_suspend */
if (PMSG_IS_AUTO(msg))
break;
...
}
Because phy_exit() is not called on runtime suspend in host or OTG modes,
rk_udphy_usb3_phy_exit() does not run, udphy->mutex is not acquired by
runtime suspend/resume, and udphy->sw_mode retains UDPHY_MODE_USB while
dwc->dev is runtime-suspended.
If dwc->dev is RPM_ACTIVE in DWC3_GCTL_PRTCAP_HOST or DWC3_GCTL_PRTCAP_OTG
mode when dwc3_rockchip_phy_pre_reset() runs (for example, via
rk_udphy_dp_phy_power_on() -> rk_udphy_power_on()), it sets
dwc_rk->phy_reset_active |= BIT(port) without holding a runtime PM reference.
While rk_udphy_power_on() sleeps in rk_udphy_init(), dwc->dev can
runtime-suspend concurrently without blocking on udphy->mutex, allowing the
USB power domain (RK3588_PD_USB / RK3576_PD_USB) to power down. When
dwc3_rockchip_phy_post_reset() then executes, won't it see
dwc_rk->phy_reset_active & BIT(port) set and call dwc3_readl() and
dwc3_writel() on DWC3_GUSB3PIPECTL(port) while dwc->dev is runtime-suspended,
triggering an arm64 SError?
Conversely, if dwc->dev is already runtime-suspended in DWC3_GCTL_PRTCAP_HOST
or DWC3_GCTL_PRTCAP_OTG mode while DWC3 clocks and reset remain active (and
the shared USB power domain is kept on by sibling controllers), won't
dwc3_rockchip_phy_pre_reset() see !pm_runtime_active(dwc->dev) and return
early without asserting DWC3_GUSB3PIPECTL_PHYSOFTRST, causing the USBDP PHY
reset to fail LCPLL lock while subsequent runtime resume in host or OTG mode
does not reinitialize the DWC3 core or PHY?
[ ... ]
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-b4-rockchip-dwc3-rockchip-glue-v18-0-60000d03fca2@collabora.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v18 6/7] usb: gadget: define stub for usb_udc_vbus_handler
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
` (4 preceding siblings ...)
2026-10-21 18:05 ` [PATCH v18 5/7] usb: dwc3: rockchip: add USBDP PHY reset quirk Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:13 ` sashiko-bot
2026-10-21 18:05 ` [PATCH v18 7/7] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel
Platform drivers with optional gadget mode support may want to
call usb_udc_vbus_handler to properly handle a cable being
re-plugged.
Ensure these drivers can call the function without a hard dependency on
CONFIG_USB_GADGET or having the ifdef logic in the driver.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
include/linux/usb/gadget.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index 8285b19a25e0..551e71b6eda0 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -979,7 +979,12 @@ extern int usb_gadget_ep_match_desc(struct usb_gadget *gadget,
/*-------------------------------------------------------------------------*/
/* utility to update vbus status for udc core, it may be scheduled */
+#if IS_ENABLED(CONFIG_USB_GADGET)
extern void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status);
+#else
+static inline void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status)
+{}
+#endif
/*-------------------------------------------------------------------------*/
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v18 7/7] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode
2026-10-21 18:05 [PATCH v18 0/7] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
` (5 preceding siblings ...)
2026-10-21 18:05 ` [PATCH v18 6/7] usb: gadget: define stub for usb_udc_vbus_handler Sebastian Reichel
@ 2026-10-21 18:05 ` Sebastian Reichel
2026-10-21 18:31 ` sashiko-bot
6 siblings, 1 reply; 15+ messages in thread
From: Sebastian Reichel @ 2026-10-21 18:05 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: Igor Paunovic, linux-kernel, linux-usb, linux-arm-kernel,
linux-rockchip, linux-phy, kernel, Sebastian Reichel
When USB-C is configured in gadget mode and the cable is unplugged
the USB controller is suspended. After plugging in the cable again,
the USB controller stays suspended and thus the port status remains
not-attached.
Fix this by informing the USB core about VBUS updates when the PHY
mode changes.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/dwc3/Kconfig | 1 +
drivers/usb/dwc3/dwc3-rockchip.c | 27 +++++++++++++++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index c86c1ac293bf..4cbc949c6abd 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -194,6 +194,7 @@ config USB_DWC3_ROCKCHIP
tristate "Rockchip DWC3 Platform Driver"
depends on ARCH_ROCKCHIP || COMPILE_TEST
depends on PHY_ROCKCHIP_USBDP || !PHY_ROCKCHIP_USBDP
+ depends on USB_GADGET || !USB_GADGET
depends on OF
default USB_DWC3
help
diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
index 75e647e04cc5..65d17536af14 100644
--- a/drivers/usb/dwc3/dwc3-rockchip.c
+++ b/drivers/usb/dwc3/dwc3-rockchip.c
@@ -10,8 +10,19 @@
struct dwc3_rockchip {
struct dwc3 dwc;
u8 phy_reset_active;
+ enum usb_role role;
};
+static void dwc3_rockchip_vbus_handler(struct dwc3 *dwc, bool present)
+{
+ guard(spinlock_irqsave)(&dwc->lock);
+
+ if (!dwc->gadget || !dwc->gadget_driver)
+ return;
+
+ usb_udc_vbus_handler(dwc->gadget, present);
+}
+
static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
{
struct dwc3 *dwc = &dwc_rk->dwc;
@@ -26,6 +37,8 @@ static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
if (!pm_runtime_active(dwc->dev))
return;
+ dwc3_rockchip_vbus_handler(dwc, false);
+
/*
* Assert USB3 PHY soft reset within DWC3 before the external
* PHY resets. This disconnects the PIPE interface, preventing
@@ -43,6 +56,9 @@ static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
static void dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
{
struct dwc3 *dwc = &dwc_rk->dwc;
+ bool gadget_mode = (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) ||
+ ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG) &&
+ (dwc_rk->role == USB_ROLE_DEVICE));
u32 reg;
scoped_guard(spinlock_irqsave, &dwc->lock) {
@@ -56,6 +72,8 @@ static void dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
}
+
+ dwc3_rockchip_vbus_handler(dwc, gadget_mode);
}
static void dwc3_rockchip_phy_reset(void *data, int port, bool pre)
@@ -99,7 +117,16 @@ static int dwc3_rk_phy_register_reset_handler(struct dwc3 *dwc)
return devm_add_action_or_reset(dwc->dev, dwc3_rk_phy_unregister_notifiers, dwc_rk);
}
+static void dwc3_rockchip_set_role(struct dwc3 *dwc, enum usb_role role)
+{
+ struct dwc3_rockchip *dwc_rk = container_of(dwc, struct dwc3_rockchip, dwc);
+
+ dwc_rk->role = role;
+ dwc3_rockchip_vbus_handler(dwc, role == USB_ROLE_DEVICE);
+}
+
static struct dwc3_glue_ops dwc3_rockchip_glue_ops = {
+ .pre_set_role = dwc3_rockchip_set_role,
.post_phy_registration = dwc3_rk_phy_register_reset_handler,
};
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v18 7/7] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode
2026-10-21 18:05 ` [PATCH v18 7/7] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
@ 2026-10-21 18:31 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-21 18:31 UTC (permalink / raw)
To: Sebastian Reichel; +Cc: linux-usb, linux-phy, vkoul, neil.armstrong, olteanv
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] usb: dwc3: rockchip: wrong role check in dwc3_rockchip_phy_post_reset()
- [High] usb: dwc3: rockchip: async vbus_work race in dwc3_rockchip_phy_reset()
- [High] usb: dwc3: rockchip: dropped VBUS state in dwc3_rockchip_vbus_handler()
--
commit 97e99b8258d01d9f23d8408a81715cbe540da468
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
usb: dwc3: rockchip: fix USB-C reconnect in gadget mode
This commit adds a vbus handler to the Rockchip dwc3 glue driver and invokes
it from the phy reset callbacks and the .pre_set_role glue callback so that
the gadget core is notified of vbus state changes across usb-c disconnects
and reconnects.
> diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
> index 75e647e04cc52..65d17536af142 100644
> --- a/drivers/usb/dwc3/dwc3-rockchip.c
> +++ b/drivers/usb/dwc3/dwc3-rockchip.c
> @@ -10,8 +10,19 @@
> struct dwc3_rockchip {
> struct dwc3 dwc;
> u8 phy_reset_active;
> + enum usb_role role;
> };
>
> +static void dwc3_rockchip_vbus_handler(struct dwc3 *dwc, bool present)
> +{
> + guard(spinlock_irqsave)(&dwc->lock);
> +
> + if (!dwc->gadget || !dwc->gadget_driver)
> + return;
> +
> + usb_udc_vbus_handler(dwc->gadget, present);
> +}
[Severity: High]
What happens in dwc3_rockchip_vbus_handler() when a role switch occurs before
a gadget driver is bound or before dwc->gadget is initialized?
When dwc3_gadget_init() registers a gadget via usb_add_gadget(), udc->vbus is
initialized to true:
drivers/usb/gadget/udc/core.c:usb_add_gadget() {
...
usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
udc->vbus = true;
...
}
If the system boots without a usb-c cable attached, tcpm invokes
usb_role_switch_set_role() -> dwc3_usb_role_switch_set() ->
dwc3_pre_set_role() -> dwc3_rockchip_set_role(dwc, USB_ROLE_NONE) ->
dwc3_rockchip_vbus_handler(dwc, false). If no gadget driver is bound yet
(dwc->gadget_driver is NULL), dwc3_rockchip_vbus_handler() returns early and
drops the present = false update, leaving udc->vbus as true.
The same thing happens when unplugging a usb-c host cable (transitioning from
USB_ROLE_HOST to USB_ROLE_NONE): dwc3_usb_role_switch_set() calls
dwc3_pre_set_role() synchronously while still in host mode, before
dwc3_set_mode() queues dwc->drd_work (__dwc3_set_mode) to call
dwc3_gadget_init(), so the newly created udc also starts with udc->vbus set
to true while dwc_rk->role is USB_ROLE_NONE.
When userspace later binds a configfs gadget driver while unplugged,
gadget_bind_driver() calls usb_udc_connect_control_locked(), which sees
udc->vbus == true and calls usb_gadget_connect_locked(), setting
gadget->connected = 1 with no cable attached. Once dwc3 runtime-suspends,
plugging in a usb-c device cable calls usb_udc_vbus_handler(dwc->gadget, true)
-> usb_gadget_connect_locked(), which sees gadget->connected == 1 and returns
early without calling dwc3_gadget_pullup() to resume the controller.
Conversely, if a cable is unplugged while a gadget driver is bound (setting
udc->vbus = false), the gadget driver is unbound (setting dwc->gadget_driver
to NULL), and the cable is plugged back in before rebinding, the present =
true update is dropped and the gadget stays disconnected after bind.
> +
> static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
> {
> struct dwc3 *dwc = &dwc_rk->dwc;
> @@ -26,6 +37,8 @@ static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
> if (!pm_runtime_active(dwc->dev))
> return;
>
> + dwc3_rockchip_vbus_handler(dwc, false);
[Severity: High]
Can calling dwc3_rockchip_vbus_handler(dwc, false) in
dwc3_rockchip_phy_pre_reset() followed by
dwc3_rockchip_vbus_handler(dwc, gadget_mode) in
dwc3_rockchip_phy_post_reset() race with udc->vbus_work?
In drivers/usb/gadget/udc/core.c, usb_udc_vbus_handler() does not disconnect
or connect synchronously; it overwrites a single boolean and schedules
asynchronous work:
void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status)
{
struct usb_udc *udc = gadget->udc;
if (udc) {
udc->vbus = status;
schedule_work(&udc->vbus_work);
}
}
During phy initialization in rk_udphy_power_on(), rk_udphy_run_reset_handler()
calls dwc3_rockchip_phy_pre_reset(), runs rk_udphy_setup() and rk_udphy_init(),
and then calls dwc3_rockchip_phy_post_reset(). This creates several issues:
1. If udc->vbus_work (vbus_event_work) has not started before
dwc3_rockchip_phy_post_reset() runs, the second usb_udc_vbus_handler() call
overwrites udc->vbus back to true. When vbus_event_work finally runs, it
calls usb_gadget_connect_locked(), sees gadget->connected is already set,
and returns as a no-op without performing the disconnect or reconnect.
2. If vbus_event_work runs on another cpu during rk_udphy_init() while
DWC3_GUSB3PIPECTL_PHYSOFTRST is asserted and the external phy is held in
reset without pipe clocks, usb_gadget_disconnect_locked() invokes
dwc3_gadget_pullup(gadget, 0) -> dwc3_gadget_soft_disconnect() ->
dwc3_gadget_run_stop(dwc, false), issuing endpoint commands and polling
DWC3_DSTS_DEVCTRLHLT while the phy is in reset.
3. During system resume, dwc3_pm_resume() in drivers/usb/dwc3/core.c calls
pm_runtime_disable(dev) and pm_runtime_set_active(dev) before
dwc3_resume_common() -> dwc3_core_init() -> dwc3_phy_init():
pm_runtime_disable(dev);
ret = pm_runtime_set_active(dev);
if (ret)
goto out;
ret = dwc3_resume_common(dwc, PMSG_RESUME);
Because pm_runtime_active(dwc->dev) is true, the phy reset callbacks
schedule vbus_event_work while runtime pm is disabled. If vbus_event_work
runs before pm_runtime_enable(dev), dwc3_gadget_pullup() clears
dwc->softconnect, fails pm_runtime_get_sync(dwc->dev) with -EACCES, calls
pm_runtime_set_suspended(dwc->dev), and leaves gadget->connected = 1 with
dwc->softconnect = 0.
4. When dwc->dev is already runtime-suspended (!pm_runtime_active(dwc->dev))
in a fixed peripheral configuration (dr_mode == USB_DR_MODE_PERIPHERAL),
both dwc3_rockchip_phy_pre_reset() and dwc3_rockchip_phy_post_reset()
return early without calling dwc3_rockchip_vbus_handler().
> +
> /*
> * Assert USB3 PHY soft reset within DWC3 before the external
> * PHY resets. This disconnects the PIPE interface, preventing
> @@ -43,6 +56,9 @@ static void dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
> static void dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
> {
> struct dwc3 *dwc = &dwc_rk->dwc;
> + bool gadget_mode = (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) ||
> + ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG) &&
> + (dwc_rk->role == USB_ROLE_DEVICE));
> u32 reg;
>
> scoped_guard(spinlock_irqsave, &dwc->lock) {
> @@ -56,6 +72,8 @@ static void dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
> reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
> dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
> }
> +
> + dwc3_rockchip_vbus_handler(dwc, gadget_mode);
> }
[Severity: High]
Does gadget_mode in dwc3_rockchip_phy_post_reset() check the right field on
dual-role controllers using usb-role-switch?
Here dwc->current_dr_role is compared against DWC3_GCTL_PRTCAP_DEVICE and
DWC3_GCTL_PRTCAP_OTG. However, in drivers/usb/dwc3/drd.c,
dwc3_usb_role_switch_set() never sets mode to DWC3_GCTL_PRTCAP_OTG:
switch (role) {
case USB_ROLE_HOST:
mode = DWC3_GCTL_PRTCAP_HOST;
break;
case USB_ROLE_DEVICE:
mode = DWC3_GCTL_PRTCAP_DEVICE;
break;
default:
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST)
mode = DWC3_GCTL_PRTCAP_HOST;
else
mode = DWC3_GCTL_PRTCAP_DEVICE;
break;
}
dwc3_pre_set_role(dwc, role);
dwc3_set_mode(dwc, mode);
When the cable is unplugged or in displayport-only altmode (role is
USB_ROLE_NONE), dwc3_usb_role_switch_set() selects DWC3_GCTL_PRTCAP_DEVICE
because dwc->role_switch_default_mode defaults to USB_DR_MODE_PERIPHERAL.
As a result, dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE evaluates to true
even when dwc_rk->role is USB_ROLE_NONE. When a phy reset completes in
dwc3_rockchip_phy_post_reset() (for example during rk_udphy_dp_phy_power_on()
or system resume), dwc3_rockchip_vbus_handler(dwc, true) is called while no
usb device cable is connected, setting gadget->connected = 1 so that a later
USB_ROLE_DEVICE plug-in becomes a no-op in usb_gadget_connect_locked().
In addition, dwc3_pre_set_role() updates dwc_rk->role synchronously in
dwc3_rockchip_set_role(), whereas dwc3_set_mode() queues dwc->drd_work
(__dwc3_set_mode) asynchronously. During a USB_ROLE_DEVICE to USB_ROLE_HOST
transition, dwc->current_dr_role is still DWC3_GCTL_PRTCAP_DEVICE (making
gadget_mode true while dwc_rk->role is USB_ROLE_HOST), and during a
USB_ROLE_HOST to USB_ROLE_DEVICE transition, dwc->current_dr_role is still
DWC3_GCTL_PRTCAP_HOST until __dwc3_set_mode() finishes (making gadget_mode
false while dwc_rk->role is USB_ROLE_DEVICE).
Also, dwc->current_dr_role and dwc_rk->role are read at the top of
dwc3_rockchip_phy_post_reset() outside dwc->lock, and dwc_rk->role is written
in dwc3_rockchip_set_role() without holding dwc->lock:
static void dwc3_rockchip_set_role(struct dwc3 *dwc, enum usb_role role)
{
struct dwc3_rockchip *dwc_rk = container_of(dwc, struct dwc3_rockchip, dwc);
dwc_rk->role = role;
dwc3_rockchip_vbus_handler(dwc, role == USB_ROLE_DEVICE);
}
Could a concurrent dwc3_rockchip_set_role(dwc, USB_ROLE_NONE) run between the
initial gadget_mode evaluation and the dwc3_rockchip_vbus_handler() call at
the end of dwc3_rockchip_phy_post_reset(), causing the stale gadget_mode value
to overwrite the vbus state? Should this check dwc->dr_mode ==
USB_DR_MODE_PERIPHERAL (and dwc_rk->role == USB_ROLE_DEVICE for otg) under
dwc->lock instead?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-b4-rockchip-dwc3-rockchip-glue-v18-0-60000d03fca2@collabora.com?part=7
^ permalink raw reply [flat|nested] 15+ messages in thread