|
@@ -55,9 +55,6 @@ 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)
|
|
@@ -65,11 +62,41 @@ __weak void usb_hub_reset_devices(int port)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+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;
|
|
|
+
|
|
|
+ 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)
|
|
@@ -95,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_hub_status), USB_CNTL_TIMEOUT);
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -154,6 +212,10 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
|
|
|
max(100, (int)pgood_delay) + 1000);
|
|
|
}
|
|
|
|
|
|
+#ifndef CONFIG_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;
|
|
@@ -170,6 +232,7 @@ 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
|
|
|
|
|
@@ -195,8 +258,18 @@ static inline char *portspeed(int portstatus)
|
|
|
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);
|
|
@@ -269,15 +342,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);
|
|
@@ -311,7 +375,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);
|
|
@@ -405,8 +469,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);
|
|
@@ -418,10 +489,6 @@ 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;
|
|
|
-
|
|
|
/* A new USB device is ready at this point */
|
|
|
debug("devnum=%d port=%d: USB dev found\n", dev->devnum, i + 1);
|
|
|
|
|
@@ -530,6 +597,20 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static struct usb_hub_device *usb_get_hub_device(struct usb_device *dev)
|
|
|
+{
|
|
|
+ struct usb_hub_device *hub;
|
|
|
+
|
|
|
+#ifndef CONFIG_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,11 +622,11 @@ static int usb_hub_configure(struct usb_device *dev)
|
|
|
__maybe_unused 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) {
|
|
@@ -570,17 +651,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);
|
|
@@ -617,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",
|
|
@@ -624,7 +757,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) {
|
|
@@ -653,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);
|
|
|
|
|
|
/*
|
|
@@ -777,6 +963,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[] = {
|