* [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver
@ 2026-09-15 15:17 Sebastian Reichel
2026-09-15 15:17 ` [PATCH 1/5] phy: core: add notifier infrastructure Sebastian Reichel
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Sebastian Reichel @ 2026-09-15 15:17 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: linux-kernel, linux-usb, linux-arm-kernel, linux-rockchip,
linux-phy, kernel, Sebastian Reichel, Igor Paunovic
The recent Rockchip platform have a combined PHY, which does USB3 and
DisplayPort as well as muxing the lines in arbitrary order. Changing
its enabled functionality requires a PHY reset and as the PHY provides
a clock to the DWC3 controller, this can result in an SError. Apart
from that the PHY might not start up correctly when the DWC3 PIPE
interface is running. This series prepares a Rockchip specific DWC3
glue driver to help out with these problems. To become useful more
patches are needed on the Rockchip USBDP PHY side, which are send
separately.
This was split from the USBDP cleanup series. It has no dependencies,
but is required for part 3 for the USBDP cleanup series (which will
be the final part).
Changes in v15:
- Link to v14: https://patch.msgid.link/20260813-rockchip-usbdp-cleanup-v14-0-b5ad9c68fa11@collabora.com
- Avoid #ifdef for CONFIG_PM in the new glue driver (Thinh Nguyen)
- Do not add #if IS_ENABLED around the DT compatible blacklist in the
generic driver, using the glue driver should be mandatory as the
generic driver results in arbitrary system hangs. Thinh Nguyen suggested
updating Kconfig for this, but did not clarify what that means.
Current Kconfig works the same way as the other mandatory glue
drivers.
- Split series to simplify review
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
Sebastian Reichel (5):
phy: core: add notifier infrastructure
usb: dwc3: rockchip: introduce glue driver
usb: dwc3: core: add post PHY registration hook for platform glue
usb: dwc3: rockchip: support PHY reset notifications
usb: dwc3: rockchip: fix USB-C reconnect in gadget mode
drivers/phy/phy-core.c | 65 ++++++++++
drivers/usb/dwc3/Kconfig | 11 ++
drivers/usb/dwc3/Makefile | 1 +
drivers/usb/dwc3/core.c | 17 ++-
drivers/usb/dwc3/core.h | 9 ++
drivers/usb/dwc3/dwc3-rockchip.c | 254 +++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc3/trace.c | 3 +
include/linux/phy/phy.h | 40 ++++++
8 files changed, 399 insertions(+), 1 deletion(-)
---
base-commit: e5e04726cdd043e309677071ab1b65a4b18f422b
change-id: 20260914-b4-rockchip-dwc3-rockchip-glue-979ea3735d43
Best regards,
--
Sebastian Reichel <sebastian.reichel@collabora.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/5] phy: core: add notifier infrastructure
2026-09-15 15:17 [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
@ 2026-09-15 15:17 ` Sebastian Reichel
2026-09-15 15:17 ` [PATCH 2/5] usb: dwc3: rockchip: introduce glue driver Sebastian Reichel
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Sebastian Reichel @ 2026-09-15 15:17 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: linux-kernel, linux-usb, linux-arm-kernel, linux-rockchip,
linux-phy, kernel, Sebastian Reichel, Igor Paunovic
Some PHY devices with multiple ports (e.g. USB3 and DP) require a reset
if the configuration changes or cable orientation changes. This is a
problem, as the consumer device will run into undefined behavior.
With the new PHY notifier API introduced in this patch, the consumer
driver can hook into reset events coming from a PHY device to handle the
PHY going down gracefully.
Note that this uses -ENOSYS instead of the more sensible -ENOTSUP for
the stub functions when GENERIC_PHY is disabled to stay consistent with
the existing ones.
Tested-by: Igor Paunovic <royalnet026@gmail.com> # Orange Pi 5 Plus
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/phy-core.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/phy/phy.h | 40 ++++++++++++++++++++++++++++++
2 files changed, 105 insertions(+)
diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c
index 21aaf2f76e53..51d261daae7a 100644
--- a/drivers/phy/phy-core.c
+++ b/drivers/phy/phy-core.c
@@ -542,6 +542,70 @@ int phy_notify_state(struct phy *phy, union phy_notify state)
}
EXPORT_SYMBOL_GPL(phy_notify_state);
+/**
+ * phy_register_notifier() - register a notifier for PHY events
+ * @phy: the phy returned by phy_get()
+ * @nb: notifier block to register
+ *
+ * Allows PHY consumers to receive notifications about PHY reset events.
+ * PHY providers can signal these events using phy_notify_reset().
+ *
+ * Returns: %0 if successful, a negative error code otherwise
+ */
+int phy_register_notifier(struct phy *phy, struct notifier_block *nb)
+{
+ if (!phy)
+ return 0;
+
+ return blocking_notifier_chain_register(&phy->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(phy_register_notifier);
+
+/**
+ * phy_unregister_notifier() - unregister a notifier for PHY events
+ * @phy: the phy returned by phy_get()
+ * @nb: notifier block to unregister
+ *
+ * Returns: %0 if successful, a negative error code otherwise
+ */
+int phy_unregister_notifier(struct phy *phy, struct notifier_block *nb)
+{
+ if (!phy)
+ return 0;
+
+ return blocking_notifier_chain_unregister(&phy->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(phy_unregister_notifier);
+
+/**
+ * phy_notify_reset() - notify consumers of a PHY reset event
+ * @phy: the phy that is being reset
+ * @event: the notification event (PRE_RESET or POST_RESET)
+ *
+ * Called by PHY providers to notify consumers that the PHY is about to
+ * be reset or has completed a reset. This allows consumers to quiesce
+ * hardware before the PHY becomes unavailable.
+ *
+ * This may be called from within PHY provider callbacks (e.g. set_mode,
+ * power_on) where phy->mutex is held. Consumer notification handlers must
+ * therefore NOT call back into the PHY framework (e.g. phy_power_off,
+ * phy_exit) on the same PHY, as this would result in a deadlock.
+ *
+ * Returns: %0 if successful or no notifiers registered, a negative error
+ * code if a notifier returns an error (for PRE_RESET only)
+ */
+int phy_notify_reset(struct phy *phy, enum phy_notification event)
+{
+ int ret;
+
+ if (!phy)
+ return 0;
+
+ ret = blocking_notifier_call_chain(&phy->notifier, event, phy);
+ return notifier_to_errno(ret);
+}
+EXPORT_SYMBOL_GPL(phy_notify_reset);
+
/**
* phy_configure() - Changes the phy parameters
* @phy: the phy returned by phy_get()
@@ -1018,6 +1082,7 @@ struct phy *phy_create(struct device *dev, struct device_node *node,
device_initialize(&phy->dev);
lockdep_register_key(&phy->lockdep_key);
mutex_init_with_key(&phy->mutex, &phy->lockdep_key);
+ BLOCKING_INIT_NOTIFIER_HEAD(&phy->notifier);
phy->dev.class = &phy_class;
phy->dev.parent = dev;
diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h
index ea47975e288a..3779a4d0a02c 100644
--- a/include/linux/phy/phy.h
+++ b/include/linux/phy/phy.h
@@ -11,6 +11,7 @@
#define __DRIVERS_PHY_H
#include <linux/err.h>
+#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/pm_runtime.h>
@@ -53,6 +54,16 @@ enum phy_media {
PHY_MEDIA_DAC,
};
+/**
+ * enum phy_notification - PHY notification events
+ * @PHY_NOTIFY_PRE_RESET: PHY is about to be reset, consumers should quiesce
+ * @PHY_NOTIFY_POST_RESET: PHY reset is complete, consumers may resume
+ */
+enum phy_notification {
+ PHY_NOTIFY_PRE_RESET,
+ PHY_NOTIFY_POST_RESET,
+};
+
enum phy_ufs_state {
PHY_UFS_HIBERN8_ENTER,
PHY_UFS_HIBERN8_EXIT,
@@ -170,6 +181,7 @@ struct phy_attrs {
* @power_count: used to protect when the PHY is used by multiple consumers
* @attrs: used to specify PHY specific attributes
* @pwr: power regulator associated with the phy
+ * @notifier: notifier head for PHY reset events
* @debugfs: debugfs directory
*/
struct phy {
@@ -182,6 +194,7 @@ struct phy {
int power_count;
struct phy_attrs attrs;
struct regulator *pwr;
+ struct blocking_notifier_head notifier;
struct dentry *debugfs;
};
@@ -267,6 +280,9 @@ int phy_calibrate(struct phy *phy);
int phy_notify_connect(struct phy *phy, int port);
int phy_notify_disconnect(struct phy *phy, int port);
int phy_notify_state(struct phy *phy, union phy_notify state);
+int phy_register_notifier(struct phy *phy, struct notifier_block *nb);
+int phy_unregister_notifier(struct phy *phy, struct notifier_block *nb);
+int phy_notify_reset(struct phy *phy, enum phy_notification event);
static inline int phy_get_bus_width(struct phy *phy)
{
return phy->attrs.bus_width;
@@ -428,6 +444,30 @@ static inline int phy_notify_state(struct phy *phy, union phy_notify state)
return -ENOSYS;
}
+static inline int phy_register_notifier(struct phy *phy,
+ struct notifier_block *nb)
+{
+ if (!phy)
+ return 0;
+ return -ENOSYS;
+}
+
+static inline int phy_unregister_notifier(struct phy *phy,
+ struct notifier_block *nb)
+{
+ if (!phy)
+ return 0;
+ return -ENOSYS;
+}
+
+static inline int phy_notify_reset(struct phy *phy,
+ enum phy_notification event)
+{
+ if (!phy)
+ return 0;
+ return -ENOSYS;
+}
+
static inline int phy_configure(struct phy *phy,
union phy_configure_opts *opts)
{
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/5] usb: dwc3: rockchip: introduce glue driver
2026-09-15 15:17 [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
2026-09-15 15:17 ` [PATCH 1/5] phy: core: add notifier infrastructure Sebastian Reichel
@ 2026-09-15 15:17 ` Sebastian Reichel
2026-09-15 15:17 ` [PATCH 3/5] usb: dwc3: core: add post PHY registration hook for platform glue Sebastian Reichel
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Sebastian Reichel @ 2026-09-15 15:17 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: linux-kernel, linux-usb, linux-arm-kernel, linux-rockchip,
linux-phy, kernel, Sebastian Reichel, Igor Paunovic
Introduce Rockchip specific glue code for the Synopsys DWC3 USB driver.
For now this handles things identical to the default glue.
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..62f2a03b08a2
--- /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 __maybe_unused dwc3_rockchip_runtime_suspend(struct device *dev)
+{
+ return dwc3_runtime_suspend(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused dwc3_rockchip_runtime_resume(struct device *dev)
+{
+ return dwc3_runtime_resume(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused dwc3_rockchip_runtime_idle(struct device *dev)
+{
+ return dwc3_runtime_idle(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused dwc3_rockchip_suspend(struct device *dev)
+{
+ return dwc3_pm_suspend(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused dwc3_rockchip_resume(struct device *dev)
+{
+ return dwc3_pm_resume(dev_get_drvdata(dev));
+}
+
+static void __maybe_unused dwc3_rockchip_complete(struct device *dev)
+{
+ dwc3_pm_complete(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused 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 = {
+ SET_SYSTEM_SLEEP_PM_OPS(dwc3_rockchip_suspend, dwc3_rockchip_resume)
+ SET_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 [flat|nested] 7+ messages in thread
* [PATCH 3/5] usb: dwc3: core: add post PHY registration hook for platform glue
2026-09-15 15:17 [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
2026-09-15 15:17 ` [PATCH 1/5] phy: core: add notifier infrastructure Sebastian Reichel
2026-09-15 15:17 ` [PATCH 2/5] usb: dwc3: rockchip: introduce glue driver Sebastian Reichel
@ 2026-09-15 15:17 ` Sebastian Reichel
2026-09-15 15:17 ` [PATCH 4/5] usb: dwc3: rockchip: support PHY reset notifications Sebastian Reichel
2026-09-15 15:17 ` [PATCH 5/5] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
4 siblings, 0 replies; 7+ messages in thread
From: Sebastian Reichel @ 2026-09-15 15:17 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: linux-kernel, linux-usb, linux-arm-kernel, linux-rockchip,
linux-phy, kernel, Sebastian Reichel, Igor Paunovic
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
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 [flat|nested] 7+ messages in thread
* [PATCH 4/5] usb: dwc3: rockchip: support PHY reset notifications
2026-09-15 15:17 [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
` (2 preceding siblings ...)
2026-09-15 15:17 ` [PATCH 3/5] usb: dwc3: core: add post PHY registration hook for platform glue Sebastian Reichel
@ 2026-09-15 15:17 ` Sebastian Reichel
2026-09-15 15:17 ` [PATCH 5/5] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
4 siblings, 0 replies; 7+ messages in thread
From: Sebastian Reichel @ 2026-09-15 15:17 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: linux-kernel, linux-usb, linux-arm-kernel, linux-rockchip,
linux-phy, kernel, Sebastian Reichel, Igor Paunovic
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 handling for the new PHY framework reset
notifications, which will assert PHYSOFTRST before the actual PHY
is disabled and will deassert it once the PHY returns.
Tested-by: Igor Paunovic <royalnet026@gmail.com> # Orange Pi 5 Plus
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/dwc3/dwc3-rockchip.c | 127 ++++++++++++++++++++++++++++++++++++++-
drivers/usb/dwc3/trace.c | 3 +
2 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
index 62f2a03b08a2..7bdd6e2eb22d 100644
--- a/drivers/usb/dwc3/dwc3-rockchip.c
+++ b/drivers/usb/dwc3/dwc3-rockchip.c
@@ -2,11 +2,136 @@
/* Copyright (c) 2026, Collabora Ltd. */
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/phy/phy.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;
+ struct dwc3_rk_phy_nb usb3_phy_nb[DWC3_USB3_MAX_PORTS];
+ u8 phy_reset_active;
+};
+
+static int dwc3_usb3_phy_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct dwc3_rk_phy_nb *pnb = container_of(nb, struct dwc3_rk_phy_nb, nb);
+ struct dwc3_rockchip *dwc_rk = pnb->dwc_rk;
+ struct dwc3 *dwc = &dwc_rk->dwc;
+ int port = pnb->port_index;
+ unsigned long flags;
+ u32 reg;
+ int ret;
+
+ switch (action) {
+ case PHY_NOTIFY_PRE_RESET:
+ /*
+ * 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 NOTIFY_OK;
+
+ /*
+ * 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);
+ break;
+
+ case PHY_NOTIFY_POST_RESET:
+ spin_lock_irqsave(&dwc->lock, flags);
+ if (!(dwc_rk->phy_reset_active & BIT(port))) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return NOTIFY_OK;
+ }
+
+ dwc_rk->phy_reset_active &= ~BIT(port);
+
+ /*
+ * Deassert PHY soft reset to reconnect the PIPE interface
+ * after PHY reinitialization.
+ */
+ 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);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+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++)
+ phy_unregister_notifier(dwc->usb3_generic_phy[i],
+ &dwc_rk->usb3_phy_nb[i].nb);
+
+ /* 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_notifiers(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++) {
+ dwc_rk->usb3_phy_nb[i].nb.notifier_call = dwc3_usb3_phy_notify;
+ dwc_rk->usb3_phy_nb[i].dwc_rk = dwc_rk;
+ dwc_rk->usb3_phy_nb[i].port_index = i;
+ phy_register_notifier(dwc->usb3_generic_phy[i],
+ &dwc_rk->usb3_phy_nb[i].nb);
+ }
+
+ 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_notifiers,
};
static int dwc3_rockchip_probe(struct platform_device *pdev)
@@ -26,7 +151,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 [flat|nested] 7+ messages in thread
* [PATCH 5/5] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode
2026-09-15 15:17 [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
` (3 preceding siblings ...)
2026-09-15 15:17 ` [PATCH 4/5] usb: dwc3: rockchip: support PHY reset notifications Sebastian Reichel
@ 2026-09-15 15:17 ` Sebastian Reichel
2026-09-18 9:10 ` Igor Paunovic
4 siblings, 1 reply; 7+ messages in thread
From: Sebastian Reichel @ 2026-09-15 15:17 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner, Vinod Koul,
Neil Armstrong, Manivannan Sadhasivam
Cc: 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 triggering a runtime PM resume when the role is changed.
The Runtime PM reference counter is immediately decreased again - the
auto-suspend time is big enough to detect the connection status, which
will then keep its own reference.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/dwc3/dwc3-rockchip.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/usb/dwc3/dwc3-rockchip.c b/drivers/usb/dwc3/dwc3-rockchip.c
index 7bdd6e2eb22d..bedec3295bb1 100644
--- a/drivers/usb/dwc3/dwc3-rockchip.c
+++ b/drivers/usb/dwc3/dwc3-rockchip.c
@@ -25,8 +25,17 @@ struct dwc3_rockchip {
struct dwc3 dwc;
struct dwc3_rk_phy_nb usb3_phy_nb[DWC3_USB3_MAX_PORTS];
u8 phy_reset_active;
+ enum usb_role role;
};
+static void dwc3_rockchip_vbus_handler(struct dwc3 *dwc, bool present)
+{
+ if (!dwc->gadget || !dwc->gadget_driver)
+ return;
+
+ usb_udc_vbus_handler(dwc->gadget, present);
+}
+
static int dwc3_usb3_phy_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
@@ -57,6 +66,8 @@ static int dwc3_usb3_phy_notify(struct notifier_block *nb,
if (!ret)
return NOTIFY_OK;
+ dwc3_rockchip_vbus_handler(dwc, false);
+
/*
* Assert USB3 PHY soft reset within DWC3 before the external
* PHY resets. This disconnects the PIPE interface, preventing
@@ -69,6 +80,7 @@ static int dwc3_usb3_phy_notify(struct notifier_block *nb,
reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
spin_unlock_irqrestore(&dwc->lock, flags);
+
break;
case PHY_NOTIFY_POST_RESET:
@@ -89,6 +101,8 @@ static int dwc3_usb3_phy_notify(struct notifier_block *nb,
dwc3_writel(dwc, DWC3_GUSB3PIPECTL(port), reg);
spin_unlock_irqrestore(&dwc->lock, flags);
+ dwc3_rockchip_vbus_handler(dwc, dwc_rk->role == USB_ROLE_DEVICE);
+
pm_runtime_put_autosuspend(dwc->dev);
break;
}
@@ -130,7 +144,16 @@ static int dwc3_rk_phy_register_notifiers(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_notifiers,
};
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 5/5] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode
2026-09-15 15:17 ` [PATCH 5/5] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
@ 2026-09-18 9:10 ` Igor Paunovic
0 siblings, 0 replies; 7+ messages in thread
From: Igor Paunovic @ 2026-09-18 9:10 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Igor Paunovic, Thinh Nguyen, Greg Kroah-Hartman, Heiko Stuebner,
Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, linux-kernel,
linux-usb, linux-arm-kernel, linux-rockchip, linux-phy, kernel
Hi Sebastian,
With this patch applied, the series fails to build when dwc3 is
host-only and USB_GADGET is either disabled, or a module while the
glue is built in. I built it on top of v7.3-rc1 for arm64 (allnoconfig
plus a fragment, gcc 13.3) with USB=y, USB_DWC3=y, USB_DWC3_HOST=y,
USB_DWC3_ROCKCHIP=y and USB_GADGET unset:
aarch64-linux-gnu-ld: drivers/usb/dwc3/dwc3-rockchip.o: in function `dwc3_rockchip_set_role':
dwc3-rockchip.c:(.text+0x270): undefined reference to `usb_udc_vbus_handler'
With USB_DWC3=m and USB_DWC3_ROCKCHIP=m, modpost reports
usb_udc_vbus_handler as undefined. With USB_GADGET=m and the glue
built in, the link error is the same. With patches 1-4 only, all
three configs build.
dwc3_rockchip_vbus_handler() checks dwc->gadget only at runtime, so
the call to usb_udc_vbus_handler() is always compiled in. That
function is only built with USB_GADGET (drivers/usb/gadget/udc/core.c)
and has no stub in include/linux/usb/gadget.h, and USB_DWC3_ROCKCHIP
does not depend on the gadget side.
The same test run also shows that the two EXPORT_TRACEPOINT_SYMBOL_GPL
lines in 4/5 fix the modpost error I reported on v14. With them
dropped, dwc3=y/glue=m (the v14 case) and dwc3=m/glue=m, both with
tracing, fail with the same four undefined tracepoint symbols.
This was a build test only; I did not run anything on a board. An LLM
assistant wrote the build script and helped draft this mail.
Igor
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-18 9:11 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15 15:17 [PATCH 0/5] usb: dwc3: introduce Rockchip glue driver Sebastian Reichel
2026-09-15 15:17 ` [PATCH 1/5] phy: core: add notifier infrastructure Sebastian Reichel
2026-09-15 15:17 ` [PATCH 2/5] usb: dwc3: rockchip: introduce glue driver Sebastian Reichel
2026-09-15 15:17 ` [PATCH 3/5] usb: dwc3: core: add post PHY registration hook for platform glue Sebastian Reichel
2026-09-15 15:17 ` [PATCH 4/5] usb: dwc3: rockchip: support PHY reset notifications Sebastian Reichel
2026-09-15 15:17 ` [PATCH 5/5] usb: dwc3: rockchip: fix USB-C reconnect in gadget mode Sebastian Reichel
2026-09-18 9:10 ` Igor Paunovic
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®