Commit a0d4922d authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

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: default avatarAlan Stern <stern@rowland.harvard.edu>
CC: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent a81a81a2
...@@ -191,17 +191,15 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_remove); ...@@ -191,17 +191,15 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);
/** /**
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
* @dev: USB Host Controller being suspended * @dev: USB Host Controller being suspended
* @message: semantics in flux * @message: Power Management message describing this state transition
* *
* Store this function in the HCD's struct pci_driver as suspend(). * Store this function in the HCD's struct pci_driver as .suspend.
*/ */
int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
{ {
struct usb_hcd *hcd; struct usb_hcd *hcd = pci_get_drvdata(dev);
int retval = 0; int retval = 0;
int has_pci_pm; int wake, w;
hcd = pci_get_drvdata(dev);
/* Root hub suspend should have stopped all downstream traffic, /* Root hub suspend should have stopped all downstream traffic,
* and all bus master traffic. And done so for both the interface * 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,8 +210,15 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
* otherwise the swsusp will save (and restore) garbage state. * otherwise the swsusp will save (and restore) garbage state.
*/ */
if (!(hcd->state == HC_STATE_SUSPENDED || if (!(hcd->state == HC_STATE_SUSPENDED ||
hcd->state == HC_STATE_HALT)) hcd->state == HC_STATE_HALT)) {
return -EBUSY; dev_warn(&dev->dev, "Root hub is not suspended\n");
retval = -EBUSY;
goto done;
}
/* We might already be suspended (runtime PM -- not yet written) */
if (dev->current_state != PCI_D0)
goto done;
if (hcd->driver->pci_suspend) { if (hcd->driver->pci_suspend) {
retval = hcd->driver->pci_suspend(hcd, message); 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,49 +226,60 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
if (retval) if (retval)
goto done; goto done;
} }
synchronize_irq(dev->irq);
/* FIXME until the generic PM interfaces change a lot more, this synchronize_irq(dev->irq);
* can't use PCI D1 and D2 states. For example, the confusion
* between messages and states will need to vanish, and messages
* will need to provide a target system state again.
*
* It'll be important to learn characteristics of the target state,
* especially on embedded hardware where the HCD will often be in
* charge of an external VBUS power supply and one or more clocks.
* Some target system states will leave them active; others won't.
* (With PCI, that's often handled by platform BIOS code.)
*/
/* even when the PCI layer rejects some of the PCI calls /* Don't fail on error to enable wakeup. We rely on pci code
* below, HCs can try global suspend and reduce DMA traffic. * to reject requests the hardware can't implement, rather
* PM-sensitive HCDs may already have done this. * than coding the same thing.
*/ */
has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); wake = (hcd->state == HC_STATE_SUSPENDED &&
device_may_wakeup(&dev->dev));
w = pci_wake_from_d3(dev, wake);
if (w < 0)
wake = w;
dev_dbg(&dev->dev, "wakeup: %d\n", wake);
/* Downstream ports from this root hub should already be quiesced, so /* Downstream ports from this root hub should already be quiesced, so
* there will be no DMA activity. Now we can shut down the upstream * there will be no DMA activity. Now we can shut down the upstream
* link (except maybe for PME# resume signaling) and enter some PCI * link (except maybe for PME# resume signaling) and enter some PCI
* low power state, if the hardware allows. * low power state, if the hardware allows.
*/ */
if (hcd->state == HC_STATE_SUSPENDED) { pci_disable_device(dev);
done:
return retval;
}
EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
/* no DMA or IRQs except when HC is active */ /**
if (dev->current_state == PCI_D0) { * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled
pci_save_state(dev); * @dev: USB Host Controller being suspended
pci_disable_device(dev); * @message: Power Management message describing this state transition
} *
* Store this function in the HCD's struct pci_driver as .suspend_late.
*/
int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message)
{
int retval = 0;
int has_pci_pm;
if (message.event == PM_EVENT_FREEZE || /* We might already be suspended (runtime PM -- not yet written) */
message.event == PM_EVENT_PRETHAW) { if (dev->current_state != PCI_D0)
dev_dbg(hcd->self.controller, "--> no state change\n"); goto done;
goto done;
}
if (!has_pci_pm) { pci_save_state(dev);
dev_dbg(hcd->self.controller, "--> PCI D0/legacy\n");
goto done; /* Don't change state if we don't need to */
} if (message.event == PM_EVENT_FREEZE ||
message.event == PM_EVENT_PRETHAW) {
dev_dbg(&dev->dev, "--> no state change\n");
goto done;
}
has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
if (!has_pci_pm) {
dev_dbg(&dev->dev, "--> PCI D0 legacy\n");
} else {
/* NOTE: dev->current_state becomes nonzero only here, and /* NOTE: dev->current_state becomes nonzero only here, and
* only for devices that support PCI PM. Also, exiting * 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,35 +289,16 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
retval = pci_set_power_state(dev, PCI_D3hot); retval = pci_set_power_state(dev, PCI_D3hot);
suspend_report_result(pci_set_power_state, retval); suspend_report_result(pci_set_power_state, retval);
if (retval == 0) { if (retval == 0) {
int wake = device_can_wakeup(&hcd->self.root_hub->dev); dev_dbg(&dev->dev, "--> PCI D3\n");
wake = wake && device_may_wakeup(hcd->self.controller);
dev_dbg(hcd->self.controller, "--> PCI D3%s\n",
wake ? "/wakeup" : "");
/* Ignore these return values. We rely on pci code to
* reject requests the hardware can't implement, rather
* than coding the same thing.
*/
(void) pci_enable_wake(dev, PCI_D3hot, wake);
(void) pci_enable_wake(dev, PCI_D3cold, wake);
} else { } else {
dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n", dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",
retval); retval);
(void) usb_hcd_pci_resume(dev); pci_restore_state(dev);
} }
} else if (hcd->state != HC_STATE_HALT) {
dev_dbg(hcd->self.controller, "hcd state %d; not suspended\n",
hcd->state);
WARN_ON(1);
retval = -EINVAL;
} }
done:
if (retval == 0) {
#ifdef CONFIG_PPC_PMAC #ifdef CONFIG_PPC_PMAC
if (retval == 0) {
/* Disable ASIC clocks for USB */ /* Disable ASIC clocks for USB */
if (machine_is(powermac)) { if (machine_is(powermac)) {
struct device_node *of_node; struct device_node *of_node;
...@@ -311,30 +308,24 @@ done: ...@@ -311,30 +308,24 @@ done:
pmac_call_feature(PMAC_FTR_USB_ENABLE, pmac_call_feature(PMAC_FTR_USB_ENABLE,
of_node, 0, 0); of_node, 0, 0);
} }
#endif
} }
#endif
done:
return retval; return retval;
} }
EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late);
/** /**
* usb_hcd_pci_resume - power management resume of a PCI-based HCD * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled
* @dev: USB Host Controller being resumed * @dev: USB Host Controller being resumed
* *
* Store this function in the HCD's struct pci_driver as resume(). * Store this function in the HCD's struct pci_driver as .resume_early.
*/ */
int usb_hcd_pci_resume(struct pci_dev *dev) int usb_hcd_pci_resume_early(struct pci_dev *dev)
{ {
struct usb_hcd *hcd; int retval = 0;
int retval; pci_power_t state = dev->current_state;
hcd = pci_get_drvdata(dev);
if (hcd->state != HC_STATE_SUSPENDED) {
dev_dbg(hcd->self.controller,
"can't resume, not suspended!\n");
return 0;
}
#ifdef CONFIG_PPC_PMAC #ifdef CONFIG_PPC_PMAC
/* Reenable ASIC clocks for USB */ /* Reenable ASIC clocks for USB */
...@@ -352,7 +343,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev) ...@@ -352,7 +343,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
* calls "standby", "suspend to RAM", and so on). There are also * calls "standby", "suspend to RAM", and so on). There are also
* dirty cases when swsusp fakes a suspend in "shutdown" mode. * dirty cases when swsusp fakes a suspend in "shutdown" mode.
*/ */
if (dev->current_state != PCI_D0) { if (state != PCI_D0) {
#ifdef DEBUG #ifdef DEBUG
int pci_pm; int pci_pm;
u16 pmcr; u16 pmcr;
...@@ -364,8 +355,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev) ...@@ -364,8 +355,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
/* Clean case: power to USB and to HC registers was /* Clean case: power to USB and to HC registers was
* maintained; remote wakeup is easy. * maintained; remote wakeup is easy.
*/ */
dev_dbg(hcd->self.controller, "resume from PCI D%d\n", dev_dbg(&dev->dev, "resume from PCI D%d\n", pmcr);
pmcr);
} else { } else {
/* Clean: HC lost Vcc power, D0 uninitialized /* Clean: HC lost Vcc power, D0 uninitialized
* + Vaux may have preserved port and transceiver * + Vaux may have preserved port and transceiver
...@@ -376,32 +366,55 @@ int usb_hcd_pci_resume(struct pci_dev *dev) ...@@ -376,32 +366,55 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
* + after BIOS init * + after BIOS init
* + after Linux init (HCD statically linked) * + after Linux init (HCD statically linked)
*/ */
dev_dbg(hcd->self.controller, dev_dbg(&dev->dev, "resume from previous PCI D%d\n",
"PCI D0, from previous PCI D%d\n", state);
dev->current_state);
} }
#endif #endif
/* yes, ignore these results too... */
(void) pci_enable_wake(dev, dev->current_state, 0); retval = pci_set_power_state(dev, PCI_D0);
(void) pci_enable_wake(dev, PCI_D3cold, 0);
} else { } else {
/* Same basic cases: clean (powered/not), dirty */ /* Same basic cases: clean (powered/not), dirty */
dev_dbg(hcd->self.controller, "PCI legacy resume\n"); dev_dbg(&dev->dev, "PCI legacy resume\n");
}
if (retval < 0)
dev_err(&dev->dev, "can't resume: %d\n", retval);
else
pci_restore_state(dev);
return retval;
}
EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early);
/**
* usb_hcd_pci_resume - power management resume of a PCI-based HCD
* @dev: USB Host Controller being resumed
*
* Store this function in the HCD's struct pci_driver as .resume.
*/
int usb_hcd_pci_resume(struct pci_dev *dev)
{
struct usb_hcd *hcd;
int retval;
hcd = pci_get_drvdata(dev);
if (hcd->state != HC_STATE_SUSPENDED) {
dev_dbg(hcd->self.controller,
"can't resume, not suspended!\n");
return 0;
} }
/* NOTE: the PCI API itself is asymmetric here. We don't need to
* pci_set_power_state(PCI_D0) since that's part of re-enabling;
* but that won't re-enable bus mastering. Yet pci_disable_device()
* explicitly disables bus mastering...
*/
retval = pci_enable_device(dev); retval = pci_enable_device(dev);
if (retval < 0) { if (retval < 0) {
dev_err(hcd->self.controller, dev_err(&dev->dev, "can't re-enable after resume, %d!\n",
"can't re-enable after resume, %d!\n", retval); retval);
return retval; return retval;
} }
pci_set_master(dev); pci_set_master(dev);
pci_restore_state(dev);
/* yes, ignore this result too... */
(void) pci_wake_from_d3(dev, 0);
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
...@@ -413,7 +426,6 @@ int usb_hcd_pci_resume(struct pci_dev *dev) ...@@ -413,7 +426,6 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
usb_hc_died(hcd); usb_hc_died(hcd);
} }
} }
return retval; return retval;
} }
EXPORT_SYMBOL_GPL(usb_hcd_pci_resume); EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
......
...@@ -256,7 +256,9 @@ extern int usb_hcd_pci_probe(struct pci_dev *dev, ...@@ -256,7 +256,9 @@ extern int usb_hcd_pci_probe(struct pci_dev *dev,
extern void usb_hcd_pci_remove(struct pci_dev *dev); extern void usb_hcd_pci_remove(struct pci_dev *dev);
#ifdef CONFIG_PM #ifdef CONFIG_PM
extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t state); extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg);
extern int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t msg);
extern int usb_hcd_pci_resume_early(struct pci_dev *dev);
extern int usb_hcd_pci_resume(struct pci_dev *dev); extern int usb_hcd_pci_resume(struct pci_dev *dev);
#endif /* CONFIG_PM */ #endif /* CONFIG_PM */
......
...@@ -428,6 +428,8 @@ static struct pci_driver ehci_pci_driver = { ...@@ -428,6 +428,8 @@ static struct pci_driver ehci_pci_driver = {
#ifdef CONFIG_PM #ifdef CONFIG_PM
.suspend = usb_hcd_pci_suspend, .suspend = usb_hcd_pci_suspend,
.suspend_late = usb_hcd_pci_suspend_late,
.resume_early = usb_hcd_pci_resume_early,
.resume = usb_hcd_pci_resume, .resume = usb_hcd_pci_resume,
#endif #endif
.shutdown = usb_hcd_pci_shutdown, .shutdown = usb_hcd_pci_shutdown,
......
...@@ -487,6 +487,8 @@ static struct pci_driver ohci_pci_driver = { ...@@ -487,6 +487,8 @@ static struct pci_driver ohci_pci_driver = {
#ifdef CONFIG_PM #ifdef CONFIG_PM
.suspend = usb_hcd_pci_suspend, .suspend = usb_hcd_pci_suspend,
.suspend_late = usb_hcd_pci_suspend_late,
.resume_early = usb_hcd_pci_resume_early,
.resume = usb_hcd_pci_resume, .resume = usb_hcd_pci_resume,
#endif #endif
......
...@@ -942,6 +942,8 @@ static struct pci_driver uhci_pci_driver = { ...@@ -942,6 +942,8 @@ static struct pci_driver uhci_pci_driver = {
#ifdef CONFIG_PM #ifdef CONFIG_PM
.suspend = usb_hcd_pci_suspend, .suspend = usb_hcd_pci_suspend,
.suspend_late = usb_hcd_pci_suspend_late,
.resume_early = usb_hcd_pci_resume_early,
.resume = usb_hcd_pci_resume, .resume = usb_hcd_pci_resume,
#endif /* PM */ #endif /* PM */
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment