Merge branch 'master' of git://git.denx.de/u-boot
[oweals/u-boot.git] / common / usb_hub.c
index a92c9fb73d01c6fe774bb29adcf8235bc988e220..c642b683e7ebc74dab4056001d8b2179265c68bb 100644 (file)
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * Most of this source has been derived from the Linux USB
  * project:
@@ -13,8 +14,6 @@
  *
  * Adapted for U-Boot:
  * (C) Copyright 2001 Denis Peter, MPL AG Switzerland
- *
- * SPDX-License-Identifier:    GPL-2.0+
  */
 
 /****************************************************************************
 #include <common.h>
 #include <command.h>
 #include <dm.h>
+#include <env.h>
 #include <errno.h>
+#include <malloc.h>
 #include <memalign.h>
 #include <asm/processor.h>
 #include <asm/unaligned.h>
 #include <linux/ctype.h>
+#include <linux/list.h>
 #include <asm/byteorder.h>
+#ifdef CONFIG_SANDBOX
+#include <asm/state.h>
+#endif
 #include <asm/unaligned.h>
-#include <dm/root.h>
-
-DECLARE_GLOBAL_DATA_PTR;
 
 #include <usb.h>
-#ifdef CONFIG_4xx
-#include <asm/4xx_pci.h>
-#endif
 
 #define USB_BUFSIZ     512
 
-/* TODO(sjg@chromium.org): Remove this when CONFIG_DM_USB is defined */
-static struct usb_hub_device hub_dev[USB_MAX_HUB];
-static int usb_hub_index;
+#define HUB_SHORT_RESET_TIME   20
+#define HUB_LONG_RESET_TIME    200
 
-__weak void usb_hub_reset_devices(int port)
+#define PORT_OVERCURRENT_MAX_SCAN_COUNT                3
+
+struct usb_device_scan {
+       struct usb_device *dev;         /* USB hub device to scan */
+       struct usb_hub_device *hub;     /* USB hub struct */
+       int port;                       /* USB port to scan */
+       struct list_head list;
+};
+
+static LIST_HEAD(usb_scan_list);
+
+__weak void usb_hub_reset_devices(struct usb_hub_device *hub, int port)
 {
        return;
 }
 
+static inline bool usb_hub_is_superspeed(struct usb_device *hdev)
+{
+       return hdev->descriptor.bDeviceProtocol == 3;
+}
+
+#if CONFIG_IS_ENABLED(DM_USB)
+bool usb_hub_is_root_hub(struct udevice *hub)
+{
+       if (device_get_uclass_id(hub->parent) != UCLASS_USB_HUB)
+               return true;
+
+       return false;
+}
+
+static int usb_set_hub_depth(struct usb_device *dev, int depth)
+{
+       if (depth < 0 || depth > 4)
+               return -EINVAL;
+
+       return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+               USB_REQ_SET_HUB_DEPTH, USB_DIR_OUT | USB_RT_HUB,
+               depth, 0, NULL, 0, USB_CNTL_TIMEOUT);
+}
+#endif
+
 static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size)
 {
+       unsigned short dtype = USB_DT_HUB;
+
+       if (usb_hub_is_superspeed(dev))
+               dtype = USB_DT_SS_HUB;
+
        return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
                USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
-               USB_DT_HUB << 8, 0, data, size, USB_CNTL_TIMEOUT);
+               dtype << 8, 0, data, size, USB_CNTL_TIMEOUT);
 }
 
 static int usb_clear_port_feature(struct usb_device *dev, int port, int feature)
@@ -82,9 +121,40 @@ static int usb_get_hub_status(struct usb_device *dev, void *data)
 
 int usb_get_port_status(struct usb_device *dev, int port, void *data)
 {
-       return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+       int ret;
+
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
                        USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port,
-                       data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT);
+                       data, sizeof(struct usb_port_status), USB_CNTL_TIMEOUT);
+
+#if CONFIG_IS_ENABLED(DM_USB)
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Translate the USB 3.0 hub port status field into the old version
+        * that U-Boot understands. Do this only when the hub is not root hub.
+        * For root hub, the port status field has already been translated
+        * in the host controller driver (see xhci_submit_root() in xhci.c).
+        *
+        * Note: this only supports driver model.
+        */
+
+       if (!usb_hub_is_root_hub(dev->dev) && usb_hub_is_superspeed(dev)) {
+               struct usb_port_status *status = (struct usb_port_status *)data;
+               u16 tmp = (status->wPortStatus) & USB_SS_PORT_STAT_MASK;
+
+               if (status->wPortStatus & USB_SS_PORT_STAT_POWER)
+                       tmp |= USB_PORT_STAT_POWER;
+               if ((status->wPortStatus & USB_SS_PORT_STAT_SPEED) ==
+                   USB_SS_PORT_STAT_SPEED_5GBPS)
+                       tmp |= USB_PORT_STAT_SUPER_SPEED;
+
+               status->wPortStatus = tmp;
+       }
+#endif
+
+       return ret;
 }
 
 
@@ -103,23 +173,54 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
                debug("port %d returns %lX\n", i + 1, dev->status);
        }
 
+#ifdef CONFIG_SANDBOX
+       /*
+        * Don't set timeout / delay values here. This results
+        * in these values still being reset to 0.
+        */
+       if (state_get_skip_delays())
+               return;
+#endif
+
        /*
         * Wait for power to become stable,
         * plus spec-defined max time for device to connect
         * but allow this time to be increased via env variable as some
         * devices break the spec and require longer warm-up times
         */
-       env = getenv("usb_pgood_delay");
+       env = env_get("usb_pgood_delay");
        if (env)
                pgood_delay = max(pgood_delay,
                                  (unsigned)simple_strtol(env, NULL, 0));
        debug("pgood_delay=%dms\n", pgood_delay);
-       mdelay(pgood_delay + 1000);
+
+       /*
+        * Do a minimum delay of the larger value of 100ms or pgood_delay
+        * so that the power can stablize before the devices are queried
+        */
+       hub->query_delay = get_timer(0) + max(100, (int)pgood_delay);
+
+       /*
+        * Record the power-on timeout here. The max. delay (timeout)
+        * will be done based on this value in the USB port loop in
+        * usb_hub_configure() later.
+        */
+       hub->connect_timeout = hub->query_delay + 1000;
+       debug("devnum=%d poweron: query_delay=%d connect_timeout=%d\n",
+             dev->devnum, max(100, (int)pgood_delay),
+             max(100, (int)pgood_delay) + 1000);
 }
 
+#if !CONFIG_IS_ENABLED(DM_USB)
+static struct usb_hub_device hub_dev[USB_MAX_HUB];
+static int usb_hub_index;
+
 void usb_hub_reset(void)
 {
        usb_hub_index = 0;
+
+       /* Zero out global hub_dev in case its re-used again */
+       memset(hub_dev, 0, sizeof(hub_dev));
 }
 
 static struct usb_hub_device *usb_hub_allocate(void)
@@ -130,39 +231,43 @@ static struct usb_hub_device *usb_hub_allocate(void)
        printf("ERROR: USB_MAX_HUB (%d) reached\n", USB_MAX_HUB);
        return NULL;
 }
+#endif
 
 #define MAX_TRIES 5
 
-static inline char *portspeed(int portstatus)
+static inline const char *portspeed(int portstatus)
 {
-       char *speed_str;
-
        switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
        case USB_PORT_STAT_SUPER_SPEED:
-               speed_str = "5 Gb/s";
-               break;
+               return "5 Gb/s";
        case USB_PORT_STAT_HIGH_SPEED:
-               speed_str = "480 Mb/s";
-               break;
+               return "480 Mb/s";
        case USB_PORT_STAT_LOW_SPEED:
-               speed_str = "1.5 Mb/s";
-               break;
+               return "1.5 Mb/s";
        default:
-               speed_str = "12 Mb/s";
-               break;
+               return "12 Mb/s";
        }
-
-       return speed_str;
 }
 
-int legacy_hub_port_reset(struct usb_device *dev, int port,
-                       unsigned short *portstat)
+/**
+ * usb_hub_port_reset() - reset a port given its usb_device pointer
+ *
+ * Reset a hub port and see if a device is present on that port, providing
+ * sufficient time for it to show itself. The port status is returned.
+ *
+ * @dev:       USB device to reset
+ * @port:      Port number to reset (note ports are numbered from 0 here)
+ * @portstat:  Returns port status
+ */
+static int usb_hub_port_reset(struct usb_device *dev, int port,
+                             unsigned short *portstat)
 {
        int err, tries;
        ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
        unsigned short portstatus, portchange;
+       int delay = HUB_SHORT_RESET_TIME; /* start with short reset delay */
 
-#ifdef CONFIG_DM_USB
+#if CONFIG_IS_ENABLED(DM_USB)
        debug("%s: resetting '%s' port %d...\n", __func__, dev->dev->name,
              port + 1);
 #else
@@ -173,7 +278,7 @@ int legacy_hub_port_reset(struct usb_device *dev, int port,
                if (err < 0)
                        return err;
 
-               mdelay(200);
+               mdelay(delay);
 
                if (usb_get_port_status(dev, port + 1, portsts) < 0) {
                        debug("get_port_status failed status %lX\n",
@@ -212,7 +317,8 @@ int legacy_hub_port_reset(struct usb_device *dev, int port,
                if (portstatus & USB_PORT_STAT_ENABLE)
                        break;
 
-               mdelay(200);
+               /* Switch to long reset delay for the next round */
+               delay = HUB_LONG_RESET_TIME;
        }
 
        if (tries == MAX_TRIES) {
@@ -227,15 +333,6 @@ int legacy_hub_port_reset(struct usb_device *dev, int port,
        return 0;
 }
 
-#ifdef CONFIG_DM_USB
-int hub_port_reset(struct udevice *dev, int port, unsigned short *portstat)
-{
-       struct usb_device *udev = dev_get_parent_priv(dev);
-
-       return legacy_hub_port_reset(udev, port, portstat);
-}
-#endif
-
 int usb_hub_port_connect_change(struct usb_device *dev, int port)
 {
        ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
@@ -267,18 +364,15 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port)
                if (!(portstatus & USB_PORT_STAT_CONNECTION))
                        return -ENOTCONN;
        }
-       mdelay(200);
 
        /* Reset the port */
-       ret = legacy_hub_port_reset(dev, port, &portstatus);
+       ret = usb_hub_port_reset(dev, port, &portstatus);
        if (ret < 0) {
                if (ret != -ENXIO)
                        printf("cannot reset port %i!?\n", port + 1);
                return ret;
        }
 
-       mdelay(200);
-
        switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
        case USB_PORT_STAT_SUPER_SPEED:
                speed = USB_SPEED_SUPER;
@@ -294,7 +388,7 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port)
                break;
        }
 
-#ifdef CONFIG_DM_USB
+#if CONFIG_IS_ENABLED(DM_USB)
        struct udevice *child;
 
        ret = usb_scan_device(dev->dev, port + 1, speed, &child);
@@ -327,6 +421,192 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port)
        return ret;
 }
 
+static int usb_scan_port(struct usb_device_scan *usb_scan)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
+       unsigned short portstatus;
+       unsigned short portchange;
+       struct usb_device *dev;
+       struct usb_hub_device *hub;
+       int ret = 0;
+       int i;
+
+       dev = usb_scan->dev;
+       hub = usb_scan->hub;
+       i = usb_scan->port;
+
+       /*
+        * Don't talk to the device before the query delay is expired.
+        * This is needed for voltages to stabalize.
+        */
+       if (get_timer(0) < hub->query_delay)
+               return 0;
+
+       ret = usb_get_port_status(dev, i + 1, portsts);
+       if (ret < 0) {
+               debug("get_port_status failed\n");
+               if (get_timer(0) >= hub->connect_timeout) {
+                       debug("devnum=%d port=%d: timeout\n",
+                             dev->devnum, i + 1);
+                       /* Remove this device from scanning list */
+                       list_del(&usb_scan->list);
+                       free(usb_scan);
+                       return 0;
+               }
+               return 0;
+       }
+
+       portstatus = le16_to_cpu(portsts->wPortStatus);
+       portchange = le16_to_cpu(portsts->wPortChange);
+       debug("Port %d Status %X Change %X\n", i + 1, portstatus, portchange);
+
+       /*
+        * No connection change happened, wait a bit more.
+        *
+        * For some situation, the hub reports no connection change but a
+        * device is connected to the port (eg: CCS bit is set but CSC is not
+        * in the PORTSC register of a root hub), ignore such case.
+        */
+       if (!(portchange & USB_PORT_STAT_C_CONNECTION) &&
+           !(portstatus & USB_PORT_STAT_CONNECTION)) {
+               if (get_timer(0) >= hub->connect_timeout) {
+                       debug("devnum=%d port=%d: timeout\n",
+                             dev->devnum, i + 1);
+                       /* Remove this device from scanning list */
+                       list_del(&usb_scan->list);
+                       free(usb_scan);
+                       return 0;
+               }
+               return 0;
+       }
+
+       if (portchange & USB_PORT_STAT_C_RESET) {
+               debug("port %d reset change\n", i + 1);
+               usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_RESET);
+       }
+
+       if ((portchange & USB_SS_PORT_STAT_C_BH_RESET) &&
+           usb_hub_is_superspeed(dev)) {
+               debug("port %d BH reset change\n", i + 1);
+               usb_clear_port_feature(dev, i + 1, USB_SS_PORT_FEAT_C_BH_RESET);
+       }
+
+       /* A new USB device is ready at this point */
+       debug("devnum=%d port=%d: USB dev found\n", dev->devnum, i + 1);
+
+       usb_hub_port_connect_change(dev, i);
+
+       if (portchange & USB_PORT_STAT_C_ENABLE) {
+               debug("port %d enable change, status %x\n", i + 1, portstatus);
+               usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE);
+               /*
+                * The following hack causes a ghost device problem
+                * to Faraday EHCI
+                */
+#ifndef CONFIG_USB_EHCI_FARADAY
+               /*
+                * EM interference sometimes causes bad shielded USB
+                * devices to be shutdown by the hub, this hack enables
+                * them again. Works at least with mouse driver
+                */
+               if (!(portstatus & USB_PORT_STAT_ENABLE) &&
+                   (portstatus & USB_PORT_STAT_CONNECTION) &&
+                   usb_device_has_child_on_port(dev, i)) {
+                       debug("already running port %i disabled by hub (EMI?), re-enabling...\n",
+                             i + 1);
+                       usb_hub_port_connect_change(dev, i);
+               }
+#endif
+       }
+
+       if (portstatus & USB_PORT_STAT_SUSPEND) {
+               debug("port %d suspend change\n", i + 1);
+               usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_SUSPEND);
+       }
+
+       if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
+               debug("port %d over-current change\n", i + 1);
+               usb_clear_port_feature(dev, i + 1,
+                                      USB_PORT_FEAT_C_OVER_CURRENT);
+               /* Only power-on this one port */
+               usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
+               hub->overcurrent_count[i]++;
+
+               /*
+                * If the max-scan-count is not reached, return without removing
+                * the device from scan-list. This will re-issue a new scan.
+                */
+               if (hub->overcurrent_count[i] <=
+                   PORT_OVERCURRENT_MAX_SCAN_COUNT)
+                       return 0;
+
+               /* Otherwise the device will get removed */
+               printf("Port %d over-current occurred %d times\n", i + 1,
+                      hub->overcurrent_count[i]);
+       }
+
+       /*
+        * We're done with this device, so let's remove this device from
+        * scanning list
+        */
+       list_del(&usb_scan->list);
+       free(usb_scan);
+
+       return 0;
+}
+
+static int usb_device_list_scan(void)
+{
+       struct usb_device_scan *usb_scan;
+       struct usb_device_scan *tmp;
+       static int running;
+       int ret = 0;
+
+       /* Only run this loop once for each controller */
+       if (running)
+               return 0;
+
+       running = 1;
+
+       while (1) {
+               /* We're done, once the list is empty again */
+               if (list_empty(&usb_scan_list))
+                       goto out;
+
+               list_for_each_entry_safe(usb_scan, tmp, &usb_scan_list, list) {
+                       int ret;
+
+                       /* Scan this port */
+                       ret = usb_scan_port(usb_scan);
+                       if (ret)
+                               goto out;
+               }
+       }
+
+out:
+       /*
+        * This USB controller has finished scanning all its connected
+        * USB devices. Set "running" back to 0, so that other USB controllers
+        * will scan their devices too.
+        */
+       running = 0;
+
+       return ret;
+}
+
+static struct usb_hub_device *usb_get_hub_device(struct usb_device *dev)
+{
+       struct usb_hub_device *hub;
+
+#if !CONFIG_IS_ENABLED(DM_USB)
+       /* "allocate" Hub device */
+       hub = usb_hub_allocate();
+#else
+       hub = dev_get_uclass_priv(dev->dev);
+#endif
+
+       return hub;
+}
 
 static int usb_hub_configure(struct usb_device *dev)
 {
@@ -336,14 +616,14 @@ static int usb_hub_configure(struct usb_device *dev)
        short hubCharacteristics;
        struct usb_hub_descriptor *descriptor;
        struct usb_hub_device *hub;
-       __maybe_unused struct usb_hub_status *hubsts;
+       struct usb_hub_status *hubsts;
        int ret;
 
-       /* "allocate" Hub device */
-       hub = usb_hub_allocate();
+       hub = usb_get_hub_device(dev);
        if (hub == NULL)
                return -ENOMEM;
        hub->pusb_dev = dev;
+
        /* Get the the hub descriptor */
        ret = usb_get_hub_descriptor(dev, buffer, 4);
        if (ret < 0) {
@@ -368,17 +648,19 @@ static int usb_hub_configure(struct usb_device *dev)
                        &descriptor->wHubCharacteristics)),
                        &hub->desc.wHubCharacteristics);
        /* set the bitmap */
-       bitmap = (unsigned char *)&hub->desc.DeviceRemovable[0];
+       bitmap = (unsigned char *)&hub->desc.u.hs.DeviceRemovable[0];
        /* devices not removable by default */
        memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8);
-       bitmap = (unsigned char *)&hub->desc.PortPowerCtrlMask[0];
+       bitmap = (unsigned char *)&hub->desc.u.hs.PortPowerCtrlMask[0];
        memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */
 
        for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++)
-               hub->desc.DeviceRemovable[i] = descriptor->DeviceRemovable[i];
+               hub->desc.u.hs.DeviceRemovable[i] =
+                       descriptor->u.hs.DeviceRemovable[i];
 
        for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++)
-               hub->desc.PortPowerCtrlMask[i] = descriptor->PortPowerCtrlMask[i];
+               hub->desc.u.hs.PortPowerCtrlMask[i] =
+                       descriptor->u.hs.PortPowerCtrlMask[i];
 
        dev->maxchild = descriptor->bNbrPorts;
        debug("%d ports detected\n", dev->maxchild);
@@ -415,6 +697,56 @@ static int usb_hub_configure(struct usb_device *dev)
                break;
        }
 
+       switch (dev->descriptor.bDeviceProtocol) {
+       case USB_HUB_PR_FS:
+               break;
+       case USB_HUB_PR_HS_SINGLE_TT:
+               debug("Single TT\n");
+               break;
+       case USB_HUB_PR_HS_MULTI_TT:
+               ret = usb_set_interface(dev, 0, 1);
+               if (ret == 0) {
+                       debug("TT per port\n");
+                       hub->tt.multi = true;
+               } else {
+                       debug("Using single TT (err %d)\n", ret);
+               }
+               break;
+       case USB_HUB_PR_SS:
+               /* USB 3.0 hubs don't have a TT */
+               break;
+       default:
+               debug("Unrecognized hub protocol %d\n",
+                     dev->descriptor.bDeviceProtocol);
+               break;
+       }
+
+       /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */
+       switch (hubCharacteristics & HUB_CHAR_TTTT) {
+       case HUB_TTTT_8_BITS:
+               if (dev->descriptor.bDeviceProtocol != 0) {
+                       hub->tt.think_time = 666;
+                       debug("TT requires at most %d FS bit times (%d ns)\n",
+                             8, hub->tt.think_time);
+               }
+               break;
+       case HUB_TTTT_16_BITS:
+               hub->tt.think_time = 666 * 2;
+               debug("TT requires at most %d FS bit times (%d ns)\n",
+                     16, hub->tt.think_time);
+               break;
+       case HUB_TTTT_24_BITS:
+               hub->tt.think_time = 666 * 3;
+               debug("TT requires at most %d FS bit times (%d ns)\n",
+                     24, hub->tt.think_time);
+               break;
+       case HUB_TTTT_32_BITS:
+               hub->tt.think_time = 666 * 4;
+               debug("TT requires at most %d FS bit times (%d ns)\n",
+                     32, hub->tt.think_time);
+               break;
+       }
+
        debug("power on to power good time: %dms\n",
              descriptor->bPwrOn2PwrGood * 2);
        debug("hub controller current requirement: %dmA\n",
@@ -422,7 +754,7 @@ static int usb_hub_configure(struct usb_device *dev)
 
        for (i = 0; i < dev->maxchild; i++)
                debug("port %d is%s removable\n", i + 1,
-                     hub->desc.DeviceRemovable[(i + 1) / 8] & \
+                     hub->desc.u.hs.DeviceRemovable[(i + 1) / 8] & \
                      (1 << ((i + 1) % 8)) ? " not" : "");
 
        if (sizeof(struct usb_hub_status) > USB_BUFSIZ) {
@@ -438,9 +770,7 @@ static int usb_hub_configure(struct usb_device *dev)
                return ret;
        }
 
-#ifdef DEBUG
        hubsts = (struct usb_hub_status *)buffer;
-#endif
 
        debug("get_hub_status returned status %X, change %X\n",
              le16_to_cpu(hubsts->wHubStatus),
@@ -451,109 +781,96 @@ static int usb_hub_configure(struct usb_device *dev)
        debug("%sover-current condition exists\n",
              (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \
              "" : "no ");
-       usb_hub_power_on(hub);
 
+#if CONFIG_IS_ENABLED(DM_USB)
        /*
-        * Reset any devices that may be in a bad state when applying
-        * the power.  This is a __weak function.  Resetting of the devices
-        * should occur in the board file of the device.
+        * Update USB host controller's internal representation of this hub
+        * after the hub descriptor is fetched.
         */
-       for (i = 0; i < dev->maxchild; i++)
-               usb_hub_reset_devices(i + 1);
-
-       for (i = 0; i < dev->maxchild; i++) {
-               ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
-               unsigned short portstatus, portchange;
-               int ret;
-               ulong start = get_timer(0);
-
-#ifdef CONFIG_DM_USB
-               debug("\n\nScanning '%s' port %d\n", dev->dev->name, i + 1);
-#else
-               debug("\n\nScanning port %d\n", i + 1);
-#endif
-               /*
-                * Wait for (whichever finishes first)
-                *  - A maximum of 10 seconds
-                *    This is a purely observational value driven by connecting
-                *    a few broken pen drives and taking the max * 1.5 approach
-                *  - connection_change and connection state to report same
-                *    state
-                */
-               do {
-                       ret = usb_get_port_status(dev, i + 1, portsts);
-                       if (ret < 0) {
-                               debug("get_port_status failed\n");
-                               break;
-                       }
-
-                       portstatus = le16_to_cpu(portsts->wPortStatus);
-                       portchange = le16_to_cpu(portsts->wPortChange);
-
-                       /* No connection change happened, wait a bit more. */
-                       if (!(portchange & USB_PORT_STAT_C_CONNECTION))
-                               continue;
-
-                       /* Test if the connection came up, and if so, exit. */
-                       if (portstatus & USB_PORT_STAT_CONNECTION)
-                               break;
-
-               } while (get_timer(start) < CONFIG_SYS_HZ * 1);
+       ret = usb_update_hub_device(dev);
+       if (ret < 0 && ret != -ENOSYS) {
+               debug("%s: failed to update hub device for HCD (%x)\n",
+                     __func__, ret);
+               return ret;
+       }
 
-               if (ret < 0)
-                       continue;
+       /*
+        * A maximum of seven tiers are allowed in a USB topology, and the
+        * root hub occupies the first tier. The last tier ends with a normal
+        * USB device. USB 3.0 hubs use a 20-bit field called 'route string'
+        * to route packets to the designated downstream port. The hub uses a
+        * hub depth value multiplied by four as an offset into the 'route
+        * string' to locate the bits it uses to determine the downstream
+        * port number.
+        */
+       if (usb_hub_is_root_hub(dev->dev)) {
+               hub->hub_depth = -1;
+       } else {
+               struct udevice *hdev;
+               int depth = 0;
+
+               hdev = dev->dev->parent;
+               while (!usb_hub_is_root_hub(hdev)) {
+                       depth++;
+                       hdev = hdev->parent;
+               }
 
-               debug("Port %d Status %X Change %X\n",
-                     i + 1, portstatus, portchange);
+               hub->hub_depth = depth;
 
-               if (portchange & USB_PORT_STAT_C_CONNECTION) {
-                       debug("port %d connection change\n", i + 1);
-                       usb_hub_port_connect_change(dev, i);
-               }
-               if (portchange & USB_PORT_STAT_C_ENABLE) {
-                       debug("port %d enable change, status %x\n",
-                             i + 1, portstatus);
-                       usb_clear_port_feature(dev, i + 1,
-                                               USB_PORT_FEAT_C_ENABLE);
+               if (usb_hub_is_superspeed(dev)) {
+                       debug("set hub (%p) depth to %d\n", dev, depth);
                        /*
-                        * The following hack causes a ghost device problem
-                        * to Faraday EHCI
+                        * This request sets the value that the hub uses to
+                        * determine the index into the 'route string index'
+                        * for this hub.
                         */
-#ifndef CONFIG_USB_EHCI_FARADAY
-                       /* EM interference sometimes causes bad shielded USB
-                        * devices to be shutdown by the hub, this hack enables
-                        * them again. Works at least with mouse driver */
-                       if (!(portstatus & USB_PORT_STAT_ENABLE) &&
-                            (portstatus & USB_PORT_STAT_CONNECTION) &&
-                            usb_device_has_child_on_port(dev, i)) {
-                               debug("already running port %i "  \
-                                     "disabled by hub (EMI?), " \
-                                     "re-enabling...\n", i + 1);
-                                     usb_hub_port_connect_change(dev, i);
+                       ret = usb_set_hub_depth(dev, depth);
+                       if (ret < 0) {
+                               debug("%s: failed to set hub depth (%lX)\n",
+                                     __func__, dev->status);
+                               return ret;
                        }
-#endif
-               }
-               if (portstatus & USB_PORT_STAT_SUSPEND) {
-                       debug("port %d suspend change\n", i + 1);
-                       usb_clear_port_feature(dev, i + 1,
-                                               USB_PORT_FEAT_SUSPEND);
                }
+       }
+#endif
 
-               if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
-                       debug("port %d over-current change\n", i + 1);
-                       usb_clear_port_feature(dev, i + 1,
-                                               USB_PORT_FEAT_C_OVER_CURRENT);
-                       usb_hub_power_on(hub);
-               }
+       usb_hub_power_on(hub);
+
+       /*
+        * Reset any devices that may be in a bad state when applying
+        * the power.  This is a __weak function.  Resetting of the devices
+        * should occur in the board file of the device.
+        */
+       for (i = 0; i < dev->maxchild; i++)
+               usb_hub_reset_devices(hub, i + 1);
 
-               if (portchange & USB_PORT_STAT_C_RESET) {
-                       debug("port %d reset change\n", i + 1);
-                       usb_clear_port_feature(dev, i + 1,
-                                               USB_PORT_FEAT_C_RESET);
+       /*
+        * Only add the connected USB devices, including potential hubs,
+        * to a scanning list. This list will get scanned and devices that
+        * are detected (either via port connected or via port timeout)
+        * will get removed from this list. Scanning of the devices on this
+        * list will continue until all devices are removed.
+        */
+       for (i = 0; i < dev->maxchild; i++) {
+               struct usb_device_scan *usb_scan;
+
+               usb_scan = calloc(1, sizeof(*usb_scan));
+               if (!usb_scan) {
+                       printf("Can't allocate memory for USB device!\n");
+                       return -ENOMEM;
                }
-       } /* end for i all ports */
+               usb_scan->dev = dev;
+               usb_scan->hub = hub;
+               usb_scan->port = i;
+               list_add_tail(&usb_scan->list, &usb_scan_list);
+       }
 
-       return 0;
+       /*
+        * And now call the scanning code which loops over the generated list
+        */
+       ret = usb_device_list_scan();
+
+       return ret;
 }
 
 static int usb_hub_check(struct usb_device *dev, int ifnum)
@@ -607,7 +924,7 @@ int usb_hub_probe(struct usb_device *dev, int ifnum)
        return ret;
 }
 
-#ifdef CONFIG_DM_USB
+#if CONFIG_IS_ENABLED(DM_USB)
 int usb_hub_scan(struct udevice *hub)
 {
        struct usb_device *udev = dev_get_parent_priv(hub);
@@ -615,12 +932,6 @@ int usb_hub_scan(struct udevice *hub)
        return usb_hub_configure(udev);
 }
 
-static int usb_hub_post_bind(struct udevice *dev)
-{
-       /* Scan the bus for devices */
-       return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
-}
-
 static int usb_hub_post_probe(struct udevice *dev)
 {
        debug("%s\n", __func__);
@@ -642,11 +953,12 @@ U_BOOT_DRIVER(usb_generic_hub) = {
 UCLASS_DRIVER(usb_hub) = {
        .id             = UCLASS_USB_HUB,
        .name           = "usb_hub",
-       .post_bind      = usb_hub_post_bind,
+       .post_bind      = dm_scan_fdt_dev,
        .post_probe     = usb_hub_post_probe,
        .child_pre_probe        = usb_child_pre_probe,
        .per_child_auto_alloc_size = sizeof(struct usb_device),
        .per_child_platdata_auto_alloc_size = sizeof(struct usb_dev_platdata),
+       .per_device_auto_alloc_size = sizeof(struct usb_hub_device),
 };
 
 static const struct usb_device_id hub_id_table[] = {