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/hcd-pci.c | |
| 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/hcd-pci.c')
| -rw-r--r-- | drivers/usb/core/hcd-pci.c | 116 |
1 files changed, 27 insertions, 89 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", |
