diff options
author | Rafael J. Wysocki <rjw@sisk.pl> | 2009-01-19 19:26:56 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@kvm.kroah.org> | 2009-01-27 19:15:32 -0500 |
commit | a15d95a003fae154121733f049dd25e9c13dbef3 (patch) | |
tree | 240642794b6ceb19f733cf93709cf5b223b146e9 /drivers/usb/core | |
parent | bcca06efea883bdf3803a0bb0ffa60f26730387d (diff) |
USB: Fix suspend-resume of PCI USB controllers
Commit a0d4922da2e4ccb0973095d8d29f36f6b1b5f703
(USB: fix up suspend and resume for PCI host controllers) attempted
to fix the suspend-resume of PCI USB controllers, but unfortunately
it did that incorrectly and interrupts are left enabled by the USB
controllers' ->suspend_late() callback as a result. This leads to
serious problems during suspend which are very difficult to debug.
Fix the issue by removing the ->suspend_late() callback of PCI
USB controllers and moving the code from there to the ->suspend()
callback executed with interrupts enabled. Additionally, make
the ->resume() callback of PCI USB controllers execute
pci_enable_wake(dev, PCI_D0, false) to disable wake-up from the
full power state (PCI_D0).
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Tested-by: Andrey Borzenkov <arvidjaar@mail.ru>
Tested-by: "Jeff Chua" <jeff.chua.linux@gmail.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Christian Borntraeger <borntraeger@de.ibm.com>
Cc: "Zdenek Kabelac" <zdenek.kabelac@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/hcd-pci.c | 116 | ||||
-rw-r--r-- | drivers/usb/core/hcd.h | 1 |
2 files changed, 27 insertions, 90 deletions
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 99432785f438..c54fc40458b1 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c | |||
@@ -200,6 +200,7 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) | |||
200 | struct usb_hcd *hcd = pci_get_drvdata(dev); | 200 | struct usb_hcd *hcd = pci_get_drvdata(dev); |
201 | int retval = 0; | 201 | int retval = 0; |
202 | int wake, w; | 202 | int wake, w; |
203 | int has_pci_pm; | ||
203 | 204 | ||
204 | /* Root hub suspend should have stopped all downstream traffic, | 205 | /* Root hub suspend should have stopped all downstream traffic, |
205 | * and all bus master traffic. And done so for both the interface | 206 | * and all bus master traffic. And done so for both the interface |
@@ -229,6 +230,15 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) | |||
229 | 230 | ||
230 | synchronize_irq(dev->irq); | 231 | synchronize_irq(dev->irq); |
231 | 232 | ||
233 | /* Downstream ports from this root hub should already be quiesced, so | ||
234 | * there will be no DMA activity. Now we can shut down the upstream | ||
235 | * link (except maybe for PME# resume signaling) and enter some PCI | ||
236 | * low power state, if the hardware allows. | ||
237 | */ | ||
238 | pci_disable_device(dev); | ||
239 | |||
240 | pci_save_state(dev); | ||
241 | |||
232 | /* Don't fail on error to enable wakeup. We rely on pci code | 242 | /* Don't fail on error to enable wakeup. We rely on pci code |
233 | * to reject requests the hardware can't implement, rather | 243 | * to reject requests the hardware can't implement, rather |
234 | * than coding the same thing. | 244 | * than coding the same thing. |
@@ -240,35 +250,6 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) | |||
240 | wake = w; | 250 | wake = w; |
241 | dev_dbg(&dev->dev, "wakeup: %d\n", wake); | 251 | dev_dbg(&dev->dev, "wakeup: %d\n", wake); |
242 | 252 | ||
243 | /* Downstream ports from this root hub should already be quiesced, so | ||
244 | * there will be no DMA activity. Now we can shut down the upstream | ||
245 | * link (except maybe for PME# resume signaling) and enter some PCI | ||
246 | * low power state, if the hardware allows. | ||
247 | */ | ||
248 | pci_disable_device(dev); | ||
249 | done: | ||
250 | return retval; | ||
251 | } | ||
252 | EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); | ||
253 | |||
254 | /** | ||
255 | * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled | ||
256 | * @dev: USB Host Controller being suspended | ||
257 | * @message: Power Management message describing this state transition | ||
258 | * | ||
259 | * Store this function in the HCD's struct pci_driver as .suspend_late. | ||
260 | */ | ||
261 | int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message) | ||
262 | { | ||
263 | int retval = 0; | ||
264 | int has_pci_pm; | ||
265 | |||
266 | /* We might already be suspended (runtime PM -- not yet written) */ | ||
267 | if (dev->current_state != PCI_D0) | ||
268 | goto done; | ||
269 | |||
270 | pci_save_state(dev); | ||
271 | |||
272 | /* Don't change state if we don't need to */ | 253 | /* Don't change state if we don't need to */ |
273 | if (message.event == PM_EVENT_FREEZE || | 254 | if (message.event == PM_EVENT_FREEZE || |
274 | message.event == PM_EVENT_PRETHAW) { | 255 | message.event == PM_EVENT_PRETHAW) { |
@@ -314,7 +295,7 @@ int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message) | |||
314 | done: | 295 | done: |
315 | return retval; | 296 | return retval; |
316 | } | 297 | } |
317 | EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late); | 298 | EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); |
318 | 299 | ||
319 | /** | 300 | /** |
320 | * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled | 301 | * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled |
@@ -324,65 +305,8 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late); | |||
324 | */ | 305 | */ |
325 | int usb_hcd_pci_resume_early(struct pci_dev *dev) | 306 | int usb_hcd_pci_resume_early(struct pci_dev *dev) |
326 | { | 307 | { |
327 | int retval = 0; | 308 | pci_restore_state(dev); |
328 | pci_power_t state = dev->current_state; | 309 | return 0; |
329 | |||
330 | #ifdef CONFIG_PPC_PMAC | ||
331 | /* Reenable ASIC clocks for USB */ | ||
332 | if (machine_is(powermac)) { | ||
333 | struct device_node *of_node; | ||
334 | |||
335 | of_node = pci_device_to_OF_node(dev); | ||
336 | if (of_node) | ||
337 | pmac_call_feature(PMAC_FTR_USB_ENABLE, | ||
338 | of_node, 0, 1); | ||
339 | } | ||
340 | #endif | ||
341 | |||
342 | /* NOTE: chip docs cover clean "real suspend" cases (what Linux | ||
343 | * calls "standby", "suspend to RAM", and so on). There are also | ||
344 | * dirty cases when swsusp fakes a suspend in "shutdown" mode. | ||
345 | */ | ||
346 | if (state != PCI_D0) { | ||
347 | #ifdef DEBUG | ||
348 | int pci_pm; | ||
349 | u16 pmcr; | ||
350 | |||
351 | pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); | ||
352 | pci_read_config_word(dev, pci_pm + PCI_PM_CTRL, &pmcr); | ||
353 | pmcr &= PCI_PM_CTRL_STATE_MASK; | ||
354 | if (pmcr) { | ||
355 | /* Clean case: power to USB and to HC registers was | ||
356 | * maintained; remote wakeup is easy. | ||
357 | */ | ||
358 | dev_dbg(&dev->dev, "resume from PCI D%d\n", pmcr); | ||
359 | } else { | ||
360 | /* Clean: HC lost Vcc power, D0 uninitialized | ||
361 | * + Vaux may have preserved port and transceiver | ||
362 | * state ... for remote wakeup from D3cold | ||
363 | * + or not; HCD must reinit + re-enumerate | ||
364 | * | ||
365 | * Dirty: D0 semi-initialized cases with swsusp | ||
366 | * + after BIOS init | ||
367 | * + after Linux init (HCD statically linked) | ||
368 | */ | ||
369 | dev_dbg(&dev->dev, "resume from previous PCI D%d\n", | ||
370 | state); | ||
371 | } | ||
372 | #endif | ||
373 | |||
374 | retval = pci_set_power_state(dev, PCI_D0); | ||
375 | } else { | ||
376 | /* Same basic cases: clean (powered/not), dirty */ | ||
377 | dev_dbg(&dev->dev, "PCI legacy resume\n"); | ||
378 | } | ||
379 | |||
380 | if (retval < 0) | ||
381 | dev_err(&dev->dev, "can't resume: %d\n", retval); | ||
382 | else | ||
383 | pci_restore_state(dev); | ||
384 | |||
385 | return retval; | ||
386 | } | 310 | } |
387 | EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early); | 311 | EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early); |
388 | 312 | ||
@@ -397,6 +321,18 @@ int usb_hcd_pci_resume(struct pci_dev *dev) | |||
397 | struct usb_hcd *hcd; | 321 | struct usb_hcd *hcd; |
398 | int retval; | 322 | int retval; |
399 | 323 | ||
324 | #ifdef CONFIG_PPC_PMAC | ||
325 | /* Reenable ASIC clocks for USB */ | ||
326 | if (machine_is(powermac)) { | ||
327 | struct device_node *of_node; | ||
328 | |||
329 | of_node = pci_device_to_OF_node(dev); | ||
330 | if (of_node) | ||
331 | pmac_call_feature(PMAC_FTR_USB_ENABLE, | ||
332 | of_node, 0, 1); | ||
333 | } | ||
334 | #endif | ||
335 | |||
400 | hcd = pci_get_drvdata(dev); | 336 | hcd = pci_get_drvdata(dev); |
401 | if (hcd->state != HC_STATE_SUSPENDED) { | 337 | if (hcd->state != HC_STATE_SUSPENDED) { |
402 | dev_dbg(hcd->self.controller, | 338 | dev_dbg(hcd->self.controller, |
@@ -404,6 +340,8 @@ int usb_hcd_pci_resume(struct pci_dev *dev) | |||
404 | return 0; | 340 | return 0; |
405 | } | 341 | } |
406 | 342 | ||
343 | pci_enable_wake(dev, PCI_D0, false); | ||
344 | |||
407 | retval = pci_enable_device(dev); | 345 | retval = pci_enable_device(dev); |
408 | if (retval < 0) { | 346 | if (retval < 0) { |
409 | dev_err(&dev->dev, "can't re-enable after resume, %d!\n", | 347 | dev_err(&dev->dev, "can't re-enable after resume, %d!\n", |
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 572d2cf46e8d..5b94a56bec23 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h | |||
@@ -257,7 +257,6 @@ extern void usb_hcd_pci_remove(struct pci_dev *dev); | |||
257 | 257 | ||
258 | #ifdef CONFIG_PM | 258 | #ifdef CONFIG_PM |
259 | extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg); | 259 | extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg); |
260 | extern int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t msg); | ||
261 | extern int usb_hcd_pci_resume_early(struct pci_dev *dev); | 260 | extern int usb_hcd_pci_resume_early(struct pci_dev *dev); |
262 | extern int usb_hcd_pci_resume(struct pci_dev *dev); | 261 | extern int usb_hcd_pci_resume(struct pci_dev *dev); |
263 | #endif /* CONFIG_PM */ | 262 | #endif /* CONFIG_PM */ |