diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/hcd-pci.c | 200 | ||||
-rw-r--r-- | drivers/usb/core/hcd.h | 4 | ||||
-rw-r--r-- | drivers/usb/host/ehci-pci.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/ohci-pci.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/uhci-hcd.c | 2 |
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 | */ |
198 | int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) | 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 | 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 | } | ||
252 | EXPORT_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 | */ | ||
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; | ||
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 | ||
302 | done: | ||
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 | } |
319 | EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); | 317 | EXPORT_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 | */ |
327 | int usb_hcd_pci_resume(struct pci_dev *dev) | 325 | int 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 | } | ||
387 | EXPORT_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 | */ | ||
395 | int 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 | } |
419 | EXPORT_SYMBOL_GPL(usb_hcd_pci_resume); | 431 | EXPORT_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, | |||
256 | extern void usb_hcd_pci_remove(struct pci_dev *dev); | 256 | 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 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 | extern int usb_hcd_pci_resume(struct pci_dev *dev); | 262 | extern 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 | }; |