]> git.ipfire.org Git - people/ms/u-boot.git/blobdiff - common/usb_hub.c
powerpc: remove 4xx support
[people/ms/u-boot.git] / common / usb_hub.c
index 2089e20fd5032152eef6672e016f1dba4211cc5d..d135526e4fe87b4fc776a14e81a9cdfe7cd10eeb 100644 (file)
 #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
 
 #define HUB_SHORT_RESET_TIME   20
 #define HUB_LONG_RESET_TIME    200
 
+#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;
+};
+
 /* 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;
+static LIST_HEAD(usb_scan_list);
 
 __weak void usb_hub_reset_devices(int port)
 {
@@ -109,6 +116,15 @@ 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
@@ -120,12 +136,30 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
                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);
 }
 
 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)
@@ -275,7 +309,6 @@ 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);
@@ -285,8 +318,6 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port)
                return ret;
        }
 
-       mdelay(200);
-
        switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
        case USB_PORT_STAT_SUPER_SPEED:
                speed = USB_SPEED_SUPER;
@@ -335,6 +366,169 @@ 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. */
+       if (!(portchange & USB_PORT_STAT_C_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;
+       }
+
+       /* Test if the connection came up, and if not exit */
+       if (!(portstatus & USB_PORT_STAT_CONNECTION))
+               return 0;
+
+       /* 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]);
+       }
+
+       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);
+       }
+
+       /*
+        * 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 int usb_hub_configure(struct usb_device *dev)
 {
@@ -469,104 +663,33 @@ static int usb_hub_configure(struct usb_device *dev)
        for (i = 0; i < dev->maxchild; i++)
                usb_hub_reset_devices(i + 1);
 
+       /*
+        * 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++) {
-               ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
-               unsigned short portstatus, portchange;
-               int ret;
-               ulong start = get_timer(0);
-               uint delay = CONFIG_SYS_HZ;
+               struct usb_device_scan *usb_scan;
 
-#ifdef CONFIG_SANDBOX
-               if (state_get_skip_delays())
-                       delay = 0;
-#endif
-#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) < delay);
-
-               if (ret < 0)
-                       continue;
-
-               debug("Port %d Status %X Change %X\n",
-                     i + 1, portstatus, portchange);
-
-               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);
-                       /*
-                        * 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);
-                       usb_hub_power_on(hub);
+               usb_scan = calloc(1, sizeof(*usb_scan));
+               if (!usb_scan) {
+                       printf("Can't allocate memory for USB device!\n");
+                       return -ENOMEM;
                }
+               usb_scan->dev = dev;
+               usb_scan->hub = hub;
+               usb_scan->port = i;
+               list_add_tail(&usb_scan->list, &usb_scan_list);
+       }
 
-               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);
-               }
-       } /* end for i all ports */
+       /*
+        * And now call the scanning code which loops over the generated list
+        */
+       ret = usb_device_list_scan();
 
-       return 0;
+       return ret;
 }
 
 static int usb_hub_check(struct usb_device *dev, int ifnum)
@@ -628,12 +751,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__);
@@ -655,7 +772,7 @@ 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),