blob: 18d3f22703160af0c8ac55fd8f425a5d20e8c369 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
David Brownelld49d4312005-05-07 13:21:50 -07002 * Copyright (C) 2001-2004 by David Brownell
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 *
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];
David Brownell10f65242005-08-31 10:55:38 -070057 u32 t1 = readl (reg) & ~PORT_RWC_BITS;
Linus Torvalds1da177e2005-04-16 15:20:36 -070058 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 */
David Brownell4756ae52005-05-09 17:23:51 -070075 del_timer_sync (&ehci->watchdog);
Linus Torvalds1da177e2005-04-16 15:20:36 -070076 ehci_halt (ehci);
77 hcd->state = HC_STATE_SUSPENDED;
78
79 ehci->next_statechange = jiffies + msecs_to_jiffies(10);
80 spin_unlock_irq (&ehci->lock);
81 return 0;
82}
83
84
85/* caller has locked the root hub, and should reset/reinit on error */
86static int ehci_hub_resume (struct usb_hcd *hcd)
87{
88 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
89 u32 temp;
90 int i;
91 int intr_enable;
92
93 if (time_before (jiffies, ehci->next_statechange))
94 msleep(5);
95 spin_lock_irq (&ehci->lock);
96
97 /* re-init operational registers in case we lost power */
98 if (readl (&ehci->regs->intr_enable) == 0) {
99 /* at least some APM implementations will try to deliver
100 * IRQs right away, so delay them until we're ready.
101 */
102 intr_enable = 1;
103 writel (0, &ehci->regs->segment);
104 writel (ehci->periodic_dma, &ehci->regs->frame_list);
105 writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
106 } else
107 intr_enable = 0;
108 ehci_dbg(ehci, "resume root hub%s\n",
109 intr_enable ? " after power loss" : "");
110
111 /* restore CMD_RUN, framelist size, and irq threshold */
112 writel (ehci->command, &ehci->regs->command);
113
114 /* take ports out of suspend */
115 i = HCS_N_PORTS (ehci->hcs_params);
116 while (i--) {
117 temp = readl (&ehci->regs->port_status [i]);
David Brownell10f65242005-08-31 10:55:38 -0700118 temp &= ~(PORT_RWC_BITS
119 | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120 if (temp & PORT_SUSPEND) {
121 ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
122 temp |= PORT_RESUME;
123 }
124 writel (temp, &ehci->regs->port_status [i]);
125 }
126 i = HCS_N_PORTS (ehci->hcs_params);
127 mdelay (20);
128 while (i--) {
129 temp = readl (&ehci->regs->port_status [i]);
130 if ((temp & PORT_SUSPEND) == 0)
131 continue;
David Brownell10f65242005-08-31 10:55:38 -0700132 temp &= ~(PORT_RWC_BITS | PORT_RESUME);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700133 writel (temp, &ehci->regs->port_status [i]);
134 ehci_vdbg (ehci, "resumed port %d\n", i + 1);
135 }
136 (void) readl (&ehci->regs->command);
137
138 /* maybe re-activate the schedule(s) */
139 temp = 0;
140 if (ehci->async->qh_next.qh)
141 temp |= CMD_ASE;
142 if (ehci->periodic_sched)
143 temp |= CMD_PSE;
144 if (temp) {
145 ehci->command |= temp;
146 writel (ehci->command, &ehci->regs->command);
147 }
148
149 ehci->next_statechange = jiffies + msecs_to_jiffies(5);
150 hcd->state = HC_STATE_RUNNING;
151
152 /* Now we can safely re-enable irqs */
153 if (intr_enable)
154 writel (INTR_MASK, &ehci->regs->intr_enable);
155
156 spin_unlock_irq (&ehci->lock);
157 return 0;
158}
159
160#else
161
162#define ehci_hub_suspend NULL
163#define ehci_hub_resume NULL
164
165#endif /* CONFIG_PM */
166
167/*-------------------------------------------------------------------------*/
168
169static int check_reset_complete (
170 struct ehci_hcd *ehci,
171 int index,
172 int port_status
173) {
174 if (!(port_status & PORT_CONNECT)) {
175 ehci->reset_done [index] = 0;
176 return port_status;
177 }
178
179 /* if reset finished and it's still not enabled -- handoff */
180 if (!(port_status & PORT_PE)) {
181
182 /* with integrated TT, there's nobody to hand it to! */
183 if (ehci_is_TDI(ehci)) {
184 ehci_dbg (ehci,
185 "Failed to enable port %d on root hub TT\n",
186 index+1);
187 return port_status;
188 }
189
190 ehci_dbg (ehci, "port %d full speed --> companion\n",
191 index + 1);
192
193 // what happens if HCS_N_CC(params) == 0 ?
194 port_status |= PORT_OWNER;
David Brownell10f65242005-08-31 10:55:38 -0700195 port_status &= ~PORT_RWC_BITS;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700196 writel (port_status, &ehci->regs->port_status [index]);
197
198 } else
199 ehci_dbg (ehci, "port %d high speed\n", index + 1);
200
201 return port_status;
202}
203
204/*-------------------------------------------------------------------------*/
205
206
207/* build "status change" packet (one or two bytes) from HC registers */
208
209static int
210ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
211{
212 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
213 u32 temp, status = 0;
214 int ports, i, retval = 1;
215 unsigned long flags;
216
217 /* if !USB_SUSPEND, root hub timers won't get shut down ... */
218 if (!HC_IS_RUNNING(hcd->state))
219 return 0;
220
221 /* init status to no-changes */
222 buf [0] = 0;
223 ports = HCS_N_PORTS (ehci->hcs_params);
224 if (ports > 7) {
225 buf [1] = 0;
226 retval++;
227 }
228
229 /* no hub change reports (bit 0) for now (power, ...) */
230
231 /* port N changes (bit N)? */
232 spin_lock_irqsave (&ehci->lock, flags);
233 for (i = 0; i < ports; i++) {
234 temp = readl (&ehci->regs->port_status [i]);
235 if (temp & PORT_OWNER) {
236 /* don't report this in GetPortStatus */
237 if (temp & PORT_CSC) {
David Brownell10f65242005-08-31 10:55:38 -0700238 temp &= ~PORT_RWC_BITS;
239 temp |= PORT_CSC;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240 writel (temp, &ehci->regs->port_status [i]);
241 }
242 continue;
243 }
244 if (!(temp & PORT_CONNECT))
245 ehci->reset_done [i] = 0;
246 if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0
247 // PORT_STAT_C_SUSPEND?
248 || ((temp & PORT_RESUME) != 0
249 && time_after (jiffies,
250 ehci->reset_done [i]))) {
251 if (i < 7)
252 buf [0] |= 1 << (i + 1);
253 else
254 buf [1] |= 1 << (i - 7);
255 status = STS_PCD;
256 }
257 }
258 /* FIXME autosuspend idle root hubs */
259 spin_unlock_irqrestore (&ehci->lock, flags);
260 return status ? retval : 0;
261}
262
263/*-------------------------------------------------------------------------*/
264
265static void
266ehci_hub_descriptor (
267 struct ehci_hcd *ehci,
268 struct usb_hub_descriptor *desc
269) {
270 int ports = HCS_N_PORTS (ehci->hcs_params);
271 u16 temp;
272
273 desc->bDescriptorType = 0x29;
274 desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */
275 desc->bHubContrCurrent = 0;
276
277 desc->bNbrPorts = ports;
278 temp = 1 + (ports / 8);
279 desc->bDescLength = 7 + 2 * temp;
280
281 /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
282 memset (&desc->bitmap [0], 0, temp);
283 memset (&desc->bitmap [temp], 0xff, temp);
284
285 temp = 0x0008; /* per-port overcurrent reporting */
286 if (HCS_PPC (ehci->hcs_params))
287 temp |= 0x0001; /* per-port power control */
David Brownell56c1e262005-04-09 09:00:29 -0700288 else
289 temp |= 0x0002; /* no power switching */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700290#if 0
291// re-enable when we support USB_PORT_FEAT_INDICATOR below.
292 if (HCS_INDICATOR (ehci->hcs_params))
293 temp |= 0x0080; /* per-port indicators (LEDs) */
294#endif
295 desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp);
296}
297
298/*-------------------------------------------------------------------------*/
299
300#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
301
302static int ehci_hub_control (
303 struct usb_hcd *hcd,
304 u16 typeReq,
305 u16 wValue,
306 u16 wIndex,
307 char *buf,
308 u16 wLength
309) {
310 struct ehci_hcd *ehci = hcd_to_ehci (hcd);
311 int ports = HCS_N_PORTS (ehci->hcs_params);
312 u32 temp, status;
313 unsigned long flags;
314 int retval = 0;
315
316 /*
317 * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
318 * HCS_INDICATOR may say we can change LEDs to off/amber/green.
319 * (track current state ourselves) ... blink for diagnostics,
320 * power, "this is the one", etc. EHCI spec supports this.
321 */
322
323 spin_lock_irqsave (&ehci->lock, flags);
324 switch (typeReq) {
325 case ClearHubFeature:
326 switch (wValue) {
327 case C_HUB_LOCAL_POWER:
328 case C_HUB_OVER_CURRENT:
329 /* no hub-wide feature/status flags */
330 break;
331 default:
332 goto error;
333 }
334 break;
335 case ClearPortFeature:
336 if (!wIndex || wIndex > ports)
337 goto error;
338 wIndex--;
339 temp = readl (&ehci->regs->port_status [wIndex]);
340 if (temp & PORT_OWNER)
341 break;
342
343 switch (wValue) {
344 case USB_PORT_FEAT_ENABLE:
345 writel (temp & ~PORT_PE,
346 &ehci->regs->port_status [wIndex]);
347 break;
348 case USB_PORT_FEAT_C_ENABLE:
David Brownell10f65242005-08-31 10:55:38 -0700349 writel((temp & ~PORT_RWC_BITS) | PORT_PEC,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700350 &ehci->regs->port_status [wIndex]);
351 break;
352 case USB_PORT_FEAT_SUSPEND:
353 if (temp & PORT_RESET)
354 goto error;
355 if (temp & PORT_SUSPEND) {
356 if ((temp & PORT_PE) == 0)
357 goto error;
358 /* resume signaling for 20 msec */
David Brownell10f65242005-08-31 10:55:38 -0700359 temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
360 writel (temp | PORT_RESUME,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700361 &ehci->regs->port_status [wIndex]);
362 ehci->reset_done [wIndex] = jiffies
363 + msecs_to_jiffies (20);
364 }
365 break;
366 case USB_PORT_FEAT_C_SUSPEND:
367 /* we auto-clear this feature */
368 break;
369 case USB_PORT_FEAT_POWER:
370 if (HCS_PPC (ehci->hcs_params))
David Brownell10f65242005-08-31 10:55:38 -0700371 writel (temp & ~(PORT_RWC_BITS | PORT_POWER),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700372 &ehci->regs->port_status [wIndex]);
373 break;
374 case USB_PORT_FEAT_C_CONNECTION:
David Brownell10f65242005-08-31 10:55:38 -0700375 writel((temp & ~PORT_RWC_BITS) | PORT_CSC,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700376 &ehci->regs->port_status [wIndex]);
377 break;
378 case USB_PORT_FEAT_C_OVER_CURRENT:
David Brownell10f65242005-08-31 10:55:38 -0700379 writel((temp & ~PORT_RWC_BITS) | PORT_OCC,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700380 &ehci->regs->port_status [wIndex]);
381 break;
382 case USB_PORT_FEAT_C_RESET:
383 /* GetPortStatus clears reset */
384 break;
385 default:
386 goto error;
387 }
388 readl (&ehci->regs->command); /* unblock posted write */
389 break;
390 case GetHubDescriptor:
391 ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
392 buf);
393 break;
394 case GetHubStatus:
395 /* no hub-wide feature/status flags */
396 memset (buf, 0, 4);
397 //cpu_to_le32s ((u32 *) buf);
398 break;
399 case GetPortStatus:
400 if (!wIndex || wIndex > ports)
401 goto error;
402 wIndex--;
403 status = 0;
404 temp = readl (&ehci->regs->port_status [wIndex]);
405
406 // wPortChange bits
407 if (temp & PORT_CSC)
408 status |= 1 << USB_PORT_FEAT_C_CONNECTION;
409 if (temp & PORT_PEC)
410 status |= 1 << USB_PORT_FEAT_C_ENABLE;
411 if (temp & PORT_OCC)
412 status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
413
414 /* whoever resumes must GetPortStatus to complete it!! */
415 if ((temp & PORT_RESUME)
416 && time_after (jiffies,
417 ehci->reset_done [wIndex])) {
418 status |= 1 << USB_PORT_FEAT_C_SUSPEND;
419 ehci->reset_done [wIndex] = 0;
420
421 /* stop resume signaling */
422 temp = readl (&ehci->regs->port_status [wIndex]);
David Brownell10f65242005-08-31 10:55:38 -0700423 writel (temp & ~(PORT_RWC_BITS | PORT_RESUME),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700424 &ehci->regs->port_status [wIndex]);
425 retval = handshake (
426 &ehci->regs->port_status [wIndex],
427 PORT_RESUME, 0, 2000 /* 2msec */);
428 if (retval != 0) {
429 ehci_err (ehci, "port %d resume error %d\n",
430 wIndex + 1, retval);
431 goto error;
432 }
433 temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
434 }
435
436 /* whoever resets must GetPortStatus to complete it!! */
437 if ((temp & PORT_RESET)
438 && time_after (jiffies,
439 ehci->reset_done [wIndex])) {
440 status |= 1 << USB_PORT_FEAT_C_RESET;
441 ehci->reset_done [wIndex] = 0;
442
443 /* force reset to complete */
David Brownell10f65242005-08-31 10:55:38 -0700444 writel (temp & ~(PORT_RWC_BITS | PORT_RESET),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700445 &ehci->regs->port_status [wIndex]);
David Brownellc22fa3a2005-06-13 07:15:28 -0700446 /* REVISIT: some hardware needs 550+ usec to clear
447 * this bit; seems too long to spin routinely...
448 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700449 retval = handshake (
450 &ehci->regs->port_status [wIndex],
David Brownellc22fa3a2005-06-13 07:15:28 -0700451 PORT_RESET, 0, 750);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700452 if (retval != 0) {
453 ehci_err (ehci, "port %d reset error %d\n",
454 wIndex + 1, retval);
455 goto error;
456 }
457
458 /* see what we found out */
459 temp = check_reset_complete (ehci, wIndex,
460 readl (&ehci->regs->port_status [wIndex]));
461 }
462
463 // don't show wPortStatus if it's owned by a companion hc
464 if (!(temp & PORT_OWNER)) {
465 if (temp & PORT_CONNECT) {
466 status |= 1 << USB_PORT_FEAT_CONNECTION;
467 // status may be from integrated TT
468 status |= ehci_port_speed(ehci, temp);
469 }
470 if (temp & PORT_PE)
471 status |= 1 << USB_PORT_FEAT_ENABLE;
472 if (temp & (PORT_SUSPEND|PORT_RESUME))
473 status |= 1 << USB_PORT_FEAT_SUSPEND;
474 if (temp & PORT_OC)
475 status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
476 if (temp & PORT_RESET)
477 status |= 1 << USB_PORT_FEAT_RESET;
478 if (temp & PORT_POWER)
479 status |= 1 << USB_PORT_FEAT_POWER;
480 }
481
482#ifndef EHCI_VERBOSE_DEBUG
483 if (status & ~0xffff) /* only if wPortChange is interesting */
484#endif
485 dbg_port (ehci, "GetStatus", wIndex + 1, temp);
486 // we "know" this alignment is good, caller used kmalloc()...
487 *((__le32 *) buf) = cpu_to_le32 (status);
488 break;
489 case SetHubFeature:
490 switch (wValue) {
491 case C_HUB_LOCAL_POWER:
492 case C_HUB_OVER_CURRENT:
493 /* no hub-wide feature/status flags */
494 break;
495 default:
496 goto error;
497 }
498 break;
499 case SetPortFeature:
500 if (!wIndex || wIndex > ports)
501 goto error;
502 wIndex--;
503 temp = readl (&ehci->regs->port_status [wIndex]);
504 if (temp & PORT_OWNER)
505 break;
506
David Brownell10f65242005-08-31 10:55:38 -0700507 temp &= ~PORT_RWC_BITS;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700508 switch (wValue) {
509 case USB_PORT_FEAT_SUSPEND:
510 if ((temp & PORT_PE) == 0
511 || (temp & PORT_RESET) != 0)
512 goto error;
513 if (hcd->remote_wakeup)
514 temp |= PORT_WAKE_BITS;
515 writel (temp | PORT_SUSPEND,
516 &ehci->regs->port_status [wIndex]);
517 break;
518 case USB_PORT_FEAT_POWER:
519 if (HCS_PPC (ehci->hcs_params))
520 writel (temp | PORT_POWER,
521 &ehci->regs->port_status [wIndex]);
522 break;
523 case USB_PORT_FEAT_RESET:
524 if (temp & PORT_RESUME)
525 goto error;
526 /* line status bits may report this as low speed,
527 * which can be fine if this root hub has a
528 * transaction translator built in.
529 */
530 if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
531 && !ehci_is_TDI(ehci)
532 && PORT_USB11 (temp)) {
533 ehci_dbg (ehci,
534 "port %d low speed --> companion\n",
535 wIndex + 1);
536 temp |= PORT_OWNER;
537 } else {
538 ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
539 temp |= PORT_RESET;
540 temp &= ~PORT_PE;
541
542 /*
543 * caller must wait, then call GetPortStatus
544 * usb 2.0 spec says 50 ms resets on root
545 */
546 ehci->reset_done [wIndex] = jiffies
547 + msecs_to_jiffies (50);
548 }
549 writel (temp, &ehci->regs->port_status [wIndex]);
550 break;
551 default:
552 goto error;
553 }
554 readl (&ehci->regs->command); /* unblock posted writes */
555 break;
556
557 default:
558error:
559 /* "stall" on error */
560 retval = -EPIPE;
561 }
562 spin_unlock_irqrestore (&ehci->lock, flags);
563 return retval;
564}