Blame view

common/usb_hub.c 21 KB
23faf2bc9   Marek Vasut   USB: Separate out...
1
  /*
23faf2bc9   Marek Vasut   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   Wolfgang Denk   Add GPL-2.0+ SPDX...
17
   * SPDX-License-Identifier:	GPL-2.0+
23faf2bc9   Marek Vasut   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   Simon Glass   dm: usb: Add driv...
27
  #include <dm.h>
79b588872   Simon Glass   dm: usb: Adjust u...
28
  #include <errno.h>
cf92e05c0   Simon Glass   Move ALLOC_CACHE_...
29
  #include <memalign.h>
23faf2bc9   Marek Vasut   USB: Separate out...
30
  #include <asm/processor.h>
93ad908c4   Lucas Stach   usb: do explicit ...
31
  #include <asm/unaligned.h>
23faf2bc9   Marek Vasut   USB: Separate out...
32
  #include <linux/ctype.h>
c998da0d6   Stefan Roese   usb: Change power...
33
  #include <linux/list.h>
23faf2bc9   Marek Vasut   USB: Separate out...
34
  #include <asm/byteorder.h>
3884c98c3   Simon Glass   dm: usb: Avoid ti...
35
36
37
  #ifdef CONFIG_SANDBOX
  #include <asm/state.h>
  #endif
23faf2bc9   Marek Vasut   USB: Separate out...
38
  #include <asm/unaligned.h>
054fe48eb   Simon Glass   dm: usb: Add driv...
39
40
  
  DECLARE_GLOBAL_DATA_PTR;
23faf2bc9   Marek Vasut   USB: Separate out...
41
42
  
  #include <usb.h>
23faf2bc9   Marek Vasut   USB: Separate out...
43

23faf2bc9   Marek Vasut   USB: Separate out...
44
  #define USB_BUFSIZ	512
f7f601002   Stefan Roese   usb: legacy_hub_p...
45
46
  #define HUB_SHORT_RESET_TIME	20
  #define HUB_LONG_RESET_TIME	200
c998da0d6   Stefan Roese   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   Simon Glass   dm: usb: Add driv...
55
  /* TODO(sjg@chromium.org): Remove this when CONFIG_DM_USB is defined */
23faf2bc9   Marek Vasut   USB: Separate out...
56
57
  static struct usb_hub_device hub_dev[USB_MAX_HUB];
  static int usb_hub_index;
c998da0d6   Stefan Roese   usb: Change power...
58
  static LIST_HEAD(usb_scan_list);
23faf2bc9   Marek Vasut   USB: Separate out...
59

3615a996a   Dan Murphy   USB: usb-hub: Add...
60
61
62
63
  __weak void usb_hub_reset_devices(int port)
  {
  	return;
  }
23faf2bc9   Marek Vasut   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   Vincent Palatin   usb: add device c...
92
  int usb_get_port_status(struct usb_device *dev, int port, void *data)
23faf2bc9   Marek Vasut   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   Wolfgang Grandegger   USB: relax usbcor...
104
  	unsigned pgood_delay = hub->desc.bPwrOn2PwrGood * 2;
319418c01   Tim Harvey   usb: hub: allow p...
105
  	const char *env;
23faf2bc9   Marek Vasut   USB: Separate out...
106
107
  
  	dev = hub->pusb_dev;
0bf796f7a   Vivek Gautam   usb: hub: Paralle...
108

ceb4972a8   Vivek Gautam   usb: common: Weed...
109
110
  	debug("enabling power on all ports
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
111
112
  	for (i = 0; i < dev->maxchild; i++) {
  		usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER);
ceb4972a8   Vivek Gautam   usb: common: Weed...
113
114
  		debug("port %d returns %lX
  ", i + 1, dev->status);
23faf2bc9   Marek Vasut   USB: Separate out...
115
  	}
a1a28c6e6   Wolfgang Grandegger   USB: relax usbcor...
116

c998da0d6   Stefan Roese   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   Stephen Warren   usb: hub: fix pow...
125
126
127
  	/*
  	 * Wait for power to become stable,
  	 * plus spec-defined max time for device to connect
319418c01   Tim Harvey   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   Stephen Warren   usb: hub: fix pow...
130
  	 */
319418c01   Tim Harvey   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   Stefan Roese   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   Marek Vasut   USB: Separate out...
154
155
156
157
158
  }
  
  void usb_hub_reset(void)
  {
  	usb_hub_index = 0;
c998da0d6   Stefan Roese   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   Marek Vasut   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   Vivek Gautam   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   Marek Vasut   USB: Separate out...
196
  }
862e75c0d   Simon Glass   dm: usb: Refactor...
197
  int legacy_hub_port_reset(struct usb_device *dev, int port,
23faf2bc9   Marek Vasut   USB: Separate out...
198
199
  			unsigned short *portstat)
  {
ad84a42fc   Hans de Goede   usb: legacy_hub_p...
200
  	int err, tries;
f57661394   Puneet Saxena   USB: Align buffer...
201
  	ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
23faf2bc9   Marek Vasut   USB: Separate out...
202
  	unsigned short portstatus, portchange;
f7f601002   Stefan Roese   usb: legacy_hub_p...
203
  	int delay = HUB_SHORT_RESET_TIME; /* start with short reset delay */
23faf2bc9   Marek Vasut   USB: Separate out...
204

054fe48eb   Simon Glass   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   Marek Vasut   USB: Separate out...
213
  	for (tries = 0; tries < MAX_TRIES; tries++) {
ad84a42fc   Hans de Goede   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   Marek Vasut   USB: Separate out...
217

f7f601002   Stefan Roese   usb: legacy_hub_p...
218
  		mdelay(delay);
23faf2bc9   Marek Vasut   USB: Separate out...
219

f57661394   Puneet Saxena   USB: Align buffer...
220
  		if (usb_get_port_status(dev, port + 1, portsts) < 0) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
221
222
223
  			debug("get_port_status failed status %lX
  ",
  			      dev->status);
23faf2bc9   Marek Vasut   USB: Separate out...
224
225
  			return -1;
  		}
f57661394   Puneet Saxena   USB: Align buffer...
226
227
  		portstatus = le16_to_cpu(portsts->wPortStatus);
  		portchange = le16_to_cpu(portsts->wPortChange);
23faf2bc9   Marek Vasut   USB: Separate out...
228

ceb4972a8   Vivek Gautam   usb: common: Weed...
229
230
231
  		debug("portstatus %x, change %x, %s
  ", portstatus, portchange,
  							portspeed(portstatus));
23faf2bc9   Marek Vasut   USB: Separate out...
232

ceb4972a8   Vivek Gautam   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   Marek Vasut   USB: Separate out...
239

74c0d756d   Stephen Warren   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   Marek Vasut   USB: Separate out...
256
257
258
  
  		if (portstatus & USB_PORT_STAT_ENABLE)
  			break;
f7f601002   Stefan Roese   usb: legacy_hub_p...
259
260
  		/* Switch to long reset delay for the next round */
  		delay = HUB_LONG_RESET_TIME;
23faf2bc9   Marek Vasut   USB: Separate out...
261
262
263
  	}
  
  	if (tries == MAX_TRIES) {
ceb4972a8   Vivek Gautam   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   Marek Vasut   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   Simon Glass   dm: usb: Add driv...
276
277
278
  #ifdef CONFIG_DM_USB
  int hub_port_reset(struct udevice *dev, int port, unsigned short *portstat)
  {
bcbe3d157   Simon Glass   dm: Rename dev_ge...
279
  	struct usb_device *udev = dev_get_parent_priv(dev);
054fe48eb   Simon Glass   dm: usb: Add driv...
280
281
282
283
  
  	return legacy_hub_port_reset(udev, port, portstat);
  }
  #endif
23faf2bc9   Marek Vasut   USB: Separate out...
284

79b588872   Simon Glass   dm: usb: Adjust u...
285
  int usb_hub_port_connect_change(struct usb_device *dev, int port)
23faf2bc9   Marek Vasut   USB: Separate out...
286
  {
f57661394   Puneet Saxena   USB: Align buffer...
287
  	ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
23faf2bc9   Marek Vasut   USB: Separate out...
288
  	unsigned short portstatus;
79b588872   Simon Glass   dm: usb: Adjust u...
289
  	int ret, speed;
23faf2bc9   Marek Vasut   USB: Separate out...
290
291
  
  	/* Check status */
79b588872   Simon Glass   dm: usb: Adjust u...
292
293
  	ret = usb_get_port_status(dev, port + 1, portsts);
  	if (ret < 0) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
294
295
  		debug("get_port_status failed
  ");
79b588872   Simon Glass   dm: usb: Adjust u...
296
  		return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
297
  	}
f57661394   Puneet Saxena   USB: Align buffer...
298
  	portstatus = le16_to_cpu(portsts->wPortStatus);
ceb4972a8   Vivek Gautam   usb: common: Weed...
299
300
301
302
303
  	debug("portstatus %x, change %x, %s
  ",
  	      portstatus,
  	      le16_to_cpu(portsts->wPortChange),
  	      portspeed(portstatus));
23faf2bc9   Marek Vasut   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   Simon Glass   dm: usb: Add driv...
310
311
  	     (!(portstatus & USB_PORT_STAT_ENABLE))) ||
  	    usb_device_has_child_on_port(dev, port)) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
312
313
  		debug("usb_disconnect(&hub->children[port]);
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
314
315
  		/* Return now if nothing is connected */
  		if (!(portstatus & USB_PORT_STAT_CONNECTION))
79b588872   Simon Glass   dm: usb: Adjust u...
316
  			return -ENOTCONN;
23faf2bc9   Marek Vasut   USB: Separate out...
317
  	}
23faf2bc9   Marek Vasut   USB: Separate out...
318
319
  
  	/* Reset the port */
862e75c0d   Simon Glass   dm: usb: Refactor...
320
  	ret = legacy_hub_port_reset(dev, port, &portstatus);
79b588872   Simon Glass   dm: usb: Adjust u...
321
  	if (ret < 0) {
45b9ea1da   Hans de Goede   usb: Stop reset p...
322
323
324
  		if (ret != -ENXIO)
  			printf("cannot reset port %i!?
  ", port + 1);
79b588872   Simon Glass   dm: usb: Adjust u...
325
  		return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
326
  	}
55f4b5754   Vivek Gautam   usb: fix: Fixing ...
327
328
  	switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
  	case USB_PORT_STAT_SUPER_SPEED:
79b588872   Simon Glass   dm: usb: Adjust u...
329
  		speed = USB_SPEED_SUPER;
55f4b5754   Vivek Gautam   usb: fix: Fixing ...
330
331
  		break;
  	case USB_PORT_STAT_HIGH_SPEED:
79b588872   Simon Glass   dm: usb: Adjust u...
332
  		speed = USB_SPEED_HIGH;
55f4b5754   Vivek Gautam   usb: fix: Fixing ...
333
334
  		break;
  	case USB_PORT_STAT_LOW_SPEED:
79b588872   Simon Glass   dm: usb: Adjust u...
335
  		speed = USB_SPEED_LOW;
55f4b5754   Vivek Gautam   usb: fix: Fixing ...
336
337
  		break;
  	default:
79b588872   Simon Glass   dm: usb: Adjust u...
338
  		speed = USB_SPEED_FULL;
55f4b5754   Vivek Gautam   usb: fix: Fixing ...
339
340
  		break;
  	}
23faf2bc9   Marek Vasut   USB: Separate out...
341

054fe48eb   Simon Glass   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   Simon Glass   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   Marek Vasut   USB: Separate out...
353
  	dev->children[port] = usb;
79b588872   Simon Glass   dm: usb: Adjust u...
354
  	usb->speed = speed;
23faf2bc9   Marek Vasut   USB: Separate out...
355
356
357
  	usb->parent = dev;
  	usb->portnr = port + 1;
  	/* Run it through the hoops (find a driver, etc) */
79b588872   Simon Glass   dm: usb: Adjust u...
358
359
  	ret = usb_new_device(usb);
  	if (ret < 0) {
23faf2bc9   Marek Vasut   USB: Separate out...
360
  		/* Woops, disable the port */
79b588872   Simon Glass   dm: usb: Adjust u...
361
  		usb_free_device(dev->controller);
359439d28   Milind Choudhary   usb: Clean up new...
362
  		dev->children[port] = NULL;
054fe48eb   Simon Glass   dm: usb: Add driv...
363
364
365
  	}
  #endif
  	if (ret < 0) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
366
367
  		debug("hub: disabling port %d
  ", port + 1);
23faf2bc9   Marek Vasut   USB: Separate out...
368
369
  		usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE);
  	}
79b588872   Simon Glass   dm: usb: Adjust u...
370
371
  
  	return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
372
  }
c998da0d6   Stefan Roese   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   Marek Vasut   usb: hub: Don't c...
407
  		return 0;
c998da0d6   Stefan Roese   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   Bin Meng   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   Stefan Roese   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   Stefan Roese   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   Vagrant Cascadian   Fix spelling of "...
489
490
  		printf("Port %d over-current occurred %d times
  ", i + 1,
c998da0d6   Stefan Roese   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   Marek Vasut   USB: Separate out...
548
549
550
  
  static int usb_hub_configure(struct usb_device *dev)
  {
eaf3e613e   Julius Werner   usb: Use well-kno...
551
  	int i, length;
f57661394   Puneet Saxena   USB: Align buffer...
552
553
  	ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, USB_BUFSIZ);
  	unsigned char *bitmap;
93ad908c4   Lucas Stach   usb: do explicit ...
554
  	short hubCharacteristics;
23faf2bc9   Marek Vasut   USB: Separate out...
555
556
  	struct usb_hub_descriptor *descriptor;
  	struct usb_hub_device *hub;
ceb4972a8   Vivek Gautam   usb: common: Weed...
557
  	__maybe_unused struct usb_hub_status *hubsts;
361ad6afc   Simon Glass   dm: usb: Split hu...
558
  	int ret;
23faf2bc9   Marek Vasut   USB: Separate out...
559
560
561
562
  
  	/* "allocate" Hub device */
  	hub = usb_hub_allocate();
  	if (hub == NULL)
361ad6afc   Simon Glass   dm: usb: Split hu...
563
  		return -ENOMEM;
23faf2bc9   Marek Vasut   USB: Separate out...
564
565
  	hub->pusb_dev = dev;
  	/* Get the the hub descriptor */
361ad6afc   Simon Glass   dm: usb: Split hu...
566
567
  	ret = usb_get_hub_descriptor(dev, buffer, 4);
  	if (ret < 0) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
568
569
570
  		debug("usb_hub_configure: failed to get hub " \
  		      "descriptor, giving up %lX
  ", dev->status);
361ad6afc   Simon Glass   dm: usb: Split hu...
571
  		return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
572
573
  	}
  	descriptor = (struct usb_hub_descriptor *)buffer;
b41411954   Masahiro Yamada   linux/kernel.h: s...
574
575
  	length = min_t(int, descriptor->bLength,
  		       sizeof(struct usb_hub_descriptor));
23faf2bc9   Marek Vasut   USB: Separate out...
576

361ad6afc   Simon Glass   dm: usb: Split hu...
577
578
  	ret = usb_get_hub_descriptor(dev, buffer, length);
  	if (ret < 0) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
579
580
581
  		debug("usb_hub_configure: failed to get hub " \
  		      "descriptor 2nd giving up %lX
  ", dev->status);
361ad6afc   Simon Glass   dm: usb: Split hu...
582
  		return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
583
  	}
eaf3e613e   Julius Werner   usb: Use well-kno...
584
  	memcpy((unsigned char *)&hub->desc, buffer, length);
23faf2bc9   Marek Vasut   USB: Separate out...
585
  	/* adjust 16bit values */
93ad908c4   Lucas Stach   usb: do explicit ...
586
587
588
  	put_unaligned(le16_to_cpu(get_unaligned(
  			&descriptor->wHubCharacteristics)),
  			&hub->desc.wHubCharacteristics);
23faf2bc9   Marek Vasut   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   Vivek Gautam   usb: common: Weed...
603
604
  	debug("%d ports detected
  ", dev->maxchild);
23faf2bc9   Marek Vasut   USB: Separate out...
605

93ad908c4   Lucas Stach   usb: do explicit ...
606
607
  	hubCharacteristics = get_unaligned(&hub->desc.wHubCharacteristics);
  	switch (hubCharacteristics & HUB_CHAR_LPSM) {
23faf2bc9   Marek Vasut   USB: Separate out...
608
  	case 0x00:
ceb4972a8   Vivek Gautam   usb: common: Weed...
609
610
  		debug("ganged power switching
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
611
612
  		break;
  	case 0x01:
ceb4972a8   Vivek Gautam   usb: common: Weed...
613
614
  		debug("individual port power switching
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
615
616
617
  		break;
  	case 0x02:
  	case 0x03:
ceb4972a8   Vivek Gautam   usb: common: Weed...
618
619
  		debug("unknown reserved power switching mode
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
620
621
  		break;
  	}
93ad908c4   Lucas Stach   usb: do explicit ...
622
  	if (hubCharacteristics & HUB_CHAR_COMPOUND)
ceb4972a8   Vivek Gautam   usb: common: Weed...
623
624
  		debug("part of a compound device
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
625
  	else
ceb4972a8   Vivek Gautam   usb: common: Weed...
626
627
  		debug("standalone hub
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
628

93ad908c4   Lucas Stach   usb: do explicit ...
629
  	switch (hubCharacteristics & HUB_CHAR_OCPM) {
23faf2bc9   Marek Vasut   USB: Separate out...
630
  	case 0x00:
ceb4972a8   Vivek Gautam   usb: common: Weed...
631
632
  		debug("global over-current protection
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
633
634
  		break;
  	case 0x08:
ceb4972a8   Vivek Gautam   usb: common: Weed...
635
636
  		debug("individual port over-current protection
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
637
638
639
  		break;
  	case 0x10:
  	case 0x18:
ceb4972a8   Vivek Gautam   usb: common: Weed...
640
641
  		debug("no over-current protection
  ");
23faf2bc9   Marek Vasut   USB: Separate out...
642
643
  		break;
  	}
ceb4972a8   Vivek Gautam   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   Marek Vasut   USB: Separate out...
650
651
  
  	for (i = 0; i < dev->maxchild; i++)
ceb4972a8   Vivek Gautam   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   Marek Vasut   USB: Separate out...
656
657
  
  	if (sizeof(struct usb_hub_status) > USB_BUFSIZ) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
658
659
660
  		debug("usb_hub_configure: failed to get Status - " \
  		      "too long: %d
  ", descriptor->bLength);
361ad6afc   Simon Glass   dm: usb: Split hu...
661
  		return -EFBIG;
23faf2bc9   Marek Vasut   USB: Separate out...
662
  	}
361ad6afc   Simon Glass   dm: usb: Split hu...
663
664
  	ret = usb_get_hub_status(dev, buffer);
  	if (ret < 0) {
ceb4972a8   Vivek Gautam   usb: common: Weed...
665
666
667
  		debug("usb_hub_configure: failed to get Status %lX
  ",
  		      dev->status);
361ad6afc   Simon Glass   dm: usb: Split hu...
668
  		return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
669
  	}
ceb4972a8   Vivek Gautam   usb: common: Weed...
670
  #ifdef DEBUG
23faf2bc9   Marek Vasut   USB: Separate out...
671
672
  	hubsts = (struct usb_hub_status *)buffer;
  #endif
ceb4972a8   Vivek Gautam   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   Marek Vasut   USB: Separate out...
686
  	usb_hub_power_on(hub);
3615a996a   Dan Murphy   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   Stefan Roese   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   Marek Vasut   USB: Separate out...
701
  	for (i = 0; i < dev->maxchild; i++) {
c998da0d6   Stefan Roese   usb: Change power...
702
  		struct usb_device_scan *usb_scan;
b6d7852cf   Vipin Kumar   usbh/ehci: Increa...
703

c998da0d6   Stefan Roese   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   Marek Vasut   USB: Separate out...
709
  		}
c998da0d6   Stefan Roese   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   Marek Vasut   USB: Separate out...
715

c998da0d6   Stefan Roese   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   Marek Vasut   USB: Separate out...
720

c998da0d6   Stefan Roese   usb: Change power...
721
  	return ret;
23faf2bc9   Marek Vasut   USB: Separate out...
722
  }
361ad6afc   Simon Glass   dm: usb: Split hu...
723
  static int usb_hub_check(struct usb_device *dev, int ifnum)
23faf2bc9   Marek Vasut   USB: Separate out...
724
725
  {
  	struct usb_interface *iface;
361ad6afc   Simon Glass   dm: usb: Split hu...
726
  	struct usb_endpoint_descriptor *ep = NULL;
23faf2bc9   Marek Vasut   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   Simon Glass   dm: usb: Split hu...
731
  		goto err;
23faf2bc9   Marek Vasut   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   Simon Glass   dm: usb: Split hu...
736
  		goto err;
23faf2bc9   Marek Vasut   USB: Separate out...
737
738
  	/* Multiple endpoints? What kind of mutant ninja-hub is this? */
  	if (iface->desc.bNumEndpoints != 1)
361ad6afc   Simon Glass   dm: usb: Split hu...
739
  		goto err;
23faf2bc9   Marek Vasut   USB: Separate out...
740
741
742
  	ep = &iface->ep_desc[0];
  	/* Output endpoint? Curiousier and curiousier.. */
  	if (!(ep->bEndpointAddress & USB_DIR_IN))
361ad6afc   Simon Glass   dm: usb: Split hu...
743
  		goto err;
23faf2bc9   Marek Vasut   USB: Separate out...
744
745
  	/* If it's not an interrupt endpoint, we'd better punt! */
  	if ((ep->bmAttributes & 3) != 3)
361ad6afc   Simon Glass   dm: usb: Split hu...
746
  		goto err;
23faf2bc9   Marek Vasut   USB: Separate out...
747
  	/* We found a hub */
ceb4972a8   Vivek Gautam   usb: common: Weed...
748
749
  	debug("USB hub found
  ");
361ad6afc   Simon Glass   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   Marek Vasut   USB: Separate out...
772
773
774
  	ret = usb_hub_configure(dev);
  	return ret;
  }
054fe48eb   Simon Glass   dm: usb: Add driv...
775
776
777
778
  
  #ifdef CONFIG_DM_USB
  int usb_hub_scan(struct udevice *hub)
  {
bcbe3d157   Simon Glass   dm: Rename dev_ge...
779
  	struct usb_device *udev = dev_get_parent_priv(hub);
054fe48eb   Simon Glass   dm: usb: Add driv...
780
781
782
  
  	return usb_hub_configure(udev);
  }
054fe48eb   Simon Glass   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   Simon Glass   dm: Use dm_scan_f...
805
  	.post_bind	= dm_scan_fdt_dev,
054fe48eb   Simon Glass   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   Simon Glass   dm: usb: Adjust t...
819
  U_BOOT_USB_DEVICE(usb_generic_hub, hub_id_table);
054fe48eb   Simon Glass   dm: usb: Add driv...
820
821
  
  #endif