Blame view
common/usb_hub.c
21 KB
23faf2bc9 USB: Separate out... |
1 |
/* |
23faf2bc9 USB: Separate out... |
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
* Most of this source has been derived from the Linux USB * project: * (C) Copyright Linus Torvalds 1999 * (C) Copyright Johannes Erdfelt 1999-2001 * (C) Copyright Andreas Gal 1999 * (C) Copyright Gregory P. Smith 1999 * (C) Copyright Deti Fliegl 1999 (new USB architecture) * (C) Copyright Randy Dunlap 2000 * (C) Copyright David Brownell 2000 (kernel hotplug, usb_device_id) * (C) Copyright Yggdrasil Computing, Inc. 2000 * (usb_device_id matching changes by Adam J. Richter) * * Adapted for U-Boot: * (C) Copyright 2001 Denis Peter, MPL AG Switzerland * |
1a4596601 Add GPL-2.0+ SPDX... |
17 |
* SPDX-License-Identifier: GPL-2.0+ |
23faf2bc9 USB: Separate out... |
18 19 20 21 22 23 24 25 26 |
*/ /**************************************************************************** * HUB "Driver" * Probes device for being a hub and configurate it */ #include <common.h> #include <command.h> |
054fe48eb dm: usb: Add driv... |
27 |
#include <dm.h> |
79b588872 dm: usb: Adjust u... |
28 |
#include <errno.h> |
cf92e05c0 Move ALLOC_CACHE_... |
29 |
#include <memalign.h> |
23faf2bc9 USB: Separate out... |
30 |
#include <asm/processor.h> |
93ad908c4 usb: do explicit ... |
31 |
#include <asm/unaligned.h> |
23faf2bc9 USB: Separate out... |
32 |
#include <linux/ctype.h> |
c998da0d6 usb: Change power... |
33 |
#include <linux/list.h> |
23faf2bc9 USB: Separate out... |
34 |
#include <asm/byteorder.h> |
3884c98c3 dm: usb: Avoid ti... |
35 36 37 |
#ifdef CONFIG_SANDBOX #include <asm/state.h> #endif |
23faf2bc9 USB: Separate out... |
38 |
#include <asm/unaligned.h> |
054fe48eb dm: usb: Add driv... |
39 40 |
DECLARE_GLOBAL_DATA_PTR; |
23faf2bc9 USB: Separate out... |
41 42 |
#include <usb.h> |
23faf2bc9 USB: Separate out... |
43 |
|
23faf2bc9 USB: Separate out... |
44 |
#define USB_BUFSIZ 512 |
f7f601002 usb: legacy_hub_p... |
45 46 |
#define HUB_SHORT_RESET_TIME 20 #define HUB_LONG_RESET_TIME 200 |
c998da0d6 usb: Change power... |
47 48 49 50 51 52 53 54 |
#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; }; |
054fe48eb dm: usb: Add driv... |
55 |
/* TODO(sjg@chromium.org): Remove this when CONFIG_DM_USB is defined */ |
23faf2bc9 USB: Separate out... |
56 57 |
static struct usb_hub_device hub_dev[USB_MAX_HUB]; static int usb_hub_index; |
c998da0d6 usb: Change power... |
58 |
static LIST_HEAD(usb_scan_list); |
23faf2bc9 USB: Separate out... |
59 |
|
3615a996a USB: usb-hub: Add... |
60 61 62 63 |
__weak void usb_hub_reset_devices(int port) { return; } |
23faf2bc9 USB: Separate out... |
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) { 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); } static int usb_clear_port_feature(struct usb_device *dev, int port, int feature) { return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port, NULL, 0, USB_CNTL_TIMEOUT); } static int usb_set_port_feature(struct usb_device *dev, int port, int feature) { return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port, NULL, 0, USB_CNTL_TIMEOUT); } static int usb_get_hub_status(struct usb_device *dev, void *data) { return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); } |
08f3bb0bc usb: add device c... |
92 |
int usb_get_port_status(struct usb_device *dev, int port, void *data) |
23faf2bc9 USB: Separate out... |
93 94 95 96 97 98 99 100 101 102 103 |
{ return 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); } static void usb_hub_power_on(struct usb_hub_device *hub) { int i; struct usb_device *dev; |
a1a28c6e6 USB: relax usbcor... |
104 |
unsigned pgood_delay = hub->desc.bPwrOn2PwrGood * 2; |
319418c01 usb: hub: allow p... |
105 |
const char *env; |
23faf2bc9 USB: Separate out... |
106 107 |
dev = hub->pusb_dev; |
0bf796f7a usb: hub: Paralle... |
108 |
|
ceb4972a8 usb: common: Weed... |
109 110 |
debug("enabling power on all ports "); |
23faf2bc9 USB: Separate out... |
111 112 |
for (i = 0; i < dev->maxchild; i++) { usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); |
ceb4972a8 usb: common: Weed... |
113 114 |
debug("port %d returns %lX ", i + 1, dev->status); |
23faf2bc9 USB: Separate out... |
115 |
} |
a1a28c6e6 USB: relax usbcor... |
116 |
|
c998da0d6 usb: Change power... |
117 118 119 120 121 122 123 124 |
#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 |
0d437bcaf usb: hub: fix pow... |
125 126 127 |
/* * Wait for power to become stable, * plus spec-defined max time for device to connect |
319418c01 usb: hub: allow p... |
128 129 |
* but allow this time to be increased via env variable as some * devices break the spec and require longer warm-up times |
0d437bcaf usb: hub: fix pow... |
130 |
*/ |
319418c01 usb: hub: allow p... |
131 132 133 134 135 136 |
env = getenv("usb_pgood_delay"); if (env) pgood_delay = max(pgood_delay, (unsigned)simple_strtol(env, NULL, 0)); debug("pgood_delay=%dms ", pgood_delay); |
c998da0d6 usb: Change power... |
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
/* * 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 ", dev->devnum, max(100, (int)pgood_delay), max(100, (int)pgood_delay) + 1000); |
23faf2bc9 USB: Separate out... |
154 155 156 157 158 |
} void usb_hub_reset(void) { usb_hub_index = 0; |
c998da0d6 usb: Change power... |
159 160 161 |
/* Zero out global hub_dev in case its re-used again */ memset(hub_dev, 0, sizeof(hub_dev)); |
23faf2bc9 USB: Separate out... |
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
} static struct usb_hub_device *usb_hub_allocate(void) { if (usb_hub_index < USB_MAX_HUB) return &hub_dev[usb_hub_index++]; printf("ERROR: USB_MAX_HUB (%d) reached ", USB_MAX_HUB); return NULL; } #define MAX_TRIES 5 static inline char *portspeed(int portstatus) { |
55f4b5754 usb: fix: Fixing ... |
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
char *speed_str; switch (portstatus & USB_PORT_STAT_SPEED_MASK) { case USB_PORT_STAT_SUPER_SPEED: speed_str = "5 Gb/s"; break; case USB_PORT_STAT_HIGH_SPEED: speed_str = "480 Mb/s"; break; case USB_PORT_STAT_LOW_SPEED: speed_str = "1.5 Mb/s"; break; default: speed_str = "12 Mb/s"; break; } return speed_str; |
23faf2bc9 USB: Separate out... |
196 |
} |
862e75c0d dm: usb: Refactor... |
197 |
int legacy_hub_port_reset(struct usb_device *dev, int port, |
23faf2bc9 USB: Separate out... |
198 199 |
unsigned short *portstat) { |
ad84a42fc usb: legacy_hub_p... |
200 |
int err, tries; |
f57661394 USB: Align buffer... |
201 |
ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); |
23faf2bc9 USB: Separate out... |
202 |
unsigned short portstatus, portchange; |
f7f601002 usb: legacy_hub_p... |
203 |
int delay = HUB_SHORT_RESET_TIME; /* start with short reset delay */ |
23faf2bc9 USB: Separate out... |
204 |
|
054fe48eb dm: usb: Add driv... |
205 206 207 208 209 210 211 212 |
#ifdef CONFIG_DM_USB debug("%s: resetting '%s' port %d... ", __func__, dev->dev->name, port + 1); #else debug("%s: resetting port %d... ", __func__, port + 1); #endif |
23faf2bc9 USB: Separate out... |
213 |
for (tries = 0; tries < MAX_TRIES; tries++) { |
ad84a42fc usb: legacy_hub_p... |
214 215 216 |
err = usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET); if (err < 0) return err; |
23faf2bc9 USB: Separate out... |
217 |
|
f7f601002 usb: legacy_hub_p... |
218 |
mdelay(delay); |
23faf2bc9 USB: Separate out... |
219 |
|
f57661394 USB: Align buffer... |
220 |
if (usb_get_port_status(dev, port + 1, portsts) < 0) { |
ceb4972a8 usb: common: Weed... |
221 222 223 |
debug("get_port_status failed status %lX ", dev->status); |
23faf2bc9 USB: Separate out... |
224 225 |
return -1; } |
f57661394 USB: Align buffer... |
226 227 |
portstatus = le16_to_cpu(portsts->wPortStatus); portchange = le16_to_cpu(portsts->wPortChange); |
23faf2bc9 USB: Separate out... |
228 |
|
ceb4972a8 usb: common: Weed... |
229 230 231 |
debug("portstatus %x, change %x, %s ", portstatus, portchange, portspeed(portstatus)); |
23faf2bc9 USB: Separate out... |
232 |
|
ceb4972a8 usb: common: Weed... |
233 234 235 236 237 238 |
debug("STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \ " USB_PORT_STAT_ENABLE %d ", (portchange & USB_PORT_STAT_C_CONNECTION) ? 1 : 0, (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0, (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0); |
23faf2bc9 USB: Separate out... |
239 |
|
74c0d756d usb: hub: don't c... |
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
/* * 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. */ |
23faf2bc9 USB: Separate out... |
256 257 258 |
if (portstatus & USB_PORT_STAT_ENABLE) break; |
f7f601002 usb: legacy_hub_p... |
259 260 |
/* Switch to long reset delay for the next round */ delay = HUB_LONG_RESET_TIME; |
23faf2bc9 USB: Separate out... |
261 262 263 |
} if (tries == MAX_TRIES) { |
ceb4972a8 usb: common: Weed... |
264 265 266 267 268 |
debug("Cannot enable port %i after %i retries, " \ "disabling port. ", port + 1, MAX_TRIES); debug("Maybe the USB cable is bad? "); |
23faf2bc9 USB: Separate out... |
269 270 271 272 273 274 275 |
return -1; } usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_RESET); *portstat = portstatus; return 0; } |
054fe48eb dm: usb: Add driv... |
276 277 278 |
#ifdef CONFIG_DM_USB int hub_port_reset(struct udevice *dev, int port, unsigned short *portstat) { |
bcbe3d157 dm: Rename dev_ge... |
279 |
struct usb_device *udev = dev_get_parent_priv(dev); |
054fe48eb dm: usb: Add driv... |
280 281 282 283 |
return legacy_hub_port_reset(udev, port, portstat); } #endif |
23faf2bc9 USB: Separate out... |
284 |
|
79b588872 dm: usb: Adjust u... |
285 |
int usb_hub_port_connect_change(struct usb_device *dev, int port) |
23faf2bc9 USB: Separate out... |
286 |
{ |
f57661394 USB: Align buffer... |
287 |
ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); |
23faf2bc9 USB: Separate out... |
288 |
unsigned short portstatus; |
79b588872 dm: usb: Adjust u... |
289 |
int ret, speed; |
23faf2bc9 USB: Separate out... |
290 291 |
/* Check status */ |
79b588872 dm: usb: Adjust u... |
292 293 |
ret = usb_get_port_status(dev, port + 1, portsts); if (ret < 0) { |
ceb4972a8 usb: common: Weed... |
294 295 |
debug("get_port_status failed "); |
79b588872 dm: usb: Adjust u... |
296 |
return ret; |
23faf2bc9 USB: Separate out... |
297 |
} |
f57661394 USB: Align buffer... |
298 |
portstatus = le16_to_cpu(portsts->wPortStatus); |
ceb4972a8 usb: common: Weed... |
299 300 301 302 303 |
debug("portstatus %x, change %x, %s ", portstatus, le16_to_cpu(portsts->wPortChange), portspeed(portstatus)); |
23faf2bc9 USB: Separate out... |
304 305 306 307 308 309 |
/* 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 (((!(portstatus & USB_PORT_STAT_CONNECTION)) && |
054fe48eb dm: usb: Add driv... |
310 311 |
(!(portstatus & USB_PORT_STAT_ENABLE))) || usb_device_has_child_on_port(dev, port)) { |
ceb4972a8 usb: common: Weed... |
312 313 |
debug("usb_disconnect(&hub->children[port]); "); |
23faf2bc9 USB: Separate out... |
314 315 |
/* Return now if nothing is connected */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) |
79b588872 dm: usb: Adjust u... |
316 |
return -ENOTCONN; |
23faf2bc9 USB: Separate out... |
317 |
} |
23faf2bc9 USB: Separate out... |
318 319 |
/* Reset the port */ |
862e75c0d dm: usb: Refactor... |
320 |
ret = legacy_hub_port_reset(dev, port, &portstatus); |
79b588872 dm: usb: Adjust u... |
321 |
if (ret < 0) { |
45b9ea1da usb: Stop reset p... |
322 323 324 |
if (ret != -ENXIO) printf("cannot reset port %i!? ", port + 1); |
79b588872 dm: usb: Adjust u... |
325 |
return ret; |
23faf2bc9 USB: Separate out... |
326 |
} |
55f4b5754 usb: fix: Fixing ... |
327 328 |
switch (portstatus & USB_PORT_STAT_SPEED_MASK) { case USB_PORT_STAT_SUPER_SPEED: |
79b588872 dm: usb: Adjust u... |
329 |
speed = USB_SPEED_SUPER; |
55f4b5754 usb: fix: Fixing ... |
330 331 |
break; case USB_PORT_STAT_HIGH_SPEED: |
79b588872 dm: usb: Adjust u... |
332 |
speed = USB_SPEED_HIGH; |
55f4b5754 usb: fix: Fixing ... |
333 334 |
break; case USB_PORT_STAT_LOW_SPEED: |
79b588872 dm: usb: Adjust u... |
335 |
speed = USB_SPEED_LOW; |
55f4b5754 usb: fix: Fixing ... |
336 337 |
break; default: |
79b588872 dm: usb: Adjust u... |
338 |
speed = USB_SPEED_FULL; |
55f4b5754 usb: fix: Fixing ... |
339 340 |
break; } |
23faf2bc9 USB: Separate out... |
341 |
|
054fe48eb dm: usb: Add driv... |
342 343 344 345 346 347 |
#ifdef CONFIG_DM_USB struct udevice *child; ret = usb_scan_device(dev->dev, port + 1, speed, &child); #else struct usb_device *usb; |
79b588872 dm: usb: Adjust u... |
348 349 350 351 352 |
ret = usb_alloc_new_device(dev->controller, &usb); if (ret) { printf("cannot create new device: ret=%d", ret); return ret; } |
23faf2bc9 USB: Separate out... |
353 |
dev->children[port] = usb; |
79b588872 dm: usb: Adjust u... |
354 |
usb->speed = speed; |
23faf2bc9 USB: Separate out... |
355 356 357 |
usb->parent = dev; usb->portnr = port + 1; /* Run it through the hoops (find a driver, etc) */ |
79b588872 dm: usb: Adjust u... |
358 359 |
ret = usb_new_device(usb); if (ret < 0) { |
23faf2bc9 USB: Separate out... |
360 |
/* Woops, disable the port */ |
79b588872 dm: usb: Adjust u... |
361 |
usb_free_device(dev->controller); |
359439d28 usb: Clean up new... |
362 |
dev->children[port] = NULL; |
054fe48eb dm: usb: Add driv... |
363 364 365 |
} #endif if (ret < 0) { |
ceb4972a8 usb: common: Weed... |
366 367 |
debug("hub: disabling port %d ", port + 1); |
23faf2bc9 USB: Separate out... |
368 369 |
usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); } |
79b588872 dm: usb: Adjust u... |
370 371 |
return ret; |
23faf2bc9 USB: Separate out... |
372 |
} |
c998da0d6 usb: Change power... |
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
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 "); if (get_timer(0) >= hub->connect_timeout) { debug("devnum=%d port=%d: timeout ", dev->devnum, i + 1); /* Remove this device from scanning list */ list_del(&usb_scan->list); free(usb_scan); return 0; } |
d81db48d4 usb: hub: Don't c... |
407 |
return 0; |
c998da0d6 usb: Change power... |
408 409 410 411 412 413 |
} portstatus = le16_to_cpu(portsts->wPortStatus); portchange = le16_to_cpu(portsts->wPortChange); debug("Port %d Status %X Change %X ", i + 1, portstatus, portchange); |
f7a9e5dd0 usb: hub: Update ... |
414 415 416 417 418 419 420 421 422 |
/* * 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)) { |
c998da0d6 usb: Change power... |
423 424 425 426 427 428 429 430 431 432 433 |
if (get_timer(0) >= hub->connect_timeout) { debug("devnum=%d port=%d: timeout ", dev->devnum, i + 1); /* Remove this device from scanning list */ list_del(&usb_scan->list); free(usb_scan); return 0; } return 0; } |
c998da0d6 usb: Change power... |
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 |
/* A new USB device is ready at this point */ debug("devnum=%d port=%d: USB dev found ", 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 ", 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... ", i + 1); usb_hub_port_connect_change(dev, i); } #endif } if (portstatus & USB_PORT_STAT_SUSPEND) { debug("port %d suspend change ", 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 ", 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 */ |
eae4b2b67 Fix spelling of "... |
489 490 |
printf("Port %d over-current occurred %d times ", i + 1, |
c998da0d6 usb: Change power... |
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 |
hub->overcurrent_count[i]); } if (portchange & USB_PORT_STAT_C_RESET) { debug("port %d reset change ", 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; } |
23faf2bc9 USB: Separate out... |
548 549 550 |
static int usb_hub_configure(struct usb_device *dev) { |
eaf3e613e usb: Use well-kno... |
551 |
int i, length; |
f57661394 USB: Align buffer... |
552 553 |
ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, USB_BUFSIZ); unsigned char *bitmap; |
93ad908c4 usb: do explicit ... |
554 |
short hubCharacteristics; |
23faf2bc9 USB: Separate out... |
555 556 |
struct usb_hub_descriptor *descriptor; struct usb_hub_device *hub; |
ceb4972a8 usb: common: Weed... |
557 |
__maybe_unused struct usb_hub_status *hubsts; |
361ad6afc dm: usb: Split hu... |
558 |
int ret; |
23faf2bc9 USB: Separate out... |
559 560 561 562 |
/* "allocate" Hub device */ hub = usb_hub_allocate(); if (hub == NULL) |
361ad6afc dm: usb: Split hu... |
563 |
return -ENOMEM; |
23faf2bc9 USB: Separate out... |
564 565 |
hub->pusb_dev = dev; /* Get the the hub descriptor */ |
361ad6afc dm: usb: Split hu... |
566 567 |
ret = usb_get_hub_descriptor(dev, buffer, 4); if (ret < 0) { |
ceb4972a8 usb: common: Weed... |
568 569 570 |
debug("usb_hub_configure: failed to get hub " \ "descriptor, giving up %lX ", dev->status); |
361ad6afc dm: usb: Split hu... |
571 |
return ret; |
23faf2bc9 USB: Separate out... |
572 573 |
} descriptor = (struct usb_hub_descriptor *)buffer; |
b41411954 linux/kernel.h: s... |
574 575 |
length = min_t(int, descriptor->bLength, sizeof(struct usb_hub_descriptor)); |
23faf2bc9 USB: Separate out... |
576 |
|
361ad6afc dm: usb: Split hu... |
577 578 |
ret = usb_get_hub_descriptor(dev, buffer, length); if (ret < 0) { |
ceb4972a8 usb: common: Weed... |
579 580 581 |
debug("usb_hub_configure: failed to get hub " \ "descriptor 2nd giving up %lX ", dev->status); |
361ad6afc dm: usb: Split hu... |
582 |
return ret; |
23faf2bc9 USB: Separate out... |
583 |
} |
eaf3e613e usb: Use well-kno... |
584 |
memcpy((unsigned char *)&hub->desc, buffer, length); |
23faf2bc9 USB: Separate out... |
585 |
/* adjust 16bit values */ |
93ad908c4 usb: do explicit ... |
586 587 588 |
put_unaligned(le16_to_cpu(get_unaligned( &descriptor->wHubCharacteristics)), &hub->desc.wHubCharacteristics); |
23faf2bc9 USB: Separate out... |
589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
/* set the bitmap */ bitmap = (unsigned char *)&hub->desc.DeviceRemovable[0]; /* devices not removable by default */ memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); bitmap = (unsigned char *)&hub->desc.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]; for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) hub->desc.PortPowerCtrlMask[i] = descriptor->PortPowerCtrlMask[i]; dev->maxchild = descriptor->bNbrPorts; |
ceb4972a8 usb: common: Weed... |
603 604 |
debug("%d ports detected ", dev->maxchild); |
23faf2bc9 USB: Separate out... |
605 |
|
93ad908c4 usb: do explicit ... |
606 607 |
hubCharacteristics = get_unaligned(&hub->desc.wHubCharacteristics); switch (hubCharacteristics & HUB_CHAR_LPSM) { |
23faf2bc9 USB: Separate out... |
608 |
case 0x00: |
ceb4972a8 usb: common: Weed... |
609 610 |
debug("ganged power switching "); |
23faf2bc9 USB: Separate out... |
611 612 |
break; case 0x01: |
ceb4972a8 usb: common: Weed... |
613 614 |
debug("individual port power switching "); |
23faf2bc9 USB: Separate out... |
615 616 617 |
break; case 0x02: case 0x03: |
ceb4972a8 usb: common: Weed... |
618 619 |
debug("unknown reserved power switching mode "); |
23faf2bc9 USB: Separate out... |
620 621 |
break; } |
93ad908c4 usb: do explicit ... |
622 |
if (hubCharacteristics & HUB_CHAR_COMPOUND) |
ceb4972a8 usb: common: Weed... |
623 624 |
debug("part of a compound device "); |
23faf2bc9 USB: Separate out... |
625 |
else |
ceb4972a8 usb: common: Weed... |
626 627 |
debug("standalone hub "); |
23faf2bc9 USB: Separate out... |
628 |
|
93ad908c4 usb: do explicit ... |
629 |
switch (hubCharacteristics & HUB_CHAR_OCPM) { |
23faf2bc9 USB: Separate out... |
630 |
case 0x00: |
ceb4972a8 usb: common: Weed... |
631 632 |
debug("global over-current protection "); |
23faf2bc9 USB: Separate out... |
633 634 |
break; case 0x08: |
ceb4972a8 usb: common: Weed... |
635 636 |
debug("individual port over-current protection "); |
23faf2bc9 USB: Separate out... |
637 638 639 |
break; case 0x10: case 0x18: |
ceb4972a8 usb: common: Weed... |
640 641 |
debug("no over-current protection "); |
23faf2bc9 USB: Separate out... |
642 643 |
break; } |
ceb4972a8 usb: common: Weed... |
644 645 646 647 648 649 |
debug("power on to power good time: %dms ", descriptor->bPwrOn2PwrGood * 2); debug("hub controller current requirement: %dmA ", descriptor->bHubContrCurrent); |
23faf2bc9 USB: Separate out... |
650 651 |
for (i = 0; i < dev->maxchild; i++) |
ceb4972a8 usb: common: Weed... |
652 653 654 655 |
debug("port %d is%s removable ", i + 1, hub->desc.DeviceRemovable[(i + 1) / 8] & \ (1 << ((i + 1) % 8)) ? " not" : ""); |
23faf2bc9 USB: Separate out... |
656 657 |
if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { |
ceb4972a8 usb: common: Weed... |
658 659 660 |
debug("usb_hub_configure: failed to get Status - " \ "too long: %d ", descriptor->bLength); |
361ad6afc dm: usb: Split hu... |
661 |
return -EFBIG; |
23faf2bc9 USB: Separate out... |
662 |
} |
361ad6afc dm: usb: Split hu... |
663 664 |
ret = usb_get_hub_status(dev, buffer); if (ret < 0) { |
ceb4972a8 usb: common: Weed... |
665 666 667 |
debug("usb_hub_configure: failed to get Status %lX ", dev->status); |
361ad6afc dm: usb: Split hu... |
668 |
return ret; |
23faf2bc9 USB: Separate out... |
669 |
} |
ceb4972a8 usb: common: Weed... |
670 |
#ifdef DEBUG |
23faf2bc9 USB: Separate out... |
671 672 |
hubsts = (struct usb_hub_status *)buffer; #endif |
ceb4972a8 usb: common: Weed... |
673 674 675 676 677 678 679 680 681 682 683 684 685 |
debug("get_hub_status returned status %X, change %X ", le16_to_cpu(hubsts->wHubStatus), le16_to_cpu(hubsts->wHubChange)); debug("local power source is %s ", (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? \ "lost (inactive)" : "good"); debug("%sover-current condition exists ", (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \ "" : "no "); |
23faf2bc9 USB: Separate out... |
686 |
usb_hub_power_on(hub); |
3615a996a USB: usb-hub: Add... |
687 688 689 690 691 692 693 |
/* * 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); |
c998da0d6 usb: Change power... |
694 695 696 697 698 699 700 |
/* * 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. */ |
23faf2bc9 USB: Separate out... |
701 |
for (i = 0; i < dev->maxchild; i++) { |
c998da0d6 usb: Change power... |
702 |
struct usb_device_scan *usb_scan; |
b6d7852cf usbh/ehci: Increa... |
703 |
|
c998da0d6 usb: Change power... |
704 705 706 707 708 |
usb_scan = calloc(1, sizeof(*usb_scan)); if (!usb_scan) { printf("Can't allocate memory for USB device! "); return -ENOMEM; |
23faf2bc9 USB: Separate out... |
709 |
} |
c998da0d6 usb: Change power... |
710 711 712 713 714 |
usb_scan->dev = dev; usb_scan->hub = hub; usb_scan->port = i; list_add_tail(&usb_scan->list, &usb_scan_list); } |
23faf2bc9 USB: Separate out... |
715 |
|
c998da0d6 usb: Change power... |
716 717 718 719 |
/* * And now call the scanning code which loops over the generated list */ ret = usb_device_list_scan(); |
23faf2bc9 USB: Separate out... |
720 |
|
c998da0d6 usb: Change power... |
721 |
return ret; |
23faf2bc9 USB: Separate out... |
722 |
} |
361ad6afc dm: usb: Split hu... |
723 |
static int usb_hub_check(struct usb_device *dev, int ifnum) |
23faf2bc9 USB: Separate out... |
724 725 |
{ struct usb_interface *iface; |
361ad6afc dm: usb: Split hu... |
726 |
struct usb_endpoint_descriptor *ep = NULL; |
23faf2bc9 USB: Separate out... |
727 728 729 730 |
iface = &dev->config.if_desc[ifnum]; /* Is it a hub? */ if (iface->desc.bInterfaceClass != USB_CLASS_HUB) |
361ad6afc dm: usb: Split hu... |
731 |
goto err; |
23faf2bc9 USB: Separate out... |
732 733 734 735 |
/* 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)) |
361ad6afc dm: usb: Split hu... |
736 |
goto err; |
23faf2bc9 USB: Separate out... |
737 738 |
/* Multiple endpoints? What kind of mutant ninja-hub is this? */ if (iface->desc.bNumEndpoints != 1) |
361ad6afc dm: usb: Split hu... |
739 |
goto err; |
23faf2bc9 USB: Separate out... |
740 741 742 |
ep = &iface->ep_desc[0]; /* Output endpoint? Curiousier and curiousier.. */ if (!(ep->bEndpointAddress & USB_DIR_IN)) |
361ad6afc dm: usb: Split hu... |
743 |
goto err; |
23faf2bc9 USB: Separate out... |
744 745 |
/* If it's not an interrupt endpoint, we'd better punt! */ if ((ep->bmAttributes & 3) != 3) |
361ad6afc dm: usb: Split hu... |
746 |
goto err; |
23faf2bc9 USB: Separate out... |
747 |
/* We found a hub */ |
ceb4972a8 usb: common: Weed... |
748 749 |
debug("USB hub found "); |
361ad6afc dm: usb: Split hu... |
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 |
return 0; err: debug("USB hub not found: bInterfaceClass=%d, bInterfaceSubClass=%d, bNumEndpoints=%d ", 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; |
23faf2bc9 USB: Separate out... |
772 773 774 |
ret = usb_hub_configure(dev); return ret; } |
054fe48eb dm: usb: Add driv... |
775 776 777 778 |
#ifdef CONFIG_DM_USB int usb_hub_scan(struct udevice *hub) { |
bcbe3d157 dm: Rename dev_ge... |
779 |
struct usb_device *udev = dev_get_parent_priv(hub); |
054fe48eb dm: usb: Add driv... |
780 781 782 |
return usb_hub_configure(udev); } |
054fe48eb dm: usb: Add driv... |
783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 |
static int usb_hub_post_probe(struct udevice *dev) { debug("%s ", __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", |
911954859 dm: Use dm_scan_f... |
805 |
.post_bind = dm_scan_fdt_dev, |
054fe48eb dm: usb: Add driv... |
806 807 808 809 810 811 812 813 814 815 816 817 818 |
.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 */ }; |
abb59cffc dm: usb: Adjust t... |
819 |
U_BOOT_USB_DEVICE(usb_generic_hub, hub_id_table); |
054fe48eb dm: usb: Add driv... |
820 821 |
#endif |