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

USB: move PCI host controllers to new PM framework

This patch (as1236) converts the USB PCI power management routines
over to the new PM framework.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Acked-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 00240c38
...@@ -185,194 +185,198 @@ void usb_hcd_pci_remove(struct pci_dev *dev) ...@@ -185,194 +185,198 @@ void usb_hcd_pci_remove(struct pci_dev *dev)
} }
EXPORT_SYMBOL_GPL(usb_hcd_pci_remove); EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);
#ifdef CONFIG_PM
/** /**
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD * usb_hcd_pci_shutdown - shutdown host controller
* @dev: USB Host Controller being suspended * @dev: USB Host Controller being shutdown
* @message: Power Management message describing this state transition
*
* 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) void usb_hcd_pci_shutdown(struct pci_dev *dev)
{
struct usb_hcd *hcd;
hcd = pci_get_drvdata(dev);
if (!hcd)
return;
if (hcd->driver->shutdown)
hcd->driver->shutdown(hcd);
}
EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown);
#ifdef CONFIG_PM_SLEEP
static int check_root_hub_suspended(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct usb_hcd *hcd = pci_get_drvdata(pci_dev);
if (!(hcd->state == HC_STATE_SUSPENDED ||
hcd->state == HC_STATE_HALT)) {
dev_warn(dev, "Root hub is not suspended\n");
return -EBUSY;
}
return 0;
}
static int hcd_pci_suspend(struct device *dev)
{ {
struct usb_hcd *hcd = pci_get_drvdata(dev); struct pci_dev *pci_dev = to_pci_dev(dev);
int retval = 0; struct usb_hcd *hcd = pci_get_drvdata(pci_dev);
int wake, w; int retval;
int has_pci_pm;
/* 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
* and the stub usb_device (which we check here). But maybe it * and the stub usb_device (which we check here). But maybe it
* didn't; writing sysfs power/state files ignores such rules... * didn't; writing sysfs power/state files ignores such rules...
*
* We must ignore the FREEZE vs SUSPEND distinction here, because
* otherwise the swsusp will save (and restore) garbage state.
*/ */
if (!(hcd->state == HC_STATE_SUSPENDED || retval = check_root_hub_suspended(dev);
hcd->state == HC_STATE_HALT)) { if (retval)
dev_warn(&dev->dev, "Root hub is not suspended\n"); return retval;
retval = -EBUSY;
goto done;
}
/* We might already be suspended (runtime PM -- not yet written) */ /* We might already be suspended (runtime PM -- not yet written) */
if (dev->current_state != PCI_D0) if (pci_dev->current_state != PCI_D0)
goto done; return retval;
if (hcd->driver->pci_suspend) { if (hcd->driver->pci_suspend) {
retval = hcd->driver->pci_suspend(hcd, message); retval = hcd->driver->pci_suspend(hcd, PMSG_SUSPEND);
suspend_report_result(hcd->driver->pci_suspend, retval); suspend_report_result(hcd->driver->pci_suspend, retval);
if (retval) if (retval)
goto done; return retval;
} }
synchronize_irq(dev->irq); synchronize_irq(pci_dev->irq);
/* 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). We'll enter a
* low power state, if the hardware allows. * low power state during suspend_noirq, if the hardware allows.
*/ */
pci_disable_device(dev); pci_disable_device(pci_dev);
return retval;
}
static int hcd_pci_suspend_noirq(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct usb_hcd *hcd = pci_get_drvdata(pci_dev);
int retval;
pci_save_state(dev); retval = check_root_hub_suspended(dev);
if (retval)
return retval;
/* Don't fail on error to enable wakeup. We rely on pci code pci_save_state(pci_dev);
* to reject requests the hardware can't implement, rather
* than coding the same thing.
*/
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);
/* 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 the root hub is HALTed rather than SUSPENDed,
if (!has_pci_pm) { * disallow remote wakeup.
dev_dbg(&dev->dev, "--> PCI D0 legacy\n"); */
} else { if (hcd->state == HC_STATE_HALT)
device_set_wakeup_enable(dev, 0);
dev_dbg(dev, "wakeup: %d\n", device_may_wakeup(dev));
/* NOTE: dev->current_state becomes nonzero only here, and /* Possibly enable remote wakeup,
* only for devices that support PCI PM. Also, exiting * choose the appropriate low-power state, and go to that state.
* PCI_D3 (but not PCI_D1 or PCI_D2) is allowed to reset
* some device state (e.g. as part of clock reinit).
*/ */
retval = pci_set_power_state(dev, PCI_D3hot); retval = pci_prepare_to_sleep(pci_dev);
suspend_report_result(pci_set_power_state, retval); if (retval == -EIO) { /* Low-power not supported */
if (retval == 0) { dev_dbg(dev, "--> PCI D0 legacy\n");
dev_dbg(&dev->dev, "--> PCI D3\n"); retval = 0;
} else if (retval == 0) {
dev_dbg(dev, "--> PCI %s\n",
pci_power_name(pci_dev->current_state));
} else { } else {
dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n", suspend_report_result(pci_prepare_to_sleep, retval);
retval); return retval;
pci_restore_state(dev);
}
} }
#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;
of_node = pci_device_to_OF_node(dev); of_node = pci_device_to_OF_node(pci_dev);
if (of_node) if (of_node)
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);
/** static int hcd_pci_resume_noirq(struct device *dev)
* 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; struct pci_dev *pci_dev = to_pci_dev(dev);
int retval;
#ifdef CONFIG_PPC_PMAC #ifdef CONFIG_PPC_PMAC
/* Reenable ASIC clocks for USB */ /* Reenable ASIC clocks for USB */
if (machine_is(powermac)) { if (machine_is(powermac)) {
struct device_node *of_node; struct device_node *of_node;
of_node = pci_device_to_OF_node(dev); of_node = pci_device_to_OF_node(pci_dev);
if (of_node) if (of_node)
pmac_call_feature(PMAC_FTR_USB_ENABLE, pmac_call_feature(PMAC_FTR_USB_ENABLE,
of_node, 0, 1); of_node, 0, 1);
} }
#endif #endif
pci_restore_state(dev); /* Go back to D0 and disable remote wakeup */
pci_back_from_sleep(pci_dev);
return 0;
}
static int resume_common(struct device *dev, bool hibernated)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct usb_hcd *hcd = pci_get_drvdata(pci_dev);
int retval;
hcd = pci_get_drvdata(dev);
if (hcd->state != HC_STATE_SUSPENDED) { if (hcd->state != HC_STATE_SUSPENDED) {
dev_dbg(hcd->self.controller, dev_dbg(dev, "can't resume, not suspended!\n");
"can't resume, not suspended!\n");
return 0; return 0;
} }
pci_enable_wake(dev, PCI_D0, false); retval = pci_enable_device(pci_dev);
retval = pci_enable_device(dev);
if (retval < 0) { if (retval < 0) {
dev_err(&dev->dev, "can't re-enable after resume, %d!\n", dev_err(dev, "can't re-enable after resume, %d!\n", retval);
retval);
return retval; return retval;
} }
pci_set_master(dev); pci_set_master(pci_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);
if (hcd->driver->pci_resume) { if (hcd->driver->pci_resume) {
retval = hcd->driver->pci_resume(hcd); retval = hcd->driver->pci_resume(hcd);
if (retval) { if (retval) {
dev_err(hcd->self.controller, dev_err(dev, "PCI post-resume error %d!\n", retval);
"PCI post-resume error %d!\n", retval);
usb_hc_died(hcd); usb_hc_died(hcd);
} }
} }
return retval; return retval;
} }
EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
#endif /* CONFIG_PM */
/** static int hcd_pci_resume(struct device *dev)
* usb_hcd_pci_shutdown - shutdown host controller
* @dev: USB Host Controller being shutdown
*/
void usb_hcd_pci_shutdown(struct pci_dev *dev)
{ {
struct usb_hcd *hcd; return resume_common(dev, false);
}
hcd = pci_get_drvdata(dev);
if (!hcd)
return;
if (hcd->driver->shutdown) static int hcd_pci_restore(struct device *dev)
hcd->driver->shutdown(hcd); {
return resume_common(dev, true);
} }
EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown);
struct dev_pm_ops usb_hcd_pci_pm_ops = {
.suspend = hcd_pci_suspend,
.suspend_noirq = hcd_pci_suspend_noirq,
.resume_noirq = hcd_pci_resume_noirq,
.resume = hcd_pci_resume,
.freeze = check_root_hub_suspended,
.freeze_noirq = check_root_hub_suspended,
.thaw_noirq = NULL,
.thaw = NULL,
.poweroff = hcd_pci_suspend,
.poweroff_noirq = hcd_pci_suspend_noirq,
.restore_noirq = hcd_pci_resume_noirq,
.restore = hcd_pci_restore,
};
EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops);
#endif /* CONFIG_PM_SLEEP */
...@@ -261,14 +261,11 @@ struct pci_device_id; ...@@ -261,14 +261,11 @@ struct pci_device_id;
extern int usb_hcd_pci_probe(struct pci_dev *dev, extern int usb_hcd_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id); const struct pci_device_id *id);
extern void usb_hcd_pci_remove(struct pci_dev *dev); extern void usb_hcd_pci_remove(struct pci_dev *dev);
#ifdef CONFIG_PM
extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg);
extern int usb_hcd_pci_resume(struct pci_dev *dev);
#endif /* CONFIG_PM */
extern void usb_hcd_pci_shutdown(struct pci_dev *dev); extern void usb_hcd_pci_shutdown(struct pci_dev *dev);
#ifdef CONFIG_PM_SLEEP
extern struct dev_pm_ops usb_hcd_pci_pm_ops;
#endif
#endif /* CONFIG_PCI */ #endif /* CONFIG_PCI */
/* pci-ish (pdev null is ok) buffer alloc/mapping support */ /* pci-ish (pdev null is ok) buffer alloc/mapping support */
......
...@@ -429,10 +429,11 @@ static struct pci_driver ehci_pci_driver = { ...@@ -429,10 +429,11 @@ static struct pci_driver ehci_pci_driver = {
.probe = usb_hcd_pci_probe, .probe = usb_hcd_pci_probe,
.remove = usb_hcd_pci_remove, .remove = usb_hcd_pci_remove,
.shutdown = usb_hcd_pci_shutdown,
#ifdef CONFIG_PM #ifdef CONFIG_PM_SLEEP
.suspend = usb_hcd_pci_suspend, .driver = {
.resume = usb_hcd_pci_resume, .pm = &usb_hcd_pci_pm_ops
},
#endif #endif
.shutdown = usb_hcd_pci_shutdown,
}; };
...@@ -484,12 +484,11 @@ static struct pci_driver ohci_pci_driver = { ...@@ -484,12 +484,11 @@ static struct pci_driver ohci_pci_driver = {
.probe = usb_hcd_pci_probe, .probe = usb_hcd_pci_probe,
.remove = usb_hcd_pci_remove, .remove = usb_hcd_pci_remove,
.shutdown = usb_hcd_pci_shutdown,
#ifdef CONFIG_PM #ifdef CONFIG_PM_SLEEP
.suspend = usb_hcd_pci_suspend, .driver = {
.resume = usb_hcd_pci_resume, .pm = &usb_hcd_pci_pm_ops
},
#endif #endif
.shutdown = usb_hcd_pci_shutdown,
}; };
...@@ -940,10 +940,11 @@ static struct pci_driver uhci_pci_driver = { ...@@ -940,10 +940,11 @@ static struct pci_driver uhci_pci_driver = {
.remove = usb_hcd_pci_remove, .remove = usb_hcd_pci_remove,
.shutdown = uhci_shutdown, .shutdown = uhci_shutdown,
#ifdef CONFIG_PM #ifdef CONFIG_PM_SLEEP
.suspend = usb_hcd_pci_suspend, .driver = {
.resume = usb_hcd_pci_resume, .pm = &usb_hcd_pci_pm_ops
#endif /* PM */ },
#endif
}; };
static int __init uhci_hcd_init(void) static int __init uhci_hcd_init(void)
......
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