Merge git://git.denx.de/u-boot-socfpga
[platform/kernel/u-boot.git] / common / usb_hub.c
index 086b155..70bc6e2 100644 (file)
@@ -67,6 +67,26 @@ static inline bool usb_hub_is_superspeed(struct usb_device *hdev)
        return hdev->descriptor.bDeviceProtocol == 3;
 }
 
+#ifdef CONFIG_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;
@@ -102,9 +122,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_port_status), USB_CNTL_TIMEOUT);
+
+#ifdef CONFIG_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;
 }
 
 
@@ -649,6 +700,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",
@@ -685,6 +786,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 ");
+
+#ifdef CONFIG_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);
 
        /*