]> git.kernelconcepts.de Git - karo-tx-uboot.git/blobdiff - common/usb_hub.c
mmc: omap_hsmmc: enable 8bit interface for eMMC for AM43xx
[karo-tx-uboot.git] / common / usb_hub.c
index 2add4b97920fefedbad4d2cd04b888d294992841..652a104361f63ee715a99e924ccd1145a2c66224 100644 (file)
 
 #include <common.h>
 #include <command.h>
+#include <dm.h>
+#include <errno.h>
 #include <asm/processor.h>
 #include <asm/unaligned.h>
 #include <linux/ctype.h>
 #include <asm/byteorder.h>
 #include <asm/unaligned.h>
+#include <dm/root.h>
+
+DECLARE_GLOBAL_DATA_PTR;
 
 #include <usb.h>
 #ifdef CONFIG_4xx
@@ -37,6 +42,7 @@
 
 #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;
 
@@ -73,7 +79,7 @@ static int usb_get_hub_status(struct usb_device *dev, void *data)
                        data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT);
 }
 
-static int usb_get_port_status(struct usb_device *dev, int port, void *data)
+int usb_get_port_status(struct usb_device *dev, int port, void *data)
 {
        return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
                        USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port,
@@ -86,49 +92,11 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
        int i;
        struct usb_device *dev;
        unsigned pgood_delay = hub->desc.bPwrOn2PwrGood * 2;
-       ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
-       unsigned short portstatus;
-       int ret;
+       const char *env;
 
        dev = hub->pusb_dev;
 
-       /*
-        * Enable power to the ports:
-        * Here we Power-cycle the ports: aka,
-        * turning them off and turning on again.
-        */
        debug("enabling power on all ports\n");
-       for (i = 0; i < dev->maxchild; i++) {
-               usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
-               debug("port %d returns %lX\n", i + 1, dev->status);
-       }
-
-       /* Wait at least 2*bPwrOn2PwrGood for PP to change */
-       mdelay(pgood_delay);
-
-       for (i = 0; i < dev->maxchild; i++) {
-               ret = usb_get_port_status(dev, i + 1, portsts);
-               if (ret < 0) {
-                       debug("port %d: get_port_status failed\n", i + 1);
-                       continue;
-               }
-
-               /*
-                * Check to confirm the state of Port Power:
-                * xHCI says "After modifying PP, s/w shall read
-                * PP and confirm that it has reached the desired state
-                * before modifying it again, undefined behavior may occur
-                * if this procedure is not followed".
-                * EHCI doesn't say anything like this, but no harm in keeping
-                * this.
-                */
-               portstatus = le16_to_cpu(portsts->wPortStatus);
-               if (portstatus & (USB_PORT_STAT_POWER << 1)) {
-                       debug("port %d: Port power change failed\n", i + 1);
-                       continue;
-               }
-       }
-
        for (i = 0; i < dev->maxchild; i++) {
                usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
                debug("port %d returns %lX\n", i + 1, dev->status);
@@ -137,7 +105,14 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
        /*
         * 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");
+       if (env)
+               pgood_delay = max(pgood_delay,
+                                 (unsigned)simple_strtol(env, NULL, 0));
+       debug("pgood_delay=%dms\n", pgood_delay);
        mdelay(pgood_delay + 1000);
 }
 
@@ -179,17 +154,24 @@ static inline char *portspeed(int portstatus)
        return speed_str;
 }
 
-int hub_port_reset(struct usb_device *dev, int port,
+int legacy_hub_port_reset(struct usb_device *dev, int port,
                        unsigned short *portstat)
 {
-       int tries;
+       int err, tries;
        ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
        unsigned short portstatus, portchange;
 
-       debug("hub_port_reset: resetting port %d...\n", port);
+#ifdef CONFIG_DM_USB
+       debug("%s: resetting '%s' port %d...\n", __func__, dev->dev->name,
+             port + 1);
+#else
+       debug("%s: resetting port %d...\n", __func__, port + 1);
+#endif
        for (tries = 0; tries < MAX_TRIES; tries++) {
+               err = usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET);
+               if (err < 0)
+                       return err;
 
-               usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET);
                mdelay(200);
 
                if (usb_get_port_status(dev, port + 1, portsts) < 0) {
@@ -209,9 +191,22 @@ int hub_port_reset(struct usb_device *dev, int port,
                      (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0,
                      (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0);
 
-               if ((portchange & USB_PORT_STAT_C_CONNECTION) ||
-                   !(portstatus & USB_PORT_STAT_CONNECTION))
-                       return -1;
+               /*
+                * Perhaps we should check for the following here:
+                * - C_CONNECTION hasn't been set.
+                * - CONNECTION is still set.
+                *
+                * Doing so would ensure that the device is still connected
+                * to the bus, and hasn't been unplugged or replaced while the
+                * USB bus reset was going on.
+                *
+                * However, if we do that, then (at least) a San Disk Ultra
+                * USB 3.0 16GB device fails to reset on (at least) an NVIDIA
+                * Tegra Jetson TK1 board. For some reason, the device appears
+                * to briefly drop off the bus when this second bus reset is
+                * executed, yet if we retry this loop, it'll eventually come
+                * back after another reset or two.
+                */
 
                if (portstatus & USB_PORT_STAT_ENABLE)
                        break;
@@ -231,17 +226,26 @@ int 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_parentdata(dev);
+
+       return legacy_hub_port_reset(udev, port, portstat);
+}
+#endif
 
-void usb_hub_port_connect_change(struct usb_device *dev, int port)
+int usb_hub_port_connect_change(struct usb_device *dev, int port)
 {
-       struct usb_device *usb;
        ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
        unsigned short portstatus;
+       int ret, speed;
 
        /* Check status */
-       if (usb_get_port_status(dev, port + 1, portsts) < 0) {
+       ret = usb_get_port_status(dev, port + 1, portsts);
+       if (ret < 0) {
                debug("get_port_status failed\n");
-               return;
+               return ret;
        }
 
        portstatus = le16_to_cpu(portsts->wPortStatus);
@@ -255,51 +259,71 @@ void usb_hub_port_connect_change(struct usb_device *dev, int port)
 
        /* Disconnect any existing devices under this port */
        if (((!(portstatus & USB_PORT_STAT_CONNECTION)) &&
-            (!(portstatus & USB_PORT_STAT_ENABLE))) || (dev->children[port])) {
+            (!(portstatus & USB_PORT_STAT_ENABLE))) ||
+           usb_device_has_child_on_port(dev, port)) {
                debug("usb_disconnect(&hub->children[port]);\n");
                /* Return now if nothing is connected */
                if (!(portstatus & USB_PORT_STAT_CONNECTION))
-                       return;
+                       return -ENOTCONN;
        }
        mdelay(200);
 
        /* Reset the port */
-       if (hub_port_reset(dev, port, &portstatus) < 0) {
-               printf("cannot reset port %i!?\n", port + 1);
-               return;
+       ret = legacy_hub_port_reset(dev, port, &portstatus);
+       if (ret < 0) {
+               if (ret != -ENXIO)
+                       printf("cannot reset port %i!?\n", port + 1);
+               return ret;
        }
 
        mdelay(200);
 
-       /* Allocate a new device struct for it */
-       usb = usb_alloc_new_device(dev->controller);
-
        switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
        case USB_PORT_STAT_SUPER_SPEED:
-               usb->speed = USB_SPEED_SUPER;
+               speed = USB_SPEED_SUPER;
                break;
        case USB_PORT_STAT_HIGH_SPEED:
-               usb->speed = USB_SPEED_HIGH;
+               speed = USB_SPEED_HIGH;
                break;
        case USB_PORT_STAT_LOW_SPEED:
-               usb->speed = USB_SPEED_LOW;
+               speed = USB_SPEED_LOW;
                break;
        default:
-               usb->speed = USB_SPEED_FULL;
+               speed = USB_SPEED_FULL;
                break;
        }
 
+#ifdef CONFIG_DM_USB
+       struct udevice *child;
+
+       ret = usb_scan_device(dev->dev, port + 1, speed, &child);
+#else
+       struct usb_device *usb;
+
+       ret = usb_alloc_new_device(dev->controller, &usb);
+       if (ret) {
+               printf("cannot create new device: ret=%d", ret);
+               return ret;
+       }
+
        dev->children[port] = usb;
+       usb->speed = speed;
        usb->parent = dev;
        usb->portnr = port + 1;
        /* Run it through the hoops (find a driver, etc) */
-       if (usb_new_device(usb)) {
+       ret = usb_new_device(usb);
+       if (ret < 0) {
                /* Woops, disable the port */
-               usb_free_device();
+               usb_free_device(dev->controller);
                dev->children[port] = NULL;
+       }
+#endif
+       if (ret < 0) {
                debug("hub: disabling port %d\n", port + 1);
                usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE);
        }
+
+       return ret;
 }
 
 
@@ -312,26 +336,30 @@ static int usb_hub_configure(struct usb_device *dev)
        struct usb_hub_descriptor *descriptor;
        struct usb_hub_device *hub;
        __maybe_unused struct usb_hub_status *hubsts;
+       int ret;
 
        /* "allocate" Hub device */
        hub = usb_hub_allocate();
        if (hub == NULL)
-               return -1;
+               return -ENOMEM;
        hub->pusb_dev = dev;
        /* Get the the hub descriptor */
-       if (usb_get_hub_descriptor(dev, buffer, 4) < 0) {
+       ret = usb_get_hub_descriptor(dev, buffer, 4);
+       if (ret < 0) {
                debug("usb_hub_configure: failed to get hub " \
                      "descriptor, giving up %lX\n", dev->status);
-               return -1;
+               return ret;
        }
        descriptor = (struct usb_hub_descriptor *)buffer;
 
-       length = min(descriptor->bLength, sizeof(struct usb_hub_descriptor));
+       length = min_t(int, descriptor->bLength,
+                      sizeof(struct usb_hub_descriptor));
 
-       if (usb_get_hub_descriptor(dev, buffer, length) < 0) {
+       ret = usb_get_hub_descriptor(dev, buffer, length);
+       if (ret < 0) {
                debug("usb_hub_configure: failed to get hub " \
                      "descriptor 2nd giving up %lX\n", dev->status);
-               return -1;
+               return ret;
        }
        memcpy((unsigned char *)&hub->desc, buffer, length);
        /* adjust 16bit values */
@@ -399,13 +427,14 @@ static int usb_hub_configure(struct usb_device *dev)
        if (sizeof(struct usb_hub_status) > USB_BUFSIZ) {
                debug("usb_hub_configure: failed to get Status - " \
                      "too long: %d\n", descriptor->bLength);
-               return -1;
+               return -EFBIG;
        }
 
-       if (usb_get_hub_status(dev, buffer) < 0) {
+       ret = usb_get_hub_status(dev, buffer);
+       if (ret < 0) {
                debug("usb_hub_configure: failed to get Status %lX\n",
                      dev->status);
-               return -1;
+               return ret;
        }
 
 #ifdef DEBUG
@@ -437,6 +466,11 @@ static int usb_hub_configure(struct usb_device *dev)
                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
@@ -455,11 +489,15 @@ static int usb_hub_configure(struct usb_device *dev)
                        portstatus = le16_to_cpu(portsts->wPortStatus);
                        portchange = le16_to_cpu(portsts->wPortChange);
 
-                       if ((portchange & USB_PORT_STAT_C_CONNECTION) ==
-                               (portstatus & USB_PORT_STAT_CONNECTION))
+                       /* 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 * 10);
+               } while (get_timer(start) < CONFIG_SYS_HZ * 1);
 
                if (ret < 0)
                        continue;
@@ -486,7 +524,7 @@ static int usb_hub_configure(struct usb_device *dev)
                         * them again. Works at least with mouse driver */
                        if (!(portstatus & USB_PORT_STAT_ENABLE) &&
                             (portstatus & USB_PORT_STAT_CONNECTION) &&
-                            ((dev->children[i]))) {
+                            usb_device_has_child_on_port(dev, i)) {
                                debug("already running port %i "  \
                                      "disabled by hub (EMI?), " \
                                      "re-enabling...\n", i + 1);
@@ -517,33 +555,107 @@ static int usb_hub_configure(struct usb_device *dev)
        return 0;
 }
 
-int usb_hub_probe(struct usb_device *dev, int ifnum)
+static int usb_hub_check(struct usb_device *dev, int ifnum)
 {
        struct usb_interface *iface;
-       struct usb_endpoint_descriptor *ep;
-       int ret;
+       struct usb_endpoint_descriptor *ep = NULL;
 
        iface = &dev->config.if_desc[ifnum];
        /* Is it a hub? */
        if (iface->desc.bInterfaceClass != USB_CLASS_HUB)
-               return 0;
+               goto err;
        /* Some hubs have a subclass of 1, which AFAICT according to the */
        /*  specs is not defined, but it works */
        if ((iface->desc.bInterfaceSubClass != 0) &&
            (iface->desc.bInterfaceSubClass != 1))
-               return 0;
+               goto err;
        /* Multiple endpoints? What kind of mutant ninja-hub is this? */
        if (iface->desc.bNumEndpoints != 1)
-               return 0;
+               goto err;
        ep = &iface->ep_desc[0];
        /* Output endpoint? Curiousier and curiousier.. */
        if (!(ep->bEndpointAddress & USB_DIR_IN))
-               return 0;
+               goto err;
        /* If it's not an interrupt endpoint, we'd better punt! */
        if ((ep->bmAttributes & 3) != 3)
-               return 0;
+               goto err;
        /* We found a hub */
        debug("USB hub found\n");
+       return 0;
+
+err:
+       debug("USB hub not found: bInterfaceClass=%d, bInterfaceSubClass=%d, bNumEndpoints=%d\n",
+             iface->desc.bInterfaceClass, iface->desc.bInterfaceSubClass,
+             iface->desc.bNumEndpoints);
+       if (ep) {
+               debug("   bEndpointAddress=%#x, bmAttributes=%d",
+                     ep->bEndpointAddress, ep->bmAttributes);
+       }
+
+       return -ENOENT;
+}
+
+int usb_hub_probe(struct usb_device *dev, int ifnum)
+{
+       int ret;
+
+       ret = usb_hub_check(dev, ifnum);
+       if (ret)
+               return 0;
        ret = usb_hub_configure(dev);
        return ret;
 }
+
+#ifdef CONFIG_DM_USB
+int usb_hub_scan(struct udevice *hub)
+{
+       struct usb_device *udev = dev_get_parentdata(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__);
+       return usb_hub_scan(dev);
+}
+
+static const struct udevice_id usb_hub_ids[] = {
+       { .compatible = "usb-hub" },
+       { }
+};
+
+U_BOOT_DRIVER(usb_generic_hub) = {
+       .name   = "usb_hub",
+       .id     = UCLASS_USB_HUB,
+       .of_match = usb_hub_ids,
+       .flags  = DM_FLAG_ALLOC_PRIV_DMA,
+};
+
+UCLASS_DRIVER(usb_hub) = {
+       .id             = UCLASS_USB_HUB,
+       .name           = "usb_hub",
+       .post_bind      = usb_hub_post_bind,
+       .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),
+};
+
+static const struct usb_device_id hub_id_table[] = {
+       {
+               .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
+               .bDeviceClass = USB_CLASS_HUB
+       },
+       { }     /* Terminating entry */
+};
+
+U_BOOT_USB_DEVICE(usb_generic_hub, hub_id_table);
+
+#endif