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