From: Peng Fan <[email protected]> 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 <[email protected]> --- 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

