Merge branch '2019-10-28-azure-ci-support'
[oweals/u-boot.git] / common / usb_hub.c
index ff9cd50eab7c4f7a335572f0e226f53bb285c04c..25c2ac43450db8b92cba39fbab8dd9c9b47ebe21 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+
  */
 
 /****************************************************************************
@@ -25,6 +24,7 @@
 #include <common.h>
 #include <command.h>
 #include <dm.h>
+#include <env.h>
 #include <errno.h>
 #include <memalign.h>
 #include <asm/processor.h>
 #endif
 #include <asm/unaligned.h>
 
-DECLARE_GLOBAL_DATA_PTR;
-
 #include <usb.h>
-#ifdef CONFIG_4xx
-#include <asm/4xx_pci.h>
-#endif
 
 #define USB_BUFSIZ     512
 
@@ -58,21 +53,48 @@ struct usb_device_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)
+__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)
@@ -98,9 +120,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;
 }
 
 
@@ -134,7 +187,7 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
         * 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));
@@ -157,6 +210,10 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
              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;
@@ -173,40 +230,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
@@ -272,15 +332,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);
@@ -314,7 +365,7 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port)
        }
 
        /* 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);
@@ -336,7 +387,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);
@@ -408,8 +459,15 @@ static int usb_scan_port(struct usb_device_scan *usb_scan)
        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)) {
+       /*
+        * 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);
@@ -421,9 +479,16 @@ static int usb_scan_port(struct usb_device_scan *usb_scan)
                return 0;
        }
 
-       /* Test if the connection came up, and if not exit */
-       if (!(portstatus & USB_PORT_STAT_CONNECTION))
-               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);
@@ -479,11 +544,6 @@ static int usb_scan_port(struct usb_device_scan *usb_scan)
                       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
@@ -533,6 +593,20 @@ out:
        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)
 {
        int i, length;
@@ -541,14 +615,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) {
@@ -573,17 +647,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);
@@ -620,6 +696,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",
@@ -627,7 +753,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) {
@@ -643,9 +769,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),
@@ -656,6 +780,59 @@ static int usb_hub_configure(struct usb_device *dev)
        debug("%sover-current condition exists\n",
              (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \
              "" : "no ");
+
+#if CONFIG_IS_ENABLED(DM_USB)
+       /*
+        * Update USB host controller's internal representation of this hub
+        * after the hub descriptor is fetched.
+        */
+       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;
+       }
+
+       /*
+        * 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;
+               }
+
+               hub->hub_depth = depth;
+
+               if (usb_hub_is_superspeed(dev)) {
+                       debug("set hub (%p) depth to %d\n", dev, depth);
+                       /*
+                        * This request sets the value that the hub uses to
+                        * determine the index into the 'route string index'
+                        * for this hub.
+                        */
+                       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
+
        usb_hub_power_on(hub);
 
        /*
@@ -664,7 +841,7 @@ static int usb_hub_configure(struct usb_device *dev)
         * should occur in the board file of the device.
         */
        for (i = 0; i < dev->maxchild; i++)
-               usb_hub_reset_devices(i + 1);
+               usb_hub_reset_devices(hub, i + 1);
 
        /*
         * Only add the connected USB devices, including potential hubs,
@@ -746,7 +923,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);
@@ -780,6 +957,7 @@ UCLASS_DRIVER(usb_hub) = {
        .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[] = {