Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

USB: fix up suspend and resume for PCI host controllers

This patch (as1192) rearranges the USB PCI host controller suspend and
resume and resume routines:

Use pci_wake_from_d3() for enabling and disabling wakeup,
instead of pci_enable_wake().

Carry out the actual state change while interrupts are
disabled.

Change the order of the preparations to agree with the
general recommendation for PCI devices, instead of
messing around with the wakeup settings while the device
is in D3.

In .suspend:
Call the underlying driver to disable IRQ
generation;
pci_wake_from_d3(device_may_wakeup());
pci_disable_device();

In .suspend_late:
pci_save_state();
pci_set_power_state(D3hot);
(for PPC_PMAC) Disable ASIC clocks

In .resume_early:
(for PPC_PMAC) Enable ASIC clocks
pci_set_power_state(D0);
pci_restore_state();

In .resume:
pci_enable_device();
pci_set_master();
pci_wake_from_d3(0);
Call the underlying driver to reenable IRQ
generation

Add the necessary .suspend_late and .resume_early method
pointers to the PCI host controller drivers.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

authored by

Alan Stern and committed by
Greg Kroah-Hartman
a0d4922d a81a81a2

+115 -95
+106 -94
drivers/usb/core/hcd-pci.c
··· 191 191 /** 192 192 * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD 193 193 * @dev: USB Host Controller being suspended 194 - * @message: semantics in flux 194 + * @message: Power Management message describing this state transition 195 195 * 196 - * Store this function in the HCD's struct pci_driver as suspend(). 196 + * Store this function in the HCD's struct pci_driver as .suspend. 197 197 */ 198 198 int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) 199 199 { 200 - struct usb_hcd *hcd; 200 + struct usb_hcd *hcd = pci_get_drvdata(dev); 201 201 int retval = 0; 202 - int has_pci_pm; 203 - 204 - hcd = pci_get_drvdata(dev); 202 + int wake, w; 205 203 206 204 /* Root hub suspend should have stopped all downstream traffic, 207 205 * and all bus master traffic. And done so for both the interface ··· 210 212 * otherwise the swsusp will save (and restore) garbage state. 211 213 */ 212 214 if (!(hcd->state == HC_STATE_SUSPENDED || 213 - hcd->state == HC_STATE_HALT)) 214 - return -EBUSY; 215 + hcd->state == HC_STATE_HALT)) { 216 + dev_warn(&dev->dev, "Root hub is not suspended\n"); 217 + retval = -EBUSY; 218 + goto done; 219 + } 220 + 221 + /* We might already be suspended (runtime PM -- not yet written) */ 222 + if (dev->current_state != PCI_D0) 223 + goto done; 215 224 216 225 if (hcd->driver->pci_suspend) { 217 226 retval = hcd->driver->pci_suspend(hcd, message); ··· 226 221 if (retval) 227 222 goto done; 228 223 } 224 + 229 225 synchronize_irq(dev->irq); 230 226 231 - /* FIXME until the generic PM interfaces change a lot more, this 232 - * can't use PCI D1 and D2 states. For example, the confusion 233 - * between messages and states will need to vanish, and messages 234 - * will need to provide a target system state again. 235 - * 236 - * It'll be important to learn characteristics of the target state, 237 - * especially on embedded hardware where the HCD will often be in 238 - * charge of an external VBUS power supply and one or more clocks. 239 - * Some target system states will leave them active; others won't. 240 - * (With PCI, that's often handled by platform BIOS code.) 227 + /* Don't fail on error to enable wakeup. We rely on pci code 228 + * to reject requests the hardware can't implement, rather 229 + * than coding the same thing. 241 230 */ 242 - 243 - /* even when the PCI layer rejects some of the PCI calls 244 - * below, HCs can try global suspend and reduce DMA traffic. 245 - * PM-sensitive HCDs may already have done this. 246 - */ 247 - has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); 231 + wake = (hcd->state == HC_STATE_SUSPENDED && 232 + device_may_wakeup(&dev->dev)); 233 + w = pci_wake_from_d3(dev, wake); 234 + if (w < 0) 235 + wake = w; 236 + dev_dbg(&dev->dev, "wakeup: %d\n", wake); 248 237 249 238 /* Downstream ports from this root hub should already be quiesced, so 250 239 * there will be no DMA activity. Now we can shut down the upstream 251 240 * link (except maybe for PME# resume signaling) and enter some PCI 252 241 * low power state, if the hardware allows. 253 242 */ 254 - if (hcd->state == HC_STATE_SUSPENDED) { 243 + pci_disable_device(dev); 244 + done: 245 + return retval; 246 + } 247 + EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); 255 248 256 - /* no DMA or IRQs except when HC is active */ 257 - if (dev->current_state == PCI_D0) { 258 - pci_save_state(dev); 259 - pci_disable_device(dev); 260 - } 249 + /** 250 + * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled 251 + * @dev: USB Host Controller being suspended 252 + * @message: Power Management message describing this state transition 253 + * 254 + * Store this function in the HCD's struct pci_driver as .suspend_late. 255 + */ 256 + int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message) 257 + { 258 + int retval = 0; 259 + int has_pci_pm; 261 260 262 - if (message.event == PM_EVENT_FREEZE || 263 - message.event == PM_EVENT_PRETHAW) { 264 - dev_dbg(hcd->self.controller, "--> no state change\n"); 265 - goto done; 266 - } 261 + /* We might already be suspended (runtime PM -- not yet written) */ 262 + if (dev->current_state != PCI_D0) 263 + goto done; 267 264 268 - if (!has_pci_pm) { 269 - dev_dbg(hcd->self.controller, "--> PCI D0/legacy\n"); 270 - goto done; 271 - } 265 + pci_save_state(dev); 266 + 267 + /* Don't change state if we don't need to */ 268 + if (message.event == PM_EVENT_FREEZE || 269 + message.event == PM_EVENT_PRETHAW) { 270 + dev_dbg(&dev->dev, "--> no state change\n"); 271 + goto done; 272 + } 273 + 274 + has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); 275 + if (!has_pci_pm) { 276 + dev_dbg(&dev->dev, "--> PCI D0 legacy\n"); 277 + } else { 272 278 273 279 /* NOTE: dev->current_state becomes nonzero only here, and 274 280 * only for devices that support PCI PM. Also, exiting ··· 289 273 retval = pci_set_power_state(dev, PCI_D3hot); 290 274 suspend_report_result(pci_set_power_state, retval); 291 275 if (retval == 0) { 292 - int wake = device_can_wakeup(&hcd->self.root_hub->dev); 293 - 294 - wake = wake && device_may_wakeup(hcd->self.controller); 295 - 296 - dev_dbg(hcd->self.controller, "--> PCI D3%s\n", 297 - wake ? "/wakeup" : ""); 298 - 299 - /* Ignore these return values. We rely on pci code to 300 - * reject requests the hardware can't implement, rather 301 - * than coding the same thing. 302 - */ 303 - (void) pci_enable_wake(dev, PCI_D3hot, wake); 304 - (void) pci_enable_wake(dev, PCI_D3cold, wake); 276 + dev_dbg(&dev->dev, "--> PCI D3\n"); 305 277 } else { 306 278 dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n", 307 279 retval); 308 - (void) usb_hcd_pci_resume(dev); 280 + pci_restore_state(dev); 309 281 } 310 - 311 - } else if (hcd->state != HC_STATE_HALT) { 312 - dev_dbg(hcd->self.controller, "hcd state %d; not suspended\n", 313 - hcd->state); 314 - WARN_ON(1); 315 - retval = -EINVAL; 316 282 } 317 283 318 - done: 319 - if (retval == 0) { 320 284 #ifdef CONFIG_PPC_PMAC 285 + if (retval == 0) { 321 286 /* Disable ASIC clocks for USB */ 322 287 if (machine_is(powermac)) { 323 288 struct device_node *of_node; ··· 308 311 pmac_call_feature(PMAC_FTR_USB_ENABLE, 309 312 of_node, 0, 0); 310 313 } 311 - #endif 312 314 } 315 + #endif 313 316 317 + done: 314 318 return retval; 315 319 } 316 - EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); 320 + EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late); 317 321 318 322 /** 319 - * usb_hcd_pci_resume - power management resume of a PCI-based HCD 323 + * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled 320 324 * @dev: USB Host Controller being resumed 321 325 * 322 - * Store this function in the HCD's struct pci_driver as resume(). 326 + * Store this function in the HCD's struct pci_driver as .resume_early. 323 327 */ 324 - int usb_hcd_pci_resume(struct pci_dev *dev) 328 + int usb_hcd_pci_resume_early(struct pci_dev *dev) 325 329 { 326 - struct usb_hcd *hcd; 327 - int retval; 328 - 329 - hcd = pci_get_drvdata(dev); 330 - if (hcd->state != HC_STATE_SUSPENDED) { 331 - dev_dbg(hcd->self.controller, 332 - "can't resume, not suspended!\n"); 333 - return 0; 334 - } 330 + int retval = 0; 331 + pci_power_t state = dev->current_state; 335 332 336 333 #ifdef CONFIG_PPC_PMAC 337 334 /* Reenable ASIC clocks for USB */ ··· 343 352 * calls "standby", "suspend to RAM", and so on). There are also 344 353 * dirty cases when swsusp fakes a suspend in "shutdown" mode. 345 354 */ 346 - if (dev->current_state != PCI_D0) { 355 + if (state != PCI_D0) { 347 356 #ifdef DEBUG 348 357 int pci_pm; 349 358 u16 pmcr; ··· 355 364 /* Clean case: power to USB and to HC registers was 356 365 * maintained; remote wakeup is easy. 357 366 */ 358 - dev_dbg(hcd->self.controller, "resume from PCI D%d\n", 359 - pmcr); 367 + dev_dbg(&dev->dev, "resume from PCI D%d\n", pmcr); 360 368 } else { 361 369 /* Clean: HC lost Vcc power, D0 uninitialized 362 370 * + Vaux may have preserved port and transceiver ··· 366 376 * + after BIOS init 367 377 * + after Linux init (HCD statically linked) 368 378 */ 369 - dev_dbg(hcd->self.controller, 370 - "PCI D0, from previous PCI D%d\n", 371 - dev->current_state); 379 + dev_dbg(&dev->dev, "resume from previous PCI D%d\n", 380 + state); 372 381 } 373 382 #endif 374 - /* yes, ignore these results too... */ 375 - (void) pci_enable_wake(dev, dev->current_state, 0); 376 - (void) pci_enable_wake(dev, PCI_D3cold, 0); 383 + 384 + retval = pci_set_power_state(dev, PCI_D0); 377 385 } else { 378 386 /* Same basic cases: clean (powered/not), dirty */ 379 - dev_dbg(hcd->self.controller, "PCI legacy resume\n"); 387 + dev_dbg(&dev->dev, "PCI legacy resume\n"); 380 388 } 381 389 382 - /* NOTE: the PCI API itself is asymmetric here. We don't need to 383 - * pci_set_power_state(PCI_D0) since that's part of re-enabling; 384 - * but that won't re-enable bus mastering. Yet pci_disable_device() 385 - * explicitly disables bus mastering... 386 - */ 390 + if (retval < 0) 391 + dev_err(&dev->dev, "can't resume: %d\n", retval); 392 + else 393 + pci_restore_state(dev); 394 + 395 + return retval; 396 + } 397 + EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early); 398 + 399 + /** 400 + * usb_hcd_pci_resume - power management resume of a PCI-based HCD 401 + * @dev: USB Host Controller being resumed 402 + * 403 + * Store this function in the HCD's struct pci_driver as .resume. 404 + */ 405 + int usb_hcd_pci_resume(struct pci_dev *dev) 406 + { 407 + struct usb_hcd *hcd; 408 + int retval; 409 + 410 + hcd = pci_get_drvdata(dev); 411 + if (hcd->state != HC_STATE_SUSPENDED) { 412 + dev_dbg(hcd->self.controller, 413 + "can't resume, not suspended!\n"); 414 + return 0; 415 + } 416 + 387 417 retval = pci_enable_device(dev); 388 418 if (retval < 0) { 389 - dev_err(hcd->self.controller, 390 - "can't re-enable after resume, %d!\n", retval); 419 + dev_err(&dev->dev, "can't re-enable after resume, %d!\n", 420 + retval); 391 421 return retval; 392 422 } 423 + 393 424 pci_set_master(dev); 394 - pci_restore_state(dev); 425 + 426 + /* yes, ignore this result too... */ 427 + (void) pci_wake_from_d3(dev, 0); 395 428 396 429 clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); 397 430 ··· 426 413 usb_hc_died(hcd); 427 414 } 428 415 } 429 - 430 416 return retval; 431 417 } 432 418 EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
+3 -1
drivers/usb/core/hcd.h
··· 256 256 extern void usb_hcd_pci_remove(struct pci_dev *dev); 257 257 258 258 #ifdef CONFIG_PM 259 - extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t state); 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 262 extern int usb_hcd_pci_resume(struct pci_dev *dev); 261 263 #endif /* CONFIG_PM */ 262 264
+2
drivers/usb/host/ehci-pci.c
··· 428 428 429 429 #ifdef CONFIG_PM 430 430 .suspend = usb_hcd_pci_suspend, 431 + .suspend_late = usb_hcd_pci_suspend_late, 432 + .resume_early = usb_hcd_pci_resume_early, 431 433 .resume = usb_hcd_pci_resume, 432 434 #endif 433 435 .shutdown = usb_hcd_pci_shutdown,
+2
drivers/usb/host/ohci-pci.c
··· 487 487 488 488 #ifdef CONFIG_PM 489 489 .suspend = usb_hcd_pci_suspend, 490 + .suspend_late = usb_hcd_pci_suspend_late, 491 + .resume_early = usb_hcd_pci_resume_early, 490 492 .resume = usb_hcd_pci_resume, 491 493 #endif 492 494
+2
drivers/usb/host/uhci-hcd.c
··· 942 942 943 943 #ifdef CONFIG_PM 944 944 .suspend = usb_hcd_pci_suspend, 945 + .suspend_late = usb_hcd_pci_suspend_late, 946 + .resume_early = usb_hcd_pci_resume_early, 945 947 .resume = usb_hcd_pci_resume, 946 948 #endif /* PM */ 947 949 };