From: Stefan Eichenberger <[email protected]>

The i.MX95 SoC has two USB controllers, a DWC3 host-only controller and
a ChipIdea OTG controller. The latter is the one that can be used for
USB gadget functionality. However, U-Boot incorrectly selects the
host-only controller when attempting to set up a USB gadget, leading to
failures in gadget mode.

Iterate over usb devices and skipping any that are host-only when
selecting the controller for gadget mode. This ensures that a device
only or otg controller is used for USB gadget functionality.

Verified on Verdin iMX95 hardware: "ums 0 mmc 0" now correctly flips
the ChipIdea OTG controller into device mode and enumerates on the
host PC.

Fixes: 821ca608d816 ("usb: Use the first available device for ehci_gadget")
Signed-off-by: Stefan Eichenberger <[email protected]>
---
 drivers/usb/host/usb-uclass.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
index 1c74d6fd39a..b9e9ef7e86a 100644
--- a/drivers/usb/host/usb-uclass.c
+++ b/drivers/usb/host/usb-uclass.c
@@ -18,6 +18,7 @@
 #include <dm/device-internal.h>
 #include <dm/lists.h>
 #include <dm/uclass-internal.h>
+#include <linux/usb/otg.h>
 #include <time.h>
 
 static bool asynch_allowed;
@@ -484,10 +485,23 @@ int usb_setup_ehci_gadget(struct ehci_ctrl **ctlrp)
        struct udevice *dev;
        int ret;
 
-       /* Find the old device and remove it */
+       /*
+        * Find the first UCLASS_USB device that isn't host-only. SoCs can
+        * have more than one UCLASS_USB controller (e.g. i.MX95, where a
+        * host-only DWC3 port and the ChipIdea OTG port both register under
+        * UCLASS_USB); taking whichever device happens to be first could
+        * pick the host-only controller instead of the OTG-capable one this
+        * is actually meant to flip into device mode.
+        */
        ret = uclass_find_first_device(UCLASS_USB, &dev);
        if (ret)
                return ret;
+       while (dev && usb_get_dr_mode(dev_ofnode(dev)) == USB_DR_MODE_HOST)
+               uclass_find_next_device(&dev);
+       if (!dev)
+               return -ENODEV;
+
+       /* Remove the old device */
        ret = device_remove(dev, DM_REMOVE_NORMAL);
        if (ret)
                return ret;
@@ -507,10 +521,15 @@ int usb_remove_ehci_gadget(struct ehci_ctrl **ctlrp)
        struct udevice *dev;
        int ret;
 
-       /* Find the old device and remove it */
+       /* Find the same non-host-only device usb_setup_ehci_gadget() used */
        ret = uclass_find_first_device(UCLASS_USB, &dev);
        if (ret)
                return ret;
+       while (dev && usb_get_dr_mode(dev_ofnode(dev)) == USB_DR_MODE_HOST)
+               uclass_find_next_device(&dev);
+       if (!dev)
+               return -ENODEV;
+
        ret = device_remove(dev, DM_REMOVE_NORMAL);
        if (ret)
                return ret;
-- 
2.55.0

Reply via email to