[PATCH v17 5/7] usb: dwc3: rockchip: add USBDP PHY reset quirk
From: Sebastian Reichel
Date: Fri Oct 16 2026 - 14:59:13 EST
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@xxxxxxxxxxxxx>
---
drivers/usb/dwc3/Kconfig | 1 +
drivers/usb/dwc3/dwc3-rockchip.c | 135 ++++++++++++++++++++++++++++++++++++++-
drivers/usb/dwc3/trace.c | 3 +
3 files changed, 138 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..1f32150caa09 100644
--- a/drivers/usb/dwc3/dwc3-rockchip.c
+++ b/drivers/usb/dwc3/dwc3-rockchip.c
@@ -2,11 +2,144 @@
/* Copyright (c) 2026, Collabora Ltd. */
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/rockchip-usbdp.h>
#include <linux/pm_runtime.h>
#include "glue.h"
+#include "io.h"
+
+struct dwc3_rockchip;
+
+/**
+ * struct dwc3_rk_phy_nb - wrapper for PHY notifier block
+ * @nb: notifier block
+ * @dwc: back-pointer to the DWC3 controller
+ * @port_index: USB3 port index this notifier is registered for
+ */
+struct dwc3_rk_phy_nb {
+ struct notifier_block nb;
+ struct dwc3_rockchip *dwc_rk;
+ u8 port_index;
+};
struct dwc3_rockchip {
struct dwc3 dwc;
+ u8 phy_reset_active;
+};
+
+static int dwc3_rockchip_phy_pre_reset(struct dwc3_rockchip *dwc_rk, int port)
+{
+ struct dwc3 *dwc = &dwc_rk->dwc;
+ unsigned long flags;
+ u32 reg;
+ int ret;
+
+ /*
+ * If already suspended, the resume path will reinit GUSB3PIPECTL
+ * via dwc3_core_init(). A forced resume is not possible as that
+ * would call phy_init() resulting in a deadlock. Due to the
+ * phy_init() in the resume path there is also no need to block
+ * async RPM resume on our side, since the PHY synchronizes it
+ * for us.
+ *
+ * pm_runtime_get_if_active() returns 0 when suspended (skip),
+ * 1 when active (ref held), or -EINVAL when PM is disabled
+ * (device always active). In the -EINVAL case PM ref counting
+ * is a no-op, so the unconditional put in POST_RESET is safe.
+ */
+ ret = pm_runtime_get_if_active(dwc->dev);
+ if (!ret)
+ return ret;
+
+ /*
+ * 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.
+ */
+ spin_lock_irqsave(&dwc->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return 0;
+}
+
+static int dwc3_rockchip_phy_post_reset(struct dwc3_rockchip *dwc_rk, int port)
+{
+ struct dwc3 *dwc = &dwc_rk->dwc;
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ if (!(dwc_rk->phy_reset_active & BIT(port))) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return 0;
+ }
+
+ 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);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ pm_runtime_put_autosuspend(dwc->dev);
+
+ return 0;
+}
+
+static int dwc3_rockchip_phy_reset(void *data, int port, bool pre)
+{
+ struct dwc3_rockchip *dwc_rk = data;
+
+ if (pre)
+ return dwc3_rockchip_phy_pre_reset(dwc_rk, port);
+ else
+ return 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 (!strcmp(dev_driver_string(phy->dev.parent), "rockchip-usbdp-phy"))
+ rk_udphy_unregister_reset_handler(dwc->usb3_generic_phy[i]);
+ }
+
+ /* Release any PM references from in-flight resets */
+ for (i = 0; i < dwc->num_usb3_ports; i++) {
+ if (dwc_rk->phy_reset_active & BIT(i))
+ pm_runtime_put_autosuspend(dwc->dev);
+ }
+ dwc_rk->phy_reset_active = 0;
+}
+
+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 (!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 +159,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