[PATCH 09/11] usb: dwc3: generic: support OTG dual-bind for runtime host/device switching

Peng Fan (OSS) peng.fan at oss.nxp.com
Wed Jun 17 10:23:08 CEST 2026


From: Peng Fan <peng.fan at nxp.com>

When dr_mode is "otg", bind both dwc3-generic-host and
dwc3-generic-peripheral children to the same DT node. This allows
runtime mode switching: "usb start" probes the host child while
"ums" or "fastboot" probes the peripheral child.

Add dwc3_otg_deactivate_sibling() which is called at the top of both
host and peripheral probe. It removes the active sibling via
device_remove() so the DWC3 core can be re-initialized in the new
mode (PHY teardown, GCTL.PRTCAPDIR flip, board_usb_cleanup/init).

Signed-off-by: Peng Fan <peng.fan at nxp.com>
---
 drivers/usb/dwc3/dwc3-generic.c | 61 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c
index 2c6f8e6d8ba..7fcb201d5f0 100644
--- a/drivers/usb/dwc3/dwc3-generic.c
+++ b/drivers/usb/dwc3/dwc3-generic.c
@@ -8,6 +8,7 @@
  */
 
 #include <dm.h>
+#include <dm/device-internal.h>
 #include <reset.h>
 #include <usb.h>
 #include <asm/gpio.h>
@@ -194,11 +195,33 @@ static int dwc3_generic_of_to_plat(struct udevice *dev)
 	return 0;
 }
 
+/*
+ * When OTG mode is used, both host and peripheral children are bound to
+ * the same DWC3 node.  Only one can be active at a time.  Before probing
+ * one child, remove the other if it is currently active so that the DWC3
+ * core can be re-initialized in the new mode.
+ */
+static void dwc3_otg_deactivate_sibling(struct udevice *dev)
+{
+	struct udevice *parent = dev->parent;
+	struct udevice *child;
+
+	device_foreach_child(child, parent) {
+		if (child != dev && device_active(child)) {
+			debug("%s: deactivating sibling %s\n",
+			      dev->name, child->name);
+			device_remove(child, DM_REMOVE_NORMAL);
+		}
+	}
+}
+
 #if CONFIG_IS_ENABLED(DM_USB_GADGET)
 static int dwc3_generic_peripheral_probe(struct udevice *dev)
 {
 	struct dwc3_generic_priv *priv = dev_get_priv(dev);
 
+	dwc3_otg_deactivate_sibling(dev);
+
 	return dwc3_generic_probe(dev, priv, USB_DR_MODE_PERIPHERAL);
 }
 
@@ -243,6 +266,8 @@ static int dwc3_generic_host_probe(struct udevice *dev)
 	struct dwc3_generic_host_priv *priv = dev_get_priv(dev);
 	int rc;
 
+	dwc3_otg_deactivate_sibling(dev);
+
 	rc = dwc3_generic_probe(dev, &priv->gen_priv, USB_DR_MODE_HOST);
 	if (rc)
 		return rc;
@@ -550,9 +575,41 @@ static int dwc3_glue_bind_common(struct udevice *parent, ofnode node)
 	if (dr_mode == USB_DR_MODE_UNKNOWN)
 		dr_mode = USB_DR_MODE_OTG;
 
+	/*
+	 * For OTG mode, bind both host and peripheral children so that
+	 * runtime mode switching is possible via usb start / ums / fastboot.
+	 * Only the one that gets probed first will actually initialize;
+	 * the other stays bound but inactive until needed.
+	 */
+	if (dr_mode == USB_DR_MODE_OTG) {
+		if (CONFIG_IS_ENABLED(USB_HOST)) {
+			ret = device_bind_driver_to_node(parent, "dwc3-generic-host",
+							 name, node, &dev);
+			if (ret) {
+				debug("%s: failed to bind host: %d\n",
+				      __func__, ret);
+				return ret;
+			}
+			debug("%s: OTG: bound host child\n", __func__);
+		}
+
+		if (CONFIG_IS_ENABLED(DM_USB_GADGET)) {
+			ret = device_bind_driver_to_node(parent, "dwc3-generic-peripheral",
+							 name, node, &dev);
+			if (ret) {
+				debug("%s: failed to bind peripheral: %d\n",
+				      __func__, ret);
+				return ret;
+			}
+			debug("%s: OTG: bound peripheral child\n", __func__);
+		}
+
+		return 0;
+	}
+
 	if (CONFIG_IS_ENABLED(DM_USB_GADGET) &&
-	    (dr_mode == USB_DR_MODE_PERIPHERAL || dr_mode == USB_DR_MODE_OTG)) {
-		debug("%s: dr_mode: OTG or Peripheral\n", __func__);
+	    dr_mode == USB_DR_MODE_PERIPHERAL) {
+		debug("%s: dr_mode: Peripheral\n", __func__);
 		driver = "dwc3-generic-peripheral";
 	} else if (CONFIG_IS_ENABLED(USB_HOST) && dr_mode == USB_DR_MODE_HOST) {
 		debug("%s: dr_mode: HOST\n", __func__);

-- 
2.51.0



More information about the U-Boot mailing list