X-Git-Url: https://git.kernelconcepts.de/?p=karo-tx-uboot.git;a=blobdiff_plain;f=common%2Fusb_hub.c;h=652a104361f63ee715a99e924ccd1145a2c66224;hp=0d79ec3ea84abfc8d61988cbfc30c8ba17cec60e;hb=53b6c1e8bb0f6592341650ea1af5e01101e9b71a;hpb=55f4b57542de9f4bee8dc0b7ca70686bd20e2aa4 diff --git a/common/usb_hub.c b/common/usb_hub.c index 0d79ec3ea8..652a104361 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -1,5 +1,4 @@ /* - * * Most of this source has been derived from the Linux USB * project: * (C) Copyright Linus Torvalds 1999 @@ -15,24 +14,7 @@ * Adapted for U-Boot: * (C) Copyright 2001 Denis Peter, MPL AG Switzerland * - * 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 as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * + * SPDX-License-Identifier: GPL-2.0+ */ /**************************************************************************** @@ -42,11 +24,16 @@ #include #include +#include +#include #include #include #include #include #include +#include + +DECLARE_GLOBAL_DATA_PTR; #include #ifdef CONFIG_4xx @@ -55,9 +42,14 @@ #define USB_BUFSIZ 512 +/* 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; +__weak void usb_hub_reset_devices(int port) +{ + return; +} static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) { @@ -87,7 +79,7 @@ static int usb_get_hub_status(struct usb_device *dev, void *data) data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); } -static int usb_get_port_status(struct usb_device *dev, int port, void *data) +int usb_get_port_status(struct usb_device *dev, int port, void *data) { return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port, @@ -100,56 +92,28 @@ static void usb_hub_power_on(struct usb_hub_device *hub) int i; struct usb_device *dev; unsigned pgood_delay = hub->desc.bPwrOn2PwrGood * 2; - ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); - unsigned short portstatus; - int ret; + const char *env; dev = hub->pusb_dev; - /* - * Enable power to the ports: - * Here we Power-cycle the ports: aka, - * turning them off and turning on again. - */ debug("enabling power on all ports\n"); - for (i = 0; i < dev->maxchild; i++) { - usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); - debug("port %d returns %lX\n", i + 1, dev->status); - } - - /* Wait at least 2*bPwrOn2PwrGood for PP to change */ - mdelay(pgood_delay); - - for (i = 0; i < dev->maxchild; i++) { - ret = usb_get_port_status(dev, i + 1, portsts); - if (ret < 0) { - debug("port %d: get_port_status failed\n", i + 1); - return; - } - - /* - * Check to confirm the state of Port Power: - * xHCI says "After modifying PP, s/w shall read - * PP and confirm that it has reached the desired state - * before modifying it again, undefined behavior may occur - * if this procedure is not followed". - * EHCI doesn't say anything like this, but no harm in keeping - * this. - */ - portstatus = le16_to_cpu(portsts->wPortStatus); - if (portstatus & (USB_PORT_STAT_POWER << 1)) { - debug("port %d: Port power change failed\n", i + 1); - return; - } - } - for (i = 0; i < dev->maxchild; i++) { usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); debug("port %d returns %lX\n", i + 1, dev->status); } - /* Wait at least 100 msec for power to become stable */ - mdelay(max(pgood_delay, (unsigned)100)); + /* + * Wait for power to become stable, + * plus spec-defined max time for device to connect + * 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"); + if (env) + pgood_delay = max(pgood_delay, + (unsigned)simple_strtol(env, NULL, 0)); + debug("pgood_delay=%dms\n", pgood_delay); + mdelay(pgood_delay + 1000); } void usb_hub_reset(void) @@ -190,17 +154,24 @@ static inline char *portspeed(int portstatus) return speed_str; } -int hub_port_reset(struct usb_device *dev, int port, +int legacy_hub_port_reset(struct usb_device *dev, int port, unsigned short *portstat) { - int tries; + int err, tries; ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); unsigned short portstatus, portchange; - debug("hub_port_reset: resetting port %d...\n", port); +#ifdef CONFIG_DM_USB + debug("%s: resetting '%s' port %d...\n", __func__, dev->dev->name, + port + 1); +#else + debug("%s: resetting port %d...\n", __func__, port + 1); +#endif for (tries = 0; tries < MAX_TRIES; tries++) { + err = usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET); + if (err < 0) + return err; - usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET); mdelay(200); if (usb_get_port_status(dev, port + 1, portsts) < 0) { @@ -220,9 +191,22 @@ int hub_port_reset(struct usb_device *dev, int port, (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0, (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0); - if ((portchange & USB_PORT_STAT_C_CONNECTION) || - !(portstatus & USB_PORT_STAT_CONNECTION)) - return -1; + /* + * Perhaps we should check for the following here: + * - C_CONNECTION hasn't been set. + * - CONNECTION is still set. + * + * Doing so would ensure that the device is still connected + * to the bus, and hasn't been unplugged or replaced while the + * USB bus reset was going on. + * + * However, if we do that, then (at least) a San Disk Ultra + * USB 3.0 16GB device fails to reset on (at least) an NVIDIA + * Tegra Jetson TK1 board. For some reason, the device appears + * to briefly drop off the bus when this second bus reset is + * executed, yet if we retry this loop, it'll eventually come + * back after another reset or two. + */ if (portstatus & USB_PORT_STAT_ENABLE) break; @@ -242,17 +226,26 @@ int 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_parentdata(dev); + + return legacy_hub_port_reset(udev, port, portstat); +} +#endif -void usb_hub_port_connect_change(struct usb_device *dev, int port) +int usb_hub_port_connect_change(struct usb_device *dev, int port) { - struct usb_device *usb; ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); unsigned short portstatus; + int ret, speed; /* Check status */ - if (usb_get_port_status(dev, port + 1, portsts) < 0) { + ret = usb_get_port_status(dev, port + 1, portsts); + if (ret < 0) { debug("get_port_status failed\n"); - return; + return ret; } portstatus = le16_to_cpu(portsts->wPortStatus); @@ -266,91 +259,109 @@ void usb_hub_port_connect_change(struct usb_device *dev, int port) /* Disconnect any existing devices under this port */ if (((!(portstatus & USB_PORT_STAT_CONNECTION)) && - (!(portstatus & USB_PORT_STAT_ENABLE))) || (dev->children[port])) { + (!(portstatus & USB_PORT_STAT_ENABLE))) || + usb_device_has_child_on_port(dev, port)) { debug("usb_disconnect(&hub->children[port]);\n"); /* Return now if nothing is connected */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) - return; + return -ENOTCONN; } mdelay(200); /* Reset the port */ - if (hub_port_reset(dev, port, &portstatus) < 0) { - printf("cannot reset port %i!?\n", port + 1); - return; + ret = legacy_hub_port_reset(dev, port, &portstatus); + if (ret < 0) { + if (ret != -ENXIO) + printf("cannot reset port %i!?\n", port + 1); + return ret; } mdelay(200); - /* Allocate a new device struct for it */ - usb = usb_alloc_new_device(dev->controller); - switch (portstatus & USB_PORT_STAT_SPEED_MASK) { case USB_PORT_STAT_SUPER_SPEED: - usb->speed = USB_SPEED_SUPER; + speed = USB_SPEED_SUPER; break; case USB_PORT_STAT_HIGH_SPEED: - usb->speed = USB_SPEED_HIGH; + speed = USB_SPEED_HIGH; break; case USB_PORT_STAT_LOW_SPEED: - usb->speed = USB_SPEED_LOW; + speed = USB_SPEED_LOW; break; default: - usb->speed = USB_SPEED_FULL; + speed = USB_SPEED_FULL; break; } +#ifdef CONFIG_DM_USB + struct udevice *child; + + ret = usb_scan_device(dev->dev, port + 1, speed, &child); +#else + struct usb_device *usb; + + ret = usb_alloc_new_device(dev->controller, &usb); + if (ret) { + printf("cannot create new device: ret=%d", ret); + return ret; + } + dev->children[port] = usb; + usb->speed = speed; usb->parent = dev; usb->portnr = port + 1; /* Run it through the hoops (find a driver, etc) */ - if (usb_new_device(usb)) { + ret = usb_new_device(usb); + if (ret < 0) { /* Woops, disable the port */ - usb_free_device(); + usb_free_device(dev->controller); dev->children[port] = NULL; + } +#endif + if (ret < 0) { debug("hub: disabling port %d\n", port + 1); usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); } + + return ret; } static int usb_hub_configure(struct usb_device *dev) { - int i; + int i, length; ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, USB_BUFSIZ); unsigned char *bitmap; short hubCharacteristics; struct usb_hub_descriptor *descriptor; struct usb_hub_device *hub; __maybe_unused struct usb_hub_status *hubsts; + int ret; /* "allocate" Hub device */ hub = usb_hub_allocate(); if (hub == NULL) - return -1; + return -ENOMEM; hub->pusb_dev = dev; /* Get the the hub descriptor */ - if (usb_get_hub_descriptor(dev, buffer, 4) < 0) { + ret = usb_get_hub_descriptor(dev, buffer, 4); + if (ret < 0) { debug("usb_hub_configure: failed to get hub " \ "descriptor, giving up %lX\n", dev->status); - return -1; + return ret; } descriptor = (struct usb_hub_descriptor *)buffer; - /* silence compiler warning if USB_BUFSIZ is > 256 [= sizeof(char)] */ - i = descriptor->bLength; - if (i > USB_BUFSIZ) { - debug("usb_hub_configure: failed to get hub " \ - "descriptor - too long: %d\n", descriptor->bLength); - return -1; - } + length = min_t(int, descriptor->bLength, + sizeof(struct usb_hub_descriptor)); - if (usb_get_hub_descriptor(dev, buffer, descriptor->bLength) < 0) { + ret = usb_get_hub_descriptor(dev, buffer, length); + if (ret < 0) { debug("usb_hub_configure: failed to get hub " \ "descriptor 2nd giving up %lX\n", dev->status); - return -1; + return ret; } - memcpy((unsigned char *)&hub->desc, buffer, descriptor->bLength); + memcpy((unsigned char *)&hub->desc, buffer, length); /* adjust 16bit values */ put_unaligned(le16_to_cpu(get_unaligned( &descriptor->wHubCharacteristics)), @@ -416,13 +427,14 @@ static int usb_hub_configure(struct usb_device *dev) if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { debug("usb_hub_configure: failed to get Status - " \ "too long: %d\n", descriptor->bLength); - return -1; + return -EFBIG; } - if (usb_get_hub_status(dev, buffer) < 0) { + ret = usb_get_hub_status(dev, buffer); + if (ret < 0) { debug("usb_hub_configure: failed to get Status %lX\n", dev->status); - return -1; + return ret; } #ifdef DEBUG @@ -440,12 +452,25 @@ static int usb_hub_configure(struct usb_device *dev) "" : "no "); usb_hub_power_on(hub); + /* + * Reset any devices that may be in a bad state when applying + * the power. This is a __weak function. Resetting of the devices + * should occur in the board file of the device. + */ + for (i = 0; i < dev->maxchild; i++) + usb_hub_reset_devices(i + 1); + 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); +#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 @@ -464,11 +489,15 @@ static int usb_hub_configure(struct usb_device *dev) portstatus = le16_to_cpu(portsts->wPortStatus); portchange = le16_to_cpu(portsts->wPortChange); - if ((portchange & USB_PORT_STAT_C_CONNECTION) == - (portstatus & USB_PORT_STAT_CONNECTION)) + /* 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) < CONFIG_SYS_HZ * 10); + } while (get_timer(start) < CONFIG_SYS_HZ * 1); if (ret < 0) continue; @@ -485,18 +514,23 @@ static int usb_hub_configure(struct usb_device *dev) 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) && - ((dev->children[i]))) { + 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); @@ -521,33 +555,107 @@ static int usb_hub_configure(struct usb_device *dev) return 0; } -int usb_hub_probe(struct usb_device *dev, int ifnum) +static int usb_hub_check(struct usb_device *dev, int ifnum) { struct usb_interface *iface; - struct usb_endpoint_descriptor *ep; - int ret; + struct usb_endpoint_descriptor *ep = NULL; iface = &dev->config.if_desc[ifnum]; /* Is it a hub? */ if (iface->desc.bInterfaceClass != USB_CLASS_HUB) - return 0; + goto err; /* Some hubs have a subclass of 1, which AFAICT according to the */ /* specs is not defined, but it works */ if ((iface->desc.bInterfaceSubClass != 0) && (iface->desc.bInterfaceSubClass != 1)) - return 0; + goto err; /* Multiple endpoints? What kind of mutant ninja-hub is this? */ if (iface->desc.bNumEndpoints != 1) - return 0; + goto err; ep = &iface->ep_desc[0]; /* Output endpoint? Curiousier and curiousier.. */ if (!(ep->bEndpointAddress & USB_DIR_IN)) - return 0; + goto err; /* If it's not an interrupt endpoint, we'd better punt! */ if ((ep->bmAttributes & 3) != 3) - return 0; + goto err; /* We found a hub */ debug("USB hub found\n"); + return 0; + +err: + debug("USB hub not found: bInterfaceClass=%d, bInterfaceSubClass=%d, bNumEndpoints=%d\n", + iface->desc.bInterfaceClass, iface->desc.bInterfaceSubClass, + iface->desc.bNumEndpoints); + if (ep) { + debug(" bEndpointAddress=%#x, bmAttributes=%d", + ep->bEndpointAddress, ep->bmAttributes); + } + + return -ENOENT; +} + +int usb_hub_probe(struct usb_device *dev, int ifnum) +{ + int ret; + + ret = usb_hub_check(dev, ifnum); + if (ret) + return 0; ret = usb_hub_configure(dev); return ret; } + +#ifdef CONFIG_DM_USB +int usb_hub_scan(struct udevice *hub) +{ + struct usb_device *udev = dev_get_parentdata(hub); + + return usb_hub_configure(udev); +} + +static int usb_hub_post_bind(struct udevice *dev) +{ + /* Scan the bus for devices */ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); +} + +static int usb_hub_post_probe(struct udevice *dev) +{ + debug("%s\n", __func__); + return usb_hub_scan(dev); +} + +static const struct udevice_id usb_hub_ids[] = { + { .compatible = "usb-hub" }, + { } +}; + +U_BOOT_DRIVER(usb_generic_hub) = { + .name = "usb_hub", + .id = UCLASS_USB_HUB, + .of_match = usb_hub_ids, + .flags = DM_FLAG_ALLOC_PRIV_DMA, +}; + +UCLASS_DRIVER(usb_hub) = { + .id = UCLASS_USB_HUB, + .name = "usb_hub", + .post_bind = usb_hub_post_bind, + .post_probe = usb_hub_post_probe, + .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), +}; + +static const struct usb_device_id hub_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, + .bDeviceClass = USB_CLASS_HUB + }, + { } /* Terminating entry */ +}; + +U_BOOT_USB_DEVICE(usb_generic_hub, hub_id_table); + +#endif