aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/ehci-hub.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:20:36 -0400
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:20:36 -0400
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/usb/host/ehci-hub.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/usb/host/ehci-hub.c')
-rw-r--r--drivers/usb/host/ehci-hub.c553
1 files changed, 553 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
new file mode 100644
index 000000000000..2373537fabed
--- /dev/null
+++ b/drivers/usb/host/ehci-hub.c
@@ -0,0 +1,553 @@
1/*
2 * Copyright (c) 2001-2002 by David Brownell
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19/* this file is part of ehci-hcd.c */
20
21/*-------------------------------------------------------------------------*/
22
23/*
24 * EHCI Root Hub ... the nonsharable stuff
25 *
26 * Registers don't need cpu_to_le32, that happens transparently
27 */
28
29/*-------------------------------------------------------------------------*/
30
31#ifdef CONFIG_PM
32
33static int ehci_hub_suspend (struct usb_hcd *hcd)
34{
35 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
36 int port;
37
38 if (time_before (jiffies, ehci->next_statechange))
39 msleep(5);
40
41 port = HCS_N_PORTS (ehci->hcs_params);
42 spin_lock_irq (&ehci->lock);
43
44 /* stop schedules, clean any completed work */
45 if (HC_IS_RUNNING(hcd->state)) {
46 ehci_quiesce (ehci);
47 hcd->state = HC_STATE_QUIESCING;
48 }
49 ehci->command = readl (&ehci->regs->command);
50 if (ehci->reclaim)
51 ehci->reclaim_ready = 1;
52 ehci_work(ehci, NULL);
53
54 /* suspend any active/unsuspended ports, maybe allow wakeup */
55 while (port--) {
56 u32 __iomem *reg = &ehci->regs->port_status [port];
57 u32 t1 = readl (reg);
58 u32 t2 = t1;
59
60 if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
61 t2 |= PORT_SUSPEND;
62 if (hcd->remote_wakeup)
63 t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E;
64 else
65 t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
66
67 if (t1 != t2) {
68 ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
69 port + 1, t1, t2);
70 writel (t2, reg);
71 }
72 }
73
74 /* turn off now-idle HC */
75 ehci_halt (ehci);
76 hcd->state = HC_STATE_SUSPENDED;
77
78 ehci->next_statechange = jiffies + msecs_to_jiffies(10);
79 spin_unlock_irq (&ehci->lock);
80 return 0;
81}
82
83
84/* caller has locked the root hub, and should reset/reinit on error */
85static int ehci_hub_resume (struct usb_hcd *hcd)
86{
87 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
88 u32 temp;
89 int i;
90 int intr_enable;
91
92 if (time_before (jiffies, ehci->next_statechange))
93 msleep(5);
94 spin_lock_irq (&ehci->lock);
95
96 /* re-init operational registers in case we lost power */
97 if (readl (&ehci->regs->intr_enable) == 0) {
98 /* at least some APM implementations will try to deliver
99 * IRQs right away, so delay them until we're ready.
100 */
101 intr_enable = 1;
102 writel (0, &ehci->regs->segment);
103 writel (ehci->periodic_dma, &ehci->regs->frame_list);
104 writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
105 } else
106 intr_enable = 0;
107 ehci_dbg(ehci, "resume root hub%s\n",
108 intr_enable ? " after power loss" : "");
109
110 /* restore CMD_RUN, framelist size, and irq threshold */
111 writel (ehci->command, &ehci->regs->command);
112
113 /* take ports out of suspend */
114 i = HCS_N_PORTS (ehci->hcs_params);
115 while (i--) {
116 temp = readl (&ehci->regs->port_status [i]);
117 temp &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
118 if (temp & PORT_SUSPEND) {
119 ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
120 temp |= PORT_RESUME;
121 }
122 writel (temp, &ehci->regs->port_status [i]);
123 }
124 i = HCS_N_PORTS (ehci->hcs_params);
125 mdelay (20);
126 while (i--) {
127 temp = readl (&ehci->regs->port_status [i]);
128 if ((temp & PORT_SUSPEND) == 0)
129 continue;
130 temp &= ~PORT_RESUME;
131 writel (temp, &ehci->regs->port_status [i]);
132 ehci_vdbg (ehci, "resumed port %d\n", i + 1);
133 }
134 (void) readl (&ehci->regs->command);
135
136 /* maybe re-activate the schedule(s) */
137 temp = 0;
138 if (ehci->async->qh_next.qh)
139 temp |= CMD_ASE;
140 if (ehci->periodic_sched)
141 temp |= CMD_PSE;
142 if (temp) {
143 ehci->command |= temp;
144 writel (ehci->command, &ehci->regs->command);
145 }
146
147 ehci->next_statechange = jiffies + msecs_to_jiffies(5);
148 hcd->state = HC_STATE_RUNNING;
149
150 /* Now we can safely re-enable irqs */
151 if (intr_enable)
152 writel (INTR_MASK, &ehci->regs->intr_enable);
153
154 spin_unlock_irq (&ehci->lock);
155 return 0;
156}
157
158#else
159
160#define ehci_hub_suspend NULL
161#define ehci_hub_resume NULL
162
163#endif /* CONFIG_PM */
164
165/*-------------------------------------------------------------------------*/
166
167static int check_reset_complete (
168 struct ehci_hcd *ehci,
169 int index,
170 int port_status
171) {
172 if (!(port_status & PORT_CONNECT)) {
173 ehci->reset_done [index] = 0;
174 return port_status;
175 }
176
177 /* if reset finished and it's still not enabled -- handoff */
178 if (!(port_status & PORT_PE)) {
179
180 /* with integrated TT, there's nobody to hand it to! */
181 if (ehci_is_TDI(ehci)) {
182 ehci_dbg (ehci,
183 "Failed to enable port %d on root hub TT\n",
184 index+1);
185 return port_status;
186 }
187
188 ehci_dbg (ehci, "port %d full speed --> companion\n",
189 index + 1);
190
191 // what happens if HCS_N_CC(params) == 0 ?
192 port_status |= PORT_OWNER;
193 writel (port_status, &ehci->regs->port_status [index]);
194
195 } else
196 ehci_dbg (ehci, "port %d high speed\n", index + 1);
197
198 return port_status;
199}
200
201/*-------------------------------------------------------------------------*/
202
203
204/* build "status change" packet (one or two bytes) from HC registers */
205
206static int
207ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
208{
209 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
210 u32 temp, status = 0;
211 int ports, i, retval = 1;
212 unsigned long flags;
213
214 /* if !USB_SUSPEND, root hub timers won't get shut down ... */
215 if (!HC_IS_RUNNING(hcd->state))
216 return 0;
217
218 /* init status to no-changes */
219 buf [0] = 0;
220 ports = HCS_N_PORTS (ehci->hcs_params);
221 if (ports > 7) {
222 buf [1] = 0;
223 retval++;
224 }
225
226 /* no hub change reports (bit 0) for now (power, ...) */
227
228 /* port N changes (bit N)? */
229 spin_lock_irqsave (&ehci->lock, flags);
230 for (i = 0; i < ports; i++) {
231 temp = readl (&ehci->regs->port_status [i]);
232 if (temp & PORT_OWNER) {
233 /* don't report this in GetPortStatus */
234 if (temp & PORT_CSC) {
235 temp &= ~PORT_CSC;
236 writel (temp, &ehci->regs->port_status [i]);
237 }
238 continue;
239 }
240 if (!(temp & PORT_CONNECT))
241 ehci->reset_done [i] = 0;
242 if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0
243 // PORT_STAT_C_SUSPEND?
244 || ((temp & PORT_RESUME) != 0
245 && time_after (jiffies,
246 ehci->reset_done [i]))) {
247 if (i < 7)
248 buf [0] |= 1 << (i + 1);
249 else
250 buf [1] |= 1 << (i - 7);
251 status = STS_PCD;
252 }
253 }
254 /* FIXME autosuspend idle root hubs */
255 spin_unlock_irqrestore (&ehci->lock, flags);
256 return status ? retval : 0;
257}
258
259/*-------------------------------------------------------------------------*/
260
261static void
262ehci_hub_descriptor (
263 struct ehci_hcd *ehci,
264 struct usb_hub_descriptor *desc
265) {
266 int ports = HCS_N_PORTS (ehci->hcs_params);
267 u16 temp;
268
269 desc->bDescriptorType = 0x29;
270 desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */
271 desc->bHubContrCurrent = 0;
272
273 desc->bNbrPorts = ports;
274 temp = 1 + (ports / 8);
275 desc->bDescLength = 7 + 2 * temp;
276
277 /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
278 memset (&desc->bitmap [0], 0, temp);
279 memset (&desc->bitmap [temp], 0xff, temp);
280
281 temp = 0x0008; /* per-port overcurrent reporting */
282 if (HCS_PPC (ehci->hcs_params))
283 temp |= 0x0001; /* per-port power control */
284#if 0
285// re-enable when we support USB_PORT_FEAT_INDICATOR below.
286 if (HCS_INDICATOR (ehci->hcs_params))
287 temp |= 0x0080; /* per-port indicators (LEDs) */
288#endif
289 desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp);
290}
291
292/*-------------------------------------------------------------------------*/
293
294#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
295
296static int ehci_hub_control (
297 struct usb_hcd *hcd,
298 u16 typeReq,
299 u16 wValue,
300 u16 wIndex,
301 char *buf,
302 u16 wLength
303) {
304 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
305 int ports = HCS_N_PORTS (ehci->hcs_params);
306 u32 temp, status;
307 unsigned long flags;
308 int retval = 0;
309
310 /*
311 * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
312 * HCS_INDICATOR may say we can change LEDs to off/amber/green.
313 * (track current state ourselves) ... blink for diagnostics,
314 * power, "this is the one", etc. EHCI spec supports this.
315 */
316
317 spin_lock_irqsave (&ehci->lock, flags);
318 switch (typeReq) {
319 case ClearHubFeature:
320 switch (wValue) {
321 case C_HUB_LOCAL_POWER:
322 case C_HUB_OVER_CURRENT:
323 /* no hub-wide feature/status flags */
324 break;
325 default:
326 goto error;
327 }
328 break;
329 case ClearPortFeature:
330 if (!wIndex || wIndex > ports)
331 goto error;
332 wIndex--;
333 temp = readl (&ehci->regs->port_status [wIndex]);
334 if (temp & PORT_OWNER)
335 break;
336
337 switch (wValue) {
338 case USB_PORT_FEAT_ENABLE:
339 writel (temp & ~PORT_PE,
340 &ehci->regs->port_status [wIndex]);
341 break;
342 case USB_PORT_FEAT_C_ENABLE:
343 writel (temp | PORT_PEC,
344 &ehci->regs->port_status [wIndex]);
345 break;
346 case USB_PORT_FEAT_SUSPEND:
347 if (temp & PORT_RESET)
348 goto error;
349 if (temp & PORT_SUSPEND) {
350 if ((temp & PORT_PE) == 0)
351 goto error;
352 /* resume signaling for 20 msec */
353 writel ((temp & ~PORT_WAKE_BITS) | PORT_RESUME,
354 &ehci->regs->port_status [wIndex]);
355 ehci->reset_done [wIndex] = jiffies
356 + msecs_to_jiffies (20);
357 }
358 break;
359 case USB_PORT_FEAT_C_SUSPEND:
360 /* we auto-clear this feature */
361 break;
362 case USB_PORT_FEAT_POWER:
363 if (HCS_PPC (ehci->hcs_params))
364 writel (temp & ~PORT_POWER,
365 &ehci->regs->port_status [wIndex]);
366 break;
367 case USB_PORT_FEAT_C_CONNECTION:
368 writel (temp | PORT_CSC,
369 &ehci->regs->port_status [wIndex]);
370 break;
371 case USB_PORT_FEAT_C_OVER_CURRENT:
372 writel (temp | PORT_OCC,
373 &ehci->regs->port_status [wIndex]);
374 break;
375 case USB_PORT_FEAT_C_RESET:
376 /* GetPortStatus clears reset */
377 break;
378 default:
379 goto error;
380 }
381 readl (&ehci->regs->command); /* unblock posted write */
382 break;
383 case GetHubDescriptor:
384 ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
385 buf);
386 break;
387 case GetHubStatus:
388 /* no hub-wide feature/status flags */
389 memset (buf, 0, 4);
390 //cpu_to_le32s ((u32 *) buf);
391 break;
392 case GetPortStatus:
393 if (!wIndex || wIndex > ports)
394 goto error;
395 wIndex--;
396 status = 0;
397 temp = readl (&ehci->regs->port_status [wIndex]);
398
399 // wPortChange bits
400 if (temp & PORT_CSC)
401 status |= 1 << USB_PORT_FEAT_C_CONNECTION;
402 if (temp & PORT_PEC)
403 status |= 1 << USB_PORT_FEAT_C_ENABLE;
404 if (temp & PORT_OCC)
405 status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
406
407 /* whoever resumes must GetPortStatus to complete it!! */
408 if ((temp & PORT_RESUME)
409 && time_after (jiffies,
410 ehci->reset_done [wIndex])) {
411 status |= 1 << USB_PORT_FEAT_C_SUSPEND;
412 ehci->reset_done [wIndex] = 0;
413
414 /* stop resume signaling */
415 temp = readl (&ehci->regs->port_status [wIndex]);
416 writel (temp & ~PORT_RESUME,
417 &ehci->regs->port_status [wIndex]);
418 retval = handshake (
419 &ehci->regs->port_status [wIndex],
420 PORT_RESUME, 0, 2000 /* 2msec */);
421 if (retval != 0) {
422 ehci_err (ehci, "port %d resume error %d\n",
423 wIndex + 1, retval);
424 goto error;
425 }
426 temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
427 }
428
429 /* whoever resets must GetPortStatus to complete it!! */
430 if ((temp & PORT_RESET)
431 && time_after (jiffies,
432 ehci->reset_done [wIndex])) {
433 status |= 1 << USB_PORT_FEAT_C_RESET;
434 ehci->reset_done [wIndex] = 0;
435
436 /* force reset to complete */
437 writel (temp & ~PORT_RESET,
438 &ehci->regs->port_status [wIndex]);
439 retval = handshake (
440 &ehci->regs->port_status [wIndex],
441 PORT_RESET, 0, 500);
442 if (retval != 0) {
443 ehci_err (ehci, "port %d reset error %d\n",
444 wIndex + 1, retval);
445 goto error;
446 }
447
448 /* see what we found out */
449 temp = check_reset_complete (ehci, wIndex,
450 readl (&ehci->regs->port_status [wIndex]));
451 }
452
453 // don't show wPortStatus if it's owned by a companion hc
454 if (!(temp & PORT_OWNER)) {
455 if (temp & PORT_CONNECT) {
456 status |= 1 << USB_PORT_FEAT_CONNECTION;
457 // status may be from integrated TT
458 status |= ehci_port_speed(ehci, temp);
459 }
460 if (temp & PORT_PE)
461 status |= 1 << USB_PORT_FEAT_ENABLE;
462 if (temp & (PORT_SUSPEND|PORT_RESUME))
463 status |= 1 << USB_PORT_FEAT_SUSPEND;
464 if (temp & PORT_OC)
465 status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
466 if (temp & PORT_RESET)
467 status |= 1 << USB_PORT_FEAT_RESET;
468 if (temp & PORT_POWER)
469 status |= 1 << USB_PORT_FEAT_POWER;
470 }
471
472#ifndef EHCI_VERBOSE_DEBUG
473 if (status & ~0xffff) /* only if wPortChange is interesting */
474#endif
475 dbg_port (ehci, "GetStatus", wIndex + 1, temp);
476 // we "know" this alignment is good, caller used kmalloc()...
477 *((__le32 *) buf) = cpu_to_le32 (status);
478 break;
479 case SetHubFeature:
480 switch (wValue) {
481 case C_HUB_LOCAL_POWER:
482 case C_HUB_OVER_CURRENT:
483 /* no hub-wide feature/status flags */
484 break;
485 default:
486 goto error;
487 }
488 break;
489 case SetPortFeature:
490 if (!wIndex || wIndex > ports)
491 goto error;
492 wIndex--;
493 temp = readl (&ehci->regs->port_status [wIndex]);
494 if (temp & PORT_OWNER)
495 break;
496
497 switch (wValue) {
498 case USB_PORT_FEAT_SUSPEND:
499 if ((temp & PORT_PE) == 0
500 || (temp & PORT_RESET) != 0)
501 goto error;
502 if (hcd->remote_wakeup)
503 temp |= PORT_WAKE_BITS;
504 writel (temp | PORT_SUSPEND,
505 &ehci->regs->port_status [wIndex]);
506 break;
507 case USB_PORT_FEAT_POWER:
508 if (HCS_PPC (ehci->hcs_params))
509 writel (temp | PORT_POWER,
510 &ehci->regs->port_status [wIndex]);
511 break;
512 case USB_PORT_FEAT_RESET:
513 if (temp & PORT_RESUME)
514 goto error;
515 /* line status bits may report this as low speed,
516 * which can be fine if this root hub has a
517 * transaction translator built in.
518 */
519 if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
520 && !ehci_is_TDI(ehci)
521 && PORT_USB11 (temp)) {
522 ehci_dbg (ehci,
523 "port %d low speed --> companion\n",
524 wIndex + 1);
525 temp |= PORT_OWNER;
526 } else {
527 ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
528 temp |= PORT_RESET;
529 temp &= ~PORT_PE;
530
531 /*
532 * caller must wait, then call GetPortStatus
533 * usb 2.0 spec says 50 ms resets on root
534 */
535 ehci->reset_done [wIndex] = jiffies
536 + msecs_to_jiffies (50);
537 }
538 writel (temp, &ehci->regs->port_status [wIndex]);
539 break;
540 default:
541 goto error;
542 }
543 readl (&ehci->regs->command); /* unblock posted writes */
544 break;
545
546 default:
547error:
548 /* "stall" on error */
549 retval = -EPIPE;
550 }
551 spin_unlock_irqrestore (&ehci->lock, flags);
552 return retval;
553}