diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2006-11-09 14:42:16 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2006-12-01 17:25:52 -0500 |
commit | 8c03356a559ced6fa78931f498193f776d67e445 (patch) | |
tree | 56539740dea1febc7cac7a83bde1a56e48e9d5ec /drivers/usb/host/ehci-pci.c | |
parent | c066475e1fe3b3afbd613ddf5f1eca9be4fb6de0 (diff) |
EHCI: Fix root-hub and port suspend/resume problems
This patch (as738b) fixes numerous problems in the controller/root-hub
suspend/resume/remote-wakeup support in ehci-hcd:
The bus_resume() routine should wake up only the ports that
were suspended by bus_suspend(). Ports that were already
suspended should remain that way.
The interrupt mask is used to detect loss of power in the
bus_resume() routine (if the mask is 0 then power was lost).
However bus_suspend() always sets the mask to 0. Instead the
mask should retain its normal value, with port-change-detect
interrupts disabled if remote wakeup is turned off.
The interrupt mask should be reset to its correct value at the
end of bus_resume() regardless of whether power was lost.
bus_resume() reinitializes the operational registers if power
was lost. However those registers are not in the aux power
well, hence they can lose their values whenever the controller
is put into D3. They should always be reinitialized.
When a port-change interrupt occurs and the root hub is
suspended, the interrupt handler should request a root-hub
resume instead of starting up the controller all by itself.
There's no need for the interrupt handler to request a
root-hub resume every time a suspended port sends a
remote-wakeup request.
The pci_resume() method doesn't need to check for connected
ports when deciding whether or not to reset the controller.
It can make that decision based on whether Vaux power was
maintained.
Even when the controller does not need to be reset,
pci_resume() must undo the effect of pci_suspend() by
re-enabling the interrupt mask.
If power was lost, pci_resume() must not call ehci_run().
At this point the root hub is still supposed to be suspended,
not running. It's enough to rewrite the command register and
set the configured_flag.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/ehci-pci.c')
-rw-r--r-- | drivers/usb/host/ehci-pci.c | 40 |
1 files changed, 16 insertions, 24 deletions
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index e51c1ed81ac4..4bc7970ba3ef 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c | |||
@@ -257,9 +257,7 @@ static int ehci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) | |||
257 | static int ehci_pci_resume(struct usb_hcd *hcd) | 257 | static int ehci_pci_resume(struct usb_hcd *hcd) |
258 | { | 258 | { |
259 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); | 259 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
260 | unsigned port; | ||
261 | struct pci_dev *pdev = to_pci_dev(hcd->self.controller); | 260 | struct pci_dev *pdev = to_pci_dev(hcd->self.controller); |
262 | int retval = -EINVAL; | ||
263 | 261 | ||
264 | // maybe restore FLADJ | 262 | // maybe restore FLADJ |
265 | 263 | ||
@@ -269,27 +267,19 @@ static int ehci_pci_resume(struct usb_hcd *hcd) | |||
269 | /* Mark hardware accessible again as we are out of D3 state by now */ | 267 | /* Mark hardware accessible again as we are out of D3 state by now */ |
270 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | 268 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); |
271 | 269 | ||
272 | /* If CF is clear, we lost PCI Vaux power and need to restart. */ | 270 | /* If CF is still set, we maintained PCI Vaux power. |
273 | if (readl(&ehci->regs->configured_flag) != FLAG_CF) | 271 | * Just undo the effect of ehci_pci_suspend(). |
274 | goto restart; | ||
275 | |||
276 | /* If any port is suspended (or owned by the companion), | ||
277 | * we know we can/must resume the HC (and mustn't reset it). | ||
278 | * We just defer that to the root hub code. | ||
279 | */ | 272 | */ |
280 | for (port = HCS_N_PORTS(ehci->hcs_params); port > 0; ) { | 273 | if (readl(&ehci->regs->configured_flag) == FLAG_CF) { |
281 | u32 status; | 274 | int mask = INTR_MASK; |
282 | port--; | 275 | |
283 | status = readl(&ehci->regs->port_status [port]); | 276 | if (!device_may_wakeup(&hcd->self.root_hub->dev)) |
284 | if (!(status & PORT_POWER)) | 277 | mask &= ~STS_PCD; |
285 | continue; | 278 | writel(mask, &ehci->regs->intr_enable); |
286 | if (status & (PORT_SUSPEND | PORT_RESUME | PORT_OWNER)) { | 279 | readl(&ehci->regs->intr_enable); |
287 | usb_hcd_resume_root_hub(hcd); | 280 | return 0; |
288 | return 0; | ||
289 | } | ||
290 | } | 281 | } |
291 | 282 | ||
292 | restart: | ||
293 | ehci_dbg(ehci, "lost power, restarting\n"); | 283 | ehci_dbg(ehci, "lost power, restarting\n"); |
294 | usb_root_hub_lost_power(hcd->self.root_hub); | 284 | usb_root_hub_lost_power(hcd->self.root_hub); |
295 | 285 | ||
@@ -307,13 +297,15 @@ restart: | |||
307 | ehci_work(ehci); | 297 | ehci_work(ehci); |
308 | spin_unlock_irq(&ehci->lock); | 298 | spin_unlock_irq(&ehci->lock); |
309 | 299 | ||
310 | /* restart; khubd will disconnect devices */ | ||
311 | retval = ehci_run(hcd); | ||
312 | |||
313 | /* here we "know" root ports should always stay powered */ | 300 | /* here we "know" root ports should always stay powered */ |
314 | ehci_port_power(ehci, 1); | 301 | ehci_port_power(ehci, 1); |
315 | 302 | ||
316 | return retval; | 303 | writel(ehci->command, &ehci->regs->command); |
304 | writel(FLAG_CF, &ehci->regs->configured_flag); | ||
305 | readl(&ehci->regs->command); /* unblock posted writes */ | ||
306 | |||
307 | hcd->state = HC_STATE_SUSPENDED; | ||
308 | return 0; | ||
317 | } | 309 | } |
318 | #endif | 310 | #endif |
319 | 311 | ||