diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-05-04 11:52:40 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-07-12 19:29:47 -0400 |
commit | 383975d765523a56dc43a6cd6d52e9b376800cf2 (patch) | |
tree | d6ecbfe620d7d5fba372211d7af185e7c44e5097 /drivers | |
parent | 0458d5b4c9cc4ca0f62625d0144ddc4b4bc97a3c (diff) |
USB: EHCI, OHCI: handover changes
This patch (as887) changes the way ehci-hcd and ohci-hcd handle a loss
of VBUS power during suspend. In order for the USB-persist facility
to work correctly, it is necessary for low- and full-speed devices
attached to a high-speed port to be handed back to the companion
controller during resume processing.
This entails three changes: adding code to ehci-hcd to perform the
handover, removing code from ohci-hcd to turn off ports during
root-hub reinit, and adding code to ohci-hcd to turn on ports during
PCI controller resume. (Other bus glue resume methods for platforms
supporting high-speed controllers would need a similar change, if any
existed.)
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hub.c | 99 | ||||
-rw-r--r-- | drivers/usb/host/ehci-pci.c | 7 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 3 | ||||
-rw-r--r-- | drivers/usb/host/ohci-hcd.c | 21 | ||||
-rw-r--r-- | drivers/usb/host/ohci-pci.c | 41 |
6 files changed, 145 insertions, 28 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 99ab31e9778b..b5a7aa90209a 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c | |||
@@ -399,6 +399,8 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on) | |||
399 | is_on ? SetPortFeature : ClearPortFeature, | 399 | is_on ? SetPortFeature : ClearPortFeature, |
400 | USB_PORT_FEAT_POWER, | 400 | USB_PORT_FEAT_POWER, |
401 | port--, NULL, 0); | 401 | port--, NULL, 0); |
402 | /* Flush those writes */ | ||
403 | ehci_readl(ehci, &ehci->regs->command); | ||
402 | msleep(20); | 404 | msleep(20); |
403 | } | 405 | } |
404 | 406 | ||
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index f4d301bc83b9..3e80de7c7f5b 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c | |||
@@ -28,6 +28,87 @@ | |||
28 | 28 | ||
29 | /*-------------------------------------------------------------------------*/ | 29 | /*-------------------------------------------------------------------------*/ |
30 | 30 | ||
31 | #ifdef CONFIG_USB_PERSIST | ||
32 | |||
33 | static int ehci_hub_control( | ||
34 | struct usb_hcd *hcd, | ||
35 | u16 typeReq, | ||
36 | u16 wValue, | ||
37 | u16 wIndex, | ||
38 | char *buf, | ||
39 | u16 wLength | ||
40 | ); | ||
41 | |||
42 | /* After a power loss, ports that were owned by the companion must be | ||
43 | * reset so that the companion can still own them. | ||
44 | */ | ||
45 | static void ehci_handover_companion_ports(struct ehci_hcd *ehci) | ||
46 | { | ||
47 | u32 __iomem *reg; | ||
48 | u32 status; | ||
49 | int port; | ||
50 | __le32 buf; | ||
51 | struct usb_hcd *hcd = ehci_to_hcd(ehci); | ||
52 | |||
53 | if (!ehci->owned_ports) | ||
54 | return; | ||
55 | |||
56 | /* Give the connections some time to appear */ | ||
57 | msleep(20); | ||
58 | |||
59 | port = HCS_N_PORTS(ehci->hcs_params); | ||
60 | while (port--) { | ||
61 | if (test_bit(port, &ehci->owned_ports)) { | ||
62 | reg = &ehci->regs->port_status[port]; | ||
63 | status = ehci_readl(ehci, reg); | ||
64 | |||
65 | /* Port already owned by companion? */ | ||
66 | if (status & PORT_OWNER) | ||
67 | clear_bit(port, &ehci->owned_ports); | ||
68 | else | ||
69 | ehci_hub_control(hcd, SetPortFeature, | ||
70 | USB_PORT_FEAT_RESET, port + 1, | ||
71 | NULL, 0); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | if (!ehci->owned_ports) | ||
76 | return; | ||
77 | msleep(90); /* Wait for resets to complete */ | ||
78 | |||
79 | port = HCS_N_PORTS(ehci->hcs_params); | ||
80 | while (port--) { | ||
81 | if (test_bit(port, &ehci->owned_ports)) { | ||
82 | ehci_hub_control(hcd, GetPortStatus, | ||
83 | 0, port + 1, | ||
84 | (char *) &buf, sizeof(buf)); | ||
85 | |||
86 | /* The companion should now own the port, | ||
87 | * but if something went wrong the port must not | ||
88 | * remain enabled. | ||
89 | */ | ||
90 | reg = &ehci->regs->port_status[port]; | ||
91 | status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; | ||
92 | if (status & PORT_OWNER) | ||
93 | ehci_writel(ehci, status | PORT_CSC, reg); | ||
94 | else { | ||
95 | ehci_dbg(ehci, "failed handover port %d: %x\n", | ||
96 | port + 1, status); | ||
97 | ehci_writel(ehci, status & ~PORT_PE, reg); | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | ehci->owned_ports = 0; | ||
103 | } | ||
104 | |||
105 | #else /* CONFIG_USB_PERSIST */ | ||
106 | |||
107 | static inline void ehci_handover_companion_ports(struct ehci_hcd *ehci) | ||
108 | { } | ||
109 | |||
110 | #endif | ||
111 | |||
31 | #ifdef CONFIG_PM | 112 | #ifdef CONFIG_PM |
32 | 113 | ||
33 | static int ehci_bus_suspend (struct usb_hcd *hcd) | 114 | static int ehci_bus_suspend (struct usb_hcd *hcd) |
@@ -60,14 +141,16 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) | |||
60 | * then manually resume them in the bus_resume() routine. | 141 | * then manually resume them in the bus_resume() routine. |
61 | */ | 142 | */ |
62 | ehci->bus_suspended = 0; | 143 | ehci->bus_suspended = 0; |
144 | ehci->owned_ports = 0; | ||
63 | while (port--) { | 145 | while (port--) { |
64 | u32 __iomem *reg = &ehci->regs->port_status [port]; | 146 | u32 __iomem *reg = &ehci->regs->port_status [port]; |
65 | u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; | 147 | u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; |
66 | u32 t2 = t1; | 148 | u32 t2 = t1; |
67 | 149 | ||
68 | /* keep track of which ports we suspend */ | 150 | /* keep track of which ports we suspend */ |
69 | if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && | 151 | if (t1 & PORT_OWNER) |
70 | !(t1 & PORT_SUSPEND)) { | 152 | set_bit(port, &ehci->owned_ports); |
153 | else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) { | ||
71 | t2 |= PORT_SUSPEND; | 154 | t2 |= PORT_SUSPEND; |
72 | set_bit(port, &ehci->bus_suspended); | 155 | set_bit(port, &ehci->bus_suspended); |
73 | } | 156 | } |
@@ -108,6 +191,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) | |||
108 | { | 191 | { |
109 | struct ehci_hcd *ehci = hcd_to_ehci (hcd); | 192 | struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
110 | u32 temp; | 193 | u32 temp; |
194 | u32 power_okay; | ||
111 | int i; | 195 | int i; |
112 | 196 | ||
113 | if (time_before (jiffies, ehci->next_statechange)) | 197 | if (time_before (jiffies, ehci->next_statechange)) |
@@ -120,8 +204,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) | |||
120 | * the last user of the controller, not reset/pm hardware keeping | 204 | * the last user of the controller, not reset/pm hardware keeping |
121 | * state we gave to it. | 205 | * state we gave to it. |
122 | */ | 206 | */ |
123 | temp = ehci_readl(ehci, &ehci->regs->intr_enable); | 207 | power_okay = ehci_readl(ehci, &ehci->regs->intr_enable); |
124 | ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss"); | 208 | ehci_dbg(ehci, "resume root hub%s\n", |
209 | power_okay ? "" : " after power loss"); | ||
125 | 210 | ||
126 | /* at least some APM implementations will try to deliver | 211 | /* at least some APM implementations will try to deliver |
127 | * IRQs right away, so delay them until we're ready. | 212 | * IRQs right away, so delay them until we're ready. |
@@ -184,6 +269,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) | |||
184 | ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); | 269 | ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); |
185 | 270 | ||
186 | spin_unlock_irq (&ehci->lock); | 271 | spin_unlock_irq (&ehci->lock); |
272 | |||
273 | if (!power_okay) | ||
274 | ehci_handover_companion_ports(ehci); | ||
187 | return 0; | 275 | return 0; |
188 | } | 276 | } |
189 | 277 | ||
@@ -448,7 +536,8 @@ static int ehci_hub_control ( | |||
448 | ) { | 536 | ) { |
449 | struct ehci_hcd *ehci = hcd_to_ehci (hcd); | 537 | struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
450 | int ports = HCS_N_PORTS (ehci->hcs_params); | 538 | int ports = HCS_N_PORTS (ehci->hcs_params); |
451 | u32 __iomem *status_reg = &ehci->regs->port_status[wIndex - 1]; | 539 | u32 __iomem *status_reg = &ehci->regs->port_status[ |
540 | (wIndex & 0xff) - 1]; | ||
452 | u32 temp, status; | 541 | u32 temp, status; |
453 | unsigned long flags; | 542 | unsigned long flags; |
454 | int retval = 0; | 543 | int retval = 0; |
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 966965f72338..a7816e392a85 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c | |||
@@ -312,13 +312,14 @@ static int ehci_pci_resume(struct usb_hcd *hcd) | |||
312 | ehci_work(ehci); | 312 | ehci_work(ehci); |
313 | spin_unlock_irq(&ehci->lock); | 313 | spin_unlock_irq(&ehci->lock); |
314 | 314 | ||
315 | /* here we "know" root ports should always stay powered */ | ||
316 | ehci_port_power(ehci, 1); | ||
317 | |||
318 | ehci_writel(ehci, ehci->command, &ehci->regs->command); | 315 | ehci_writel(ehci, ehci->command, &ehci->regs->command); |
319 | ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); | 316 | ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); |
320 | ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ | 317 | ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ |
321 | 318 | ||
319 | /* here we "know" root ports should always stay powered */ | ||
320 | ehci_port_power(ehci, 1); | ||
321 | ehci_handover_companion_ports(ehci); | ||
322 | |||
322 | hcd->state = HC_STATE_SUSPENDED; | 323 | hcd->state = HC_STATE_SUSPENDED; |
323 | return 0; | 324 | return 0; |
324 | } | 325 | } |
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 6ef9d775775b..4d617108f552 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h | |||
@@ -96,11 +96,14 @@ struct ehci_hcd { /* one per controller */ | |||
96 | 96 | ||
97 | /* per root hub port */ | 97 | /* per root hub port */ |
98 | unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; | 98 | unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; |
99 | |||
99 | /* bit vectors (one bit per port) */ | 100 | /* bit vectors (one bit per port) */ |
100 | unsigned long bus_suspended; /* which ports were | 101 | unsigned long bus_suspended; /* which ports were |
101 | already suspended at the start of a bus suspend */ | 102 | already suspended at the start of a bus suspend */ |
102 | unsigned long companion_ports; /* which ports are | 103 | unsigned long companion_ports; /* which ports are |
103 | dedicated to the companion controller */ | 104 | dedicated to the companion controller */ |
105 | unsigned long owned_ports; /* which ports are | ||
106 | owned by the companion during a bus suspend */ | ||
104 | 107 | ||
105 | /* per-HC memory pools (could be per-bus, but ...) */ | 108 | /* per-HC memory pools (could be per-bus, but ...) */ |
106 | struct dma_pool *qh_pool; /* qh per active urb */ | 109 | struct dma_pool *qh_pool; /* qh per active urb */ |
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index a66637e725f3..ce05e5f7bed6 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c | |||
@@ -510,15 +510,7 @@ static int ohci_run (struct ohci_hcd *ohci) | |||
510 | // flush the writes | 510 | // flush the writes |
511 | (void) ohci_readl (ohci, &ohci->regs->control); | 511 | (void) ohci_readl (ohci, &ohci->regs->control); |
512 | msleep(temp); | 512 | msleep(temp); |
513 | temp = roothub_a (ohci); | 513 | |
514 | if (!(temp & RH_A_NPS)) { | ||
515 | /* power down each port */ | ||
516 | for (temp = 0; temp < ohci->num_ports; temp++) | ||
517 | ohci_writel (ohci, RH_PS_LSDA, | ||
518 | &ohci->regs->roothub.portstatus [temp]); | ||
519 | } | ||
520 | // flush those writes | ||
521 | (void) ohci_readl (ohci, &ohci->regs->control); | ||
522 | memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); | 514 | memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); |
523 | 515 | ||
524 | /* 2msec timelimit here means no irqs/preempt */ | 516 | /* 2msec timelimit here means no irqs/preempt */ |
@@ -826,17 +818,8 @@ static int ohci_restart (struct ohci_hcd *ohci) | |||
826 | if ((temp = ohci_run (ohci)) < 0) { | 818 | if ((temp = ohci_run (ohci)) < 0) { |
827 | ohci_err (ohci, "can't restart, %d\n", temp); | 819 | ohci_err (ohci, "can't restart, %d\n", temp); |
828 | return temp; | 820 | return temp; |
829 | } else { | ||
830 | /* here we "know" root ports should always stay powered, | ||
831 | * and that if we try to turn them back on the root hub | ||
832 | * will respond to CSC processing. | ||
833 | */ | ||
834 | i = ohci->num_ports; | ||
835 | while (i--) | ||
836 | ohci_writel (ohci, RH_PS_PSS, | ||
837 | &ohci->regs->roothub.portstatus [i]); | ||
838 | ohci_dbg (ohci, "restart complete\n"); | ||
839 | } | 821 | } |
822 | ohci_dbg(ohci, "restart complete\n"); | ||
840 | return 0; | 823 | return 0; |
841 | } | 824 | } |
842 | #endif | 825 | #endif |
diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index ca62cb583221..15013f4519ad 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c | |||
@@ -202,6 +202,42 @@ static int __devinit ohci_pci_start (struct usb_hcd *hcd) | |||
202 | return ret; | 202 | return ret; |
203 | } | 203 | } |
204 | 204 | ||
205 | #if defined(CONFIG_USB_PERSIST) && (defined(CONFIG_USB_EHCI_HCD) || \ | ||
206 | defined(CONFIG_USB_EHCI_HCD_MODULE)) | ||
207 | |||
208 | /* Following a power loss, we must prepare to regain control of the ports | ||
209 | * we used to own. This means turning on the port power before ehci-hcd | ||
210 | * tries to switch ownership. | ||
211 | * | ||
212 | * This isn't a 100% perfect solution. On most systems the OHCI controllers | ||
213 | * lie at lower PCI addresses than the EHCI controller, so they will be | ||
214 | * discovered (and hence resumed) first. But there is no guarantee things | ||
215 | * will always work this way. If the EHCI controller is resumed first and | ||
216 | * the OHCI ports are unpowered, then the handover will fail. | ||
217 | */ | ||
218 | static void prepare_for_handover(struct usb_hcd *hcd) | ||
219 | { | ||
220 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | ||
221 | int port; | ||
222 | |||
223 | /* Here we "know" root ports should always stay powered */ | ||
224 | ohci_dbg(ohci, "powerup ports\n"); | ||
225 | for (port = 0; port < ohci->num_ports; port++) | ||
226 | ohci_writel(ohci, RH_PS_PPS, | ||
227 | &ohci->regs->roothub.portstatus[port]); | ||
228 | |||
229 | /* Flush those writes */ | ||
230 | ohci_readl(ohci, &ohci->regs->control); | ||
231 | msleep(20); | ||
232 | } | ||
233 | |||
234 | #else | ||
235 | |||
236 | static inline void prepare_for_handover(struct usb_hcd *hcd) | ||
237 | { } | ||
238 | |||
239 | #endif /* CONFIG_USB_PERSIST etc. */ | ||
240 | |||
205 | #ifdef CONFIG_PM | 241 | #ifdef CONFIG_PM |
206 | 242 | ||
207 | static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) | 243 | static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) |
@@ -241,7 +277,10 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) | |||
241 | static int ohci_pci_resume (struct usb_hcd *hcd) | 277 | static int ohci_pci_resume (struct usb_hcd *hcd) |
242 | { | 278 | { |
243 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | 279 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); |
244 | usb_hcd_resume_root_hub(hcd); | 280 | |
281 | /* FIXME: we should try to detect loss of VBUS power here */ | ||
282 | prepare_for_handover(hcd); | ||
283 | |||
245 | return 0; | 284 | return 0; |
246 | } | 285 | } |
247 | 286 | ||