|
@@ -30,6 +30,7 @@
|
|
#include <asm/processor.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/unaligned.h>
|
|
#include <asm/unaligned.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/ctype.h>
|
|
|
|
+#include <linux/list.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/byteorder.h>
|
|
#ifdef CONFIG_SANDBOX
|
|
#ifdef CONFIG_SANDBOX
|
|
#include <asm/state.h>
|
|
#include <asm/state.h>
|
|
@@ -49,9 +50,19 @@ DECLARE_GLOBAL_DATA_PTR;
|
|
#define HUB_SHORT_RESET_TIME 20
|
|
#define HUB_SHORT_RESET_TIME 20
|
|
#define HUB_LONG_RESET_TIME 200
|
|
#define HUB_LONG_RESET_TIME 200
|
|
|
|
|
|
|
|
+#define PORT_OVERCURRENT_MAX_SCAN_COUNT 3
|
|
|
|
+
|
|
|
|
+struct usb_device_scan {
|
|
|
|
+ struct usb_device *dev; /* USB hub device to scan */
|
|
|
|
+ struct usb_hub_device *hub; /* USB hub struct */
|
|
|
|
+ int port; /* USB port to scan */
|
|
|
|
+ struct list_head list;
|
|
|
|
+};
|
|
|
|
+
|
|
/* TODO(sjg@chromium.org): Remove this when CONFIG_DM_USB is defined */
|
|
/* TODO(sjg@chromium.org): Remove this when CONFIG_DM_USB is defined */
|
|
static struct usb_hub_device hub_dev[USB_MAX_HUB];
|
|
static struct usb_hub_device hub_dev[USB_MAX_HUB];
|
|
static int usb_hub_index;
|
|
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(int port)
|
|
{
|
|
{
|
|
@@ -109,6 +120,15 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
|
|
debug("port %d returns %lX\n", i + 1, dev->status);
|
|
debug("port %d returns %lX\n", i + 1, dev->status);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#ifdef CONFIG_SANDBOX
|
|
|
|
+ /*
|
|
|
|
+ * Don't set timeout / delay values here. This results
|
|
|
|
+ * in these values still being reset to 0.
|
|
|
|
+ */
|
|
|
|
+ if (state_get_skip_delays())
|
|
|
|
+ return;
|
|
|
|
+#endif
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Wait for power to become stable,
|
|
* Wait for power to become stable,
|
|
* plus spec-defined max time for device to connect
|
|
* plus spec-defined max time for device to connect
|
|
@@ -120,12 +140,30 @@ static void usb_hub_power_on(struct usb_hub_device *hub)
|
|
pgood_delay = max(pgood_delay,
|
|
pgood_delay = max(pgood_delay,
|
|
(unsigned)simple_strtol(env, NULL, 0));
|
|
(unsigned)simple_strtol(env, NULL, 0));
|
|
debug("pgood_delay=%dms\n", pgood_delay);
|
|
debug("pgood_delay=%dms\n", pgood_delay);
|
|
- mdelay(pgood_delay + 1000);
|
|
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Do a minimum delay of the larger value of 100ms or pgood_delay
|
|
|
|
+ * so that the power can stablize before the devices are queried
|
|
|
|
+ */
|
|
|
|
+ hub->query_delay = get_timer(0) + max(100, (int)pgood_delay);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Record the power-on timeout here. The max. delay (timeout)
|
|
|
|
+ * will be done based on this value in the USB port loop in
|
|
|
|
+ * usb_hub_configure() later.
|
|
|
|
+ */
|
|
|
|
+ hub->connect_timeout = hub->query_delay + 1000;
|
|
|
|
+ debug("devnum=%d poweron: query_delay=%d connect_timeout=%d\n",
|
|
|
|
+ dev->devnum, max(100, (int)pgood_delay),
|
|
|
|
+ max(100, (int)pgood_delay) + 1000);
|
|
}
|
|
}
|
|
|
|
|
|
void usb_hub_reset(void)
|
|
void usb_hub_reset(void)
|
|
{
|
|
{
|
|
usb_hub_index = 0;
|
|
usb_hub_index = 0;
|
|
|
|
+
|
|
|
|
+ /* Zero out global hub_dev in case its re-used again */
|
|
|
|
+ memset(hub_dev, 0, sizeof(hub_dev));
|
|
}
|
|
}
|
|
|
|
|
|
static struct usb_hub_device *usb_hub_allocate(void)
|
|
static struct usb_hub_device *usb_hub_allocate(void)
|
|
@@ -332,6 +370,168 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port)
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int usb_scan_port(struct usb_device_scan *usb_scan)
|
|
|
|
+{
|
|
|
|
+ ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
|
|
|
|
+ unsigned short portstatus;
|
|
|
|
+ unsigned short portchange;
|
|
|
|
+ struct usb_device *dev;
|
|
|
|
+ struct usb_hub_device *hub;
|
|
|
|
+ int ret = 0;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ dev = usb_scan->dev;
|
|
|
|
+ hub = usb_scan->hub;
|
|
|
|
+ i = usb_scan->port;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Don't talk to the device before the query delay is expired.
|
|
|
|
+ * This is needed for voltages to stabalize.
|
|
|
|
+ */
|
|
|
|
+ if (get_timer(0) < hub->query_delay)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ ret = usb_get_port_status(dev, i + 1, portsts);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ debug("get_port_status failed\n");
|
|
|
|
+ if (get_timer(0) >= hub->connect_timeout) {
|
|
|
|
+ debug("devnum=%d port=%d: timeout\n",
|
|
|
|
+ dev->devnum, i + 1);
|
|
|
|
+ /* Remove this device from scanning list */
|
|
|
|
+ list_del(&usb_scan->list);
|
|
|
|
+ free(usb_scan);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ portstatus = le16_to_cpu(portsts->wPortStatus);
|
|
|
|
+ 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)) {
|
|
|
|
+ if (get_timer(0) >= hub->connect_timeout) {
|
|
|
|
+ debug("devnum=%d port=%d: timeout\n",
|
|
|
|
+ dev->devnum, i + 1);
|
|
|
|
+ /* Remove this device from scanning list */
|
|
|
|
+ list_del(&usb_scan->list);
|
|
|
|
+ free(usb_scan);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ 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);
|
|
|
|
+
|
|
|
|
+ usb_hub_port_connect_change(dev, i);
|
|
|
|
+
|
|
|
|
+ if (portchange & USB_PORT_STAT_C_ENABLE) {
|
|
|
|
+ debug("port %d enable change, status %x\n", i + 1, portstatus);
|
|
|
|
+ usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE);
|
|
|
|
+ /*
|
|
|
|
+ * The following hack causes a ghost device problem
|
|
|
|
+ * to Faraday EHCI
|
|
|
|
+ */
|
|
|
|
+#ifndef CONFIG_USB_EHCI_FARADAY
|
|
|
|
+ /*
|
|
|
|
+ * 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) &&
|
|
|
|
+ usb_device_has_child_on_port(dev, i)) {
|
|
|
|
+ debug("already running port %i disabled by hub (EMI?), re-enabling...\n",
|
|
|
|
+ i + 1);
|
|
|
|
+ usb_hub_port_connect_change(dev, i);
|
|
|
|
+ }
|
|
|
|
+#endif
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (portstatus & USB_PORT_STAT_SUSPEND) {
|
|
|
|
+ debug("port %d suspend change\n", i + 1);
|
|
|
|
+ usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_SUSPEND);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
|
|
|
|
+ debug("port %d over-current change\n", i + 1);
|
|
|
|
+ usb_clear_port_feature(dev, i + 1,
|
|
|
|
+ USB_PORT_FEAT_C_OVER_CURRENT);
|
|
|
|
+ /* Only power-on this one port */
|
|
|
|
+ usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
|
|
|
|
+ hub->overcurrent_count[i]++;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * If the max-scan-count is not reached, return without removing
|
|
|
|
+ * the device from scan-list. This will re-issue a new scan.
|
|
|
|
+ */
|
|
|
|
+ if (hub->overcurrent_count[i] <=
|
|
|
|
+ PORT_OVERCURRENT_MAX_SCAN_COUNT)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ /* Otherwise the device will get removed */
|
|
|
|
+ printf("Port %d over-current occured %d times\n", i + 1,
|
|
|
|
+ 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
|
|
|
|
+ */
|
|
|
|
+ list_del(&usb_scan->list);
|
|
|
|
+ free(usb_scan);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ running = 1;
|
|
|
|
+
|
|
|
|
+ while (1) {
|
|
|
|
+ /* We're done, once the list is empty again */
|
|
|
|
+ if (list_empty(&usb_scan_list))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry_safe(usb_scan, tmp, &usb_scan_list, list) {
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ /* Scan this port */
|
|
|
|
+ ret = usb_scan_port(usb_scan);
|
|
|
|
+ if (ret)
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ /*
|
|
|
|
+ * This USB controller has finished scanning all its connected
|
|
|
|
+ * USB devices. Set "running" back to 0, so that other USB controllers
|
|
|
|
+ * will scan their devices too.
|
|
|
|
+ */
|
|
|
|
+ running = 0;
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
|
|
static int usb_hub_configure(struct usb_device *dev)
|
|
static int usb_hub_configure(struct usb_device *dev)
|
|
{
|
|
{
|
|
@@ -466,104 +666,33 @@ static int usb_hub_configure(struct usb_device *dev)
|
|
for (i = 0; i < dev->maxchild; i++)
|
|
for (i = 0; i < dev->maxchild; i++)
|
|
usb_hub_reset_devices(i + 1);
|
|
usb_hub_reset_devices(i + 1);
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Only add the connected USB devices, including potential hubs,
|
|
|
|
+ * to a scanning list. This list will get scanned and devices that
|
|
|
|
+ * are detected (either via port connected or via port timeout)
|
|
|
|
+ * will get removed from this list. Scanning of the devices on this
|
|
|
|
+ * list will continue until all devices are removed.
|
|
|
|
+ */
|
|
for (i = 0; i < dev->maxchild; i++) {
|
|
for (i = 0; i < dev->maxchild; i++) {
|
|
- ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
|
|
|
|
- unsigned short portstatus, portchange;
|
|
|
|
- int ret;
|
|
|
|
- ulong start = get_timer(0);
|
|
|
|
- uint delay = CONFIG_SYS_HZ;
|
|
|
|
-
|
|
|
|
-#ifdef CONFIG_SANDBOX
|
|
|
|
- if (state_get_skip_delays())
|
|
|
|
- delay = 0;
|
|
|
|
-#endif
|
|
|
|
-#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
|
|
|
|
- * This is a purely observational value driven by connecting
|
|
|
|
- * a few broken pen drives and taking the max * 1.5 approach
|
|
|
|
- * - connection_change and connection state to report same
|
|
|
|
- * state
|
|
|
|
- */
|
|
|
|
- do {
|
|
|
|
- ret = usb_get_port_status(dev, i + 1, portsts);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- debug("get_port_status failed\n");
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- portstatus = le16_to_cpu(portsts->wPortStatus);
|
|
|
|
- portchange = le16_to_cpu(portsts->wPortChange);
|
|
|
|
-
|
|
|
|
- /* 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) < delay);
|
|
|
|
-
|
|
|
|
- if (ret < 0)
|
|
|
|
- continue;
|
|
|
|
|
|
+ struct usb_device_scan *usb_scan;
|
|
|
|
|
|
- debug("Port %d Status %X Change %X\n",
|
|
|
|
- i + 1, portstatus, portchange);
|
|
|
|
-
|
|
|
|
- if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
|
|
|
- debug("port %d connection change\n", i + 1);
|
|
|
|
- usb_hub_port_connect_change(dev, i);
|
|
|
|
- }
|
|
|
|
- if (portchange & USB_PORT_STAT_C_ENABLE) {
|
|
|
|
- debug("port %d enable change, status %x\n",
|
|
|
|
- i + 1, portstatus);
|
|
|
|
- usb_clear_port_feature(dev, i + 1,
|
|
|
|
- USB_PORT_FEAT_C_ENABLE);
|
|
|
|
- /*
|
|
|
|
- * The following hack causes a ghost device problem
|
|
|
|
- * to Faraday EHCI
|
|
|
|
- */
|
|
|
|
-#ifndef CONFIG_USB_EHCI_FARADAY
|
|
|
|
- /* 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) &&
|
|
|
|
- usb_device_has_child_on_port(dev, i)) {
|
|
|
|
- debug("already running port %i " \
|
|
|
|
- "disabled by hub (EMI?), " \
|
|
|
|
- "re-enabling...\n", i + 1);
|
|
|
|
- usb_hub_port_connect_change(dev, i);
|
|
|
|
- }
|
|
|
|
-#endif
|
|
|
|
- }
|
|
|
|
- if (portstatus & USB_PORT_STAT_SUSPEND) {
|
|
|
|
- debug("port %d suspend change\n", i + 1);
|
|
|
|
- usb_clear_port_feature(dev, i + 1,
|
|
|
|
- USB_PORT_FEAT_SUSPEND);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
|
|
|
|
- debug("port %d over-current change\n", i + 1);
|
|
|
|
- usb_clear_port_feature(dev, i + 1,
|
|
|
|
- USB_PORT_FEAT_C_OVER_CURRENT);
|
|
|
|
- usb_hub_power_on(hub);
|
|
|
|
|
|
+ usb_scan = calloc(1, sizeof(*usb_scan));
|
|
|
|
+ if (!usb_scan) {
|
|
|
|
+ printf("Can't allocate memory for USB device!\n");
|
|
|
|
+ return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
+ usb_scan->dev = dev;
|
|
|
|
+ usb_scan->hub = hub;
|
|
|
|
+ usb_scan->port = i;
|
|
|
|
+ list_add_tail(&usb_scan->list, &usb_scan_list);
|
|
|
|
+ }
|
|
|
|
|
|
- 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);
|
|
|
|
- }
|
|
|
|
- } /* end for i all ports */
|
|
|
|
|
|
+ /*
|
|
|
|
+ * And now call the scanning code which loops over the generated list
|
|
|
|
+ */
|
|
|
|
+ ret = usb_device_list_scan();
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
static int usb_hub_check(struct usb_device *dev, int ifnum)
|
|
static int usb_hub_check(struct usb_device *dev, int ifnum)
|