summaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c312
1 files changed, 200 insertions, 112 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 8874775f17..650af0e884 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1,32 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* hub.c - USB hub support
*
* Copyright (c) 2011 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
- *
- * See file CREDITS for list of people who contributed to this
- * project.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
*/
#include <common.h>
#include <init.h>
#include <malloc.h>
#include <errno.h>
-#include <usb/phy.h>
-#include <usb/usb.h>
-#include <usb/usb_defs.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/usb.h>
+#include <linux/usb/usb_defs.h>
#include "usb.h"
-#include "hub.h"
#define USB_BUFSIZ 512
@@ -44,11 +31,38 @@ struct usb_device_scan {
static LIST_HEAD(usb_scan_list);
+static bool usb_hub_is_superspeed(struct usb_device *hdev)
+{
+ return hdev->descriptor->bDeviceProtocol == 3;
+}
+
+bool usb_hub_is_root_hub(struct usb_device *hdev)
+{
+ return hdev->level == 0;
+}
+
+static int usb_set_hub_depth(struct usb_device *dev, int depth)
+{
+ dev_dbg(&dev->dev, "set hub depth to %d\n", dev->level);
+
+ if (depth < 0 || depth > 4)
+ return -EINVAL;
+
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ HUB_SET_DEPTH, USB_DIR_OUT | USB_RT_HUB,
+ depth, 0, NULL, 0, USB_CNTL_TIMEOUT);
+}
+
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)
@@ -74,9 +88,28 @@ static int usb_get_hub_status(struct usb_device *dev, void *data)
static 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);
+ if (ret < 0)
+ return ret;
+
+ if (!usb_hub_is_root_hub(dev) && usb_hub_is_superspeed(dev)) {
+ struct usb_port_status *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_PORT_STAT_SPEED_5GBPS)
+ tmp |= USB_PORT_STAT_SUPER_SPEED;
+
+ status->wPortStatus = tmp;
+ }
+
+ return ret;
}
@@ -88,22 +121,13 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
dev = hub->pusb_dev;
- /*
- * Enable power to the ports:
- * Here we Power-cycle the ports: aka,
- * turning them off and turning on again.
- */
- for (i = 0; i < dev->maxchild; i++) {
- usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
- dev_dbg(&dev->dev, "port %d returns %lX\n", i + 1, dev->status);
- }
-
/* Enable power to the ports */
dev_dbg(&dev->dev, "enabling power on all ports\n");
for (i = 0; i < dev->maxchild; i++) {
usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
- dev_dbg(&dev->dev, "port %d returns %lX\n", i + 1, dev->status);
+ dev_dbg(&dev->dev, "port%d: usb_set_port_feature returns 0x%08lx\n",
+ i + 1, dev->status);
}
/*
@@ -120,8 +144,7 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
*/
hub->connect_timeout = hub->query_delay + 1000 * MSECOND;
- dev_dbg(&dev->dev, "devnum=%d poweron: query_delay=%d \
- connect_timeout=%d\n",
+ dev_dbg(&dev->dev, "devnum=%d poweron: query_delay=%d connect_timeout=%d\n",
dev->devnum, max(100, (int) pgood_delay),
max(100, (int) pgood_delay) + 1000);
}
@@ -130,41 +153,48 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
static inline char *portspeed(int portstatus)
{
- if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED))
+ switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
+ case USB_PORT_STAT_SUPER_SPEED:
+ return "5 Gb/s";
+ case USB_PORT_STAT_HIGH_SPEED:
return "480 Mb/s";
- else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED))
+ case USB_PORT_STAT_LOW_SPEED:
return "1.5 Mb/s";
- else
+ default:
return "12 Mb/s";
+ }
}
-int hub_port_reset(struct usb_device *hub, int port, struct usb_device *usb)
+static int hub_port_reset(struct usb_device *hub, int port,
+ struct usb_device *usb)
{
int tries;
struct usb_port_status portsts;
unsigned short portstatus, portchange;
int delay = HUB_SHORT_RESET_TIME; /* start with short reset delay */
- dev_dbg(&hub->dev, "hub_port_reset: resetting port %d...\n", port);
+ dev_dbg(&hub->dev, "port%d: resetting...\n", port + 1);
for (tries = 0; tries < MAX_TRIES; tries++) {
usb_set_port_feature(hub, port + 1, USB_PORT_FEAT_RESET);
mdelay(delay);
if (usb_get_port_status(hub, port + 1, &portsts) < 0) {
- dev_dbg(&hub->dev, "get_port_status failed status %lX\n",
- hub->status);
+ dev_dbg(&hub->dev, "port%d: get_port_status failed status 0x%lX\n",
+ port + 1, hub->status);
return -1;
}
portstatus = le16_to_cpu(portsts.wPortStatus);
portchange = le16_to_cpu(portsts.wPortChange);
- dev_dbg(&hub->dev, "portstatus %x, change %x, %s\n",
+ dev_dbg(&hub->dev, "port%d: status 0x%04x, change 0x%04x, %s\n",
+ port + 1,
portstatus, portchange,
portspeed(portstatus));
- dev_dbg(&hub->dev, "STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \
+ dev_dbg(&hub->dev, "port%d: STAT_C_CONNECTION = %d STAT_CONNECTION = %d"
" USB_PORT_STAT_ENABLE %d\n",
+ port + 1,
(portchange & USB_PORT_STAT_C_CONNECTION) ? 1 : 0,
(portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0,
(portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0);
@@ -181,48 +211,44 @@ int hub_port_reset(struct usb_device *hub, int port, struct usb_device *usb)
}
if (tries == MAX_TRIES) {
- dev_dbg(&hub->dev, "Cannot enable port %i after %i retries, " \
- "disabling port.\n", port + 1, MAX_TRIES);
+ dev_dbg(&hub->dev, "port%d: Cannot enable after %i retries, disabling port.\n",
+ port + 1, MAX_TRIES);
dev_dbg(&hub->dev, "Maybe the USB cable is bad?\n");
return -1;
}
usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_RESET);
- if (portstatus & USB_PORT_STAT_HIGH_SPEED)
+ switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
+ case USB_PORT_STAT_SUPER_SPEED:
+ usb->speed = USB_SPEED_SUPER;
+ break;
+ case USB_PORT_STAT_HIGH_SPEED:
usb->speed = USB_SPEED_HIGH;
- else if (portstatus & USB_PORT_STAT_LOW_SPEED)
+ break;
+ case USB_PORT_STAT_LOW_SPEED:
usb->speed = USB_SPEED_LOW;
- else
+ break;
+ default:
usb->speed = USB_SPEED_FULL;
+ break;
+ }
return 0;
}
-static void usb_hub_port_connect_change(struct usb_device *dev, int port)
+static void usb_hub_port_connect_change(struct usb_device *dev, int port,
+ uint16_t portstatus, uint16_t portchange)
{
struct usb_device *usb;
- struct usb_port_status portsts;
- unsigned short portstatus, portchange;
-
- /* Check status */
- if (usb_get_port_status(dev, port + 1, &portsts) < 0) {
- dev_dbg(&dev->dev, "get_port_status failed\n");
- return;
- }
-
- portstatus = le16_to_cpu(portsts.wPortStatus);
- portchange = le16_to_cpu(portsts.wPortChange);
- dev_dbg(&dev->dev, "portstatus %x, change %x, %s\n",
- portstatus, portchange, portspeed(portstatus));
/* Clear the connection change status */
usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION);
/* Disconnect any existing devices under this port */
if (dev->children[port] && !(portstatus & USB_PORT_STAT_CONNECTION)) {
- dev_dbg(&dev->dev, "disconnect detected on port %d\n", port + 1);
+ dev_dbg(&dev->dev, "port%d: disconnect detected\n", port + 1);
usb_remove_device(dev->children[port]);
if (!dev->parent && dev->host->usbphy)
@@ -242,7 +268,7 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port)
/* Reset it */
if (hub_port_reset(dev, port, usb) < 0) {
- dev_warn(&dev->dev, "cannot reset port %i!?\n", port + 1);
+ dev_warn(&dev->dev, "port%d: cannot reset\n", port + 1);
usb_free_device(usb);
return;
}
@@ -267,7 +293,7 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port)
device_detect(&usb->dev);
}
-static int usb_scan_port(struct usb_device_scan *usb_scan)
+static void usb_scan_port(struct usb_device_scan *usb_scan)
{
struct usb_port_status portsts;
unsigned short portstatus, portchange;
@@ -284,68 +310,65 @@ static int usb_scan_port(struct usb_device_scan *usb_scan)
* This is needed for voltages to stabilize.
*/
if (get_time_ns() < hub->query_delay)
- return 0;
+ return;
if (usb_get_port_status(dev, port + 1, &portsts) < 0) {
- dev_dbg(&dev->dev, "get_port_status failed\n");
+ dev_dbg(&dev->dev, "port%d: get_port_status failed\n", port + 1);
if(get_time_ns() >= hub->connect_timeout) {
- dev_dbg(&dev->dev, "port=%d: timeout\n", port + 1);
+ dev_dbg(&dev->dev, "port%d: timeout\n", port + 1);
/* Remove this device from scanning list */
goto remove;
}
- return 0;
+ return;
}
portstatus = le16_to_cpu(portsts.wPortStatus);
portchange = le16_to_cpu(portsts.wPortChange);
- dev_dbg(&dev->dev, "Port %d Status %X Change %X\n",
+ dev_dbg(&dev->dev, "port%d: Status 0x%04x Change 0x%04x\n",
port + 1, portstatus, portchange);
- if (!(portchange & USB_PORT_STAT_C_CONNECTION)) {
- if(get_time_ns() >= hub->connect_timeout) {
- dev_dbg(&dev->dev, "port=%d: timeout\n", port + 1);
+ if (!(portchange & USB_PORT_STAT_C_CONNECTION) ||
+ !(portstatus & USB_PORT_STAT_CONNECTION)) {
+ if (get_time_ns() >= hub->connect_timeout) {
+ dev_dbg(&dev->dev, "port%d: timeout\n", port + 1);
/* Remove this device from scanning list */
goto remove;
}
- return 0;
+ return;
}
- /* 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) {
+ dev_dbg(&dev->dev, "port%d: reset change\n", port + 1);
+ usb_clear_port_feature(dev, port + 1,
+ USB_PORT_FEAT_C_RESET);
+ }
+
+ if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
+ usb_hub_is_superspeed(dev)) {
+ dev_dbg(&dev->dev, "port%d: BH reset change\n", port + 1);
+ usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_BH_PORT_RESET);
+ }
/* A new USB device is ready at this point */
- dev_dbg(&dev->dev, "port=%d: USB dev found\n", port + 1);
+ dev_dbg(&dev->dev, "port%d: USB dev found\n", port + 1);
- usb_hub_port_connect_change(dev, port);
+ usb_hub_port_connect_change(dev, port, portstatus, portchange);
if (portchange & USB_PORT_STAT_C_ENABLE) {
- dev_dbg(&dev->dev, "port %d enable change, status %x\n",
+ dev_dbg(&dev->dev, "port%d: enable change, status 0x%04x\n",
port + 1, portstatus);
usb_clear_port_feature(dev, port + 1,
USB_PORT_FEAT_C_ENABLE);
-
- /* 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) &&
- ((dev->children[port]))) {
- dev_dbg(&dev->dev, "already running port %i " \
- "disabled by hub (EMI?), " \
- "re-enabling...\n", port + 1);
- usb_hub_port_connect_change(dev, port);
- }
}
if (portstatus & USB_PORT_STAT_SUSPEND) {
- dev_dbg(&dev->dev, "port %d suspend change\n", port + 1);
+ dev_dbg(&dev->dev, "port%d: suspend change\n", port + 1);
usb_clear_port_feature(dev, port + 1,
USB_PORT_FEAT_SUSPEND);
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
- dev_dbg(&dev->dev, "port %d over-current change\n", port + 1);
+ dev_dbg(&dev->dev, "port%d: over-current change\n", port + 1);
usb_clear_port_feature(dev, port + 1,
USB_PORT_FEAT_C_OVER_CURRENT);
/* Only power-on this one port */
@@ -358,19 +381,13 @@ static int usb_scan_port(struct usb_device_scan *usb_scan)
*/
if (hub->overcurrent_count[port] <=
PORT_OVERCURRENT_MAX_SCAN_COUNT)
- return 0;
+ return;
/* Otherwise the device will get removed */
- dev_dbg(&dev->dev,"Port %d over-current occurred %d times\n",
+ dev_dbg(&dev->dev,"port%d: over-current occurred %d times\n",
port + 1, hub->overcurrent_count[port]);
}
- if (portchange & USB_PORT_STAT_C_RESET) {
- dev_dbg(&dev->dev, "port %d reset change\n", port + 1);
- usb_clear_port_feature(dev, port + 1,
- USB_PORT_FEAT_C_RESET);
- }
-
remove:
/*
* We're done with this device, so let's remove this device from
@@ -378,8 +395,6 @@ remove:
*/
list_del(&usb_scan->list);
free(usb_scan);
-
- return 0;
}
static int usb_device_list_scan(void)
@@ -387,7 +402,6 @@ 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)
@@ -400,12 +414,11 @@ static int usb_device_list_scan(void)
if (list_empty(&usb_scan_list))
goto out;
- list_for_each_entry_safe(usb_scan, tmp, &usb_scan_list, list) {
- /* Scan this port */
- ret = usb_scan_port(usb_scan);
- if (ret)
- goto out;
- }
+ list_for_each_entry_safe(usb_scan, tmp, &usb_scan_list, list)
+ usb_scan_port(usb_scan);
+
+ /* Avoid hammering the HUB with port scans */
+ mdelay(25);
}
out:
@@ -416,7 +429,7 @@ out:
*/
running = 0;
- return ret;
+ return 0;
}
static int usb_hub_configure(struct usb_device *dev)
@@ -424,7 +437,7 @@ static int usb_hub_configure(struct usb_device *dev)
unsigned char buffer[USB_BUFSIZ], *bitmap;
struct usb_hub_descriptor *descriptor;
struct usb_hub_status *hubsts;
- int i;
+ int i, ret;
struct usb_hub_device *hub;
hub = xzalloc(sizeof (*hub));
@@ -504,6 +517,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:
+ dev_dbg(&dev->dev, "Single TT\n");
+ break;
+ case USB_HUB_PR_HS_MULTI_TT:
+ ret = usb_set_interface(dev, 0, 1);
+ if (ret == 0) {
+ dev_dbg(&dev->dev, "TT per port\n");
+ hub->tt.multi = true;
+ } else {
+ dev_dbg(&dev->dev, "Using single TT (err %d)\n", ret);
+ }
+ break;
+ case USB_HUB_PR_SS:
+ /* USB 3.0 hubs don't have a TT */
+ break;
+ default:
+ dev_dbg(&dev->dev, "Unrecognized hub protocol %d\n",
+ dev->descriptor->bDeviceProtocol);
+ break;
+ }
+
+ /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */
+ switch (hub->desc.wHubCharacteristics & HUB_CHAR_TTTT) {
+ case HUB_TTTT_8_BITS:
+ if (dev->descriptor->bDeviceProtocol != 0) {
+ hub->tt.think_time = 666;
+ dev_dbg(&dev->dev, "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;
+ dev_dbg(&dev->dev, "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;
+ dev_dbg(&dev->dev, "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;
+ dev_dbg(&dev->dev, "TT requires at most %d FS bit times (%d ns)\n",
+ 32, hub->tt.think_time);
+ break;
+ }
+
dev_dbg(&dev->dev, "power on to power good time: %dms\n",
descriptor->bPwrOn2PwrGood * 2);
dev_dbg(&dev->dev, "hub controller current requirement: %dmA\n",
@@ -536,6 +599,31 @@ static int usb_hub_configure(struct usb_device *dev)
dev_dbg(&dev->dev, "%sover-current condition exists\n",
(le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \
"" : "no ");
+
+ if (dev->host->update_hub_device) {
+ int ret;
+
+ ret = dev->host->update_hub_device(dev);
+ if (ret)
+ return ret;
+ }
+
+ if (!usb_hub_is_root_hub(dev) && usb_hub_is_superspeed(dev)) {
+ int ret;
+
+ /*
+ * 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, dev->level - 1);
+ if (ret < 0) {
+ dev_dbg(&dev->dev, "failed to set hub depth (0x%08lx)\n",
+ dev->status);
+ return ret;
+ }
+ }
+
usb_hub_power_on(hub);
return 0;
@@ -572,7 +660,7 @@ static int usb_hub_configure_ports(struct usb_device *dev)
return usb_device_list_scan();
}
-static int usb_hub_detect(struct device_d *dev)
+static int usb_hub_detect(struct device *dev)
{
struct usb_device *usbdev = container_of(dev, struct usb_device, dev);
int i;