|
|
|
@ -185,180 +185,6 @@ void usb_hcd_pci_remove(struct pci_dev *dev)
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
|
|
|
|
|
* @dev: USB Host Controller being suspended
|
|
|
|
|
* @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)
|
|
|
|
|
{
|
|
|
|
|
struct usb_hcd *hcd = pci_get_drvdata(dev);
|
|
|
|
|
int retval = 0;
|
|
|
|
|
int wake, w;
|
|
|
|
|
int has_pci_pm;
|
|
|
|
|
|
|
|
|
|
/* Root hub suspend should have stopped all downstream traffic,
|
|
|
|
|
* and all bus master traffic. And done so for both the interface
|
|
|
|
|
* and the stub usb_device (which we check here). But maybe it
|
|
|
|
|
* 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 ||
|
|
|
|
|
hcd->state == HC_STATE_HALT)) {
|
|
|
|
|
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) {
|
|
|
|
|
retval = hcd->driver->pci_suspend(hcd, message);
|
|
|
|
|
suspend_report_result(hcd->driver->pci_suspend, retval);
|
|
|
|
|
if (retval)
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
synchronize_irq(dev->irq);
|
|
|
|
|
|
|
|
|
|
/* Downstream ports from this root hub should already be quiesced, so
|
|
|
|
|
* there will be no DMA activity. Now we can shut down the upstream
|
|
|
|
|
* link (except maybe for PME# resume signaling) and enter some PCI
|
|
|
|
|
* low power state, if the hardware allows.
|
|
|
|
|
*/
|
|
|
|
|
pci_disable_device(dev);
|
|
|
|
|
|
|
|
|
|
pci_save_state(dev);
|
|
|
|
|
|
|
|
|
|
/* Don't fail on error to enable wakeup. We rely on pci code
|
|
|
|
|
* 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 (!has_pci_pm) {
|
|
|
|
|
dev_dbg(&dev->dev, "--> PCI D0 legacy\n");
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
/* NOTE: dev->current_state becomes nonzero only here, and
|
|
|
|
|
* only for devices that support PCI PM. Also, exiting
|
|
|
|
|
* 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);
|
|
|
|
|
suspend_report_result(pci_set_power_state, retval);
|
|
|
|
|
if (retval == 0) {
|
|
|
|
|
dev_dbg(&dev->dev, "--> PCI D3\n");
|
|
|
|
|
} else {
|
|
|
|
|
dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",
|
|
|
|
|
retval);
|
|
|
|
|
pci_restore_state(dev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_PMAC
|
|
|
|
|
if (retval == 0) {
|
|
|
|
|
/* Disable ASIC clocks for USB */
|
|
|
|
|
if (machine_is(powermac)) {
|
|
|
|
|
struct device_node *of_node;
|
|
|
|
|
|
|
|
|
|
of_node = pci_device_to_OF_node(dev);
|
|
|
|
|
if (of_node)
|
|
|
|
|
pmac_call_feature(PMAC_FTR_USB_ENABLE,
|
|
|
|
|
of_node, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_PMAC
|
|
|
|
|
/* Reenable ASIC clocks for USB */
|
|
|
|
|
if (machine_is(powermac)) {
|
|
|
|
|
struct device_node *of_node;
|
|
|
|
|
|
|
|
|
|
of_node = pci_device_to_OF_node(dev);
|
|
|
|
|
if (of_node)
|
|
|
|
|
pmac_call_feature(PMAC_FTR_USB_ENABLE,
|
|
|
|
|
of_node, 0, 1);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
pci_restore_state(dev);
|
|
|
|
|
|
|
|
|
|
hcd = pci_get_drvdata(dev);
|
|
|
|
|
if (hcd->state != HC_STATE_SUSPENDED) {
|
|
|
|
|
dev_dbg(hcd->self.controller,
|
|
|
|
|
"can't resume, not suspended!\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pci_enable_wake(dev, PCI_D0, false);
|
|
|
|
|
|
|
|
|
|
retval = pci_enable_device(dev);
|
|
|
|
|
if (retval < 0) {
|
|
|
|
|
dev_err(&dev->dev, "can't re-enable after resume, %d!\n",
|
|
|
|
|
retval);
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pci_set_master(dev);
|
|
|
|
|
|
|
|
|
|
/* yes, ignore this result too... */
|
|
|
|
|
(void) pci_wake_from_d3(dev, 0);
|
|
|
|
|
|
|
|
|
|
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
|
|
|
|
|
|
|
|
|
|
if (hcd->driver->pci_resume) {
|
|
|
|
|
retval = hcd->driver->pci_resume(hcd);
|
|
|
|
|
if (retval) {
|
|
|
|
|
dev_err(hcd->self.controller,
|
|
|
|
|
"PCI post-resume error %d!\n", retval);
|
|
|
|
|
usb_hc_died(hcd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
|
|
|
|
|
|
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* usb_hcd_pci_shutdown - shutdown host controller
|
|
|
|
|
* @dev: USB Host Controller being shutdown
|
|
|
|
@ -376,3 +202,181 @@ void usb_hcd_pci_shutdown(struct pci_dev *dev)
|
|
|
|
|
}
|
|
|
|
|
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 pci_dev *pci_dev = to_pci_dev(dev);
|
|
|
|
|
struct usb_hcd *hcd = pci_get_drvdata(pci_dev);
|
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
|
|
/* Root hub suspend should have stopped all downstream traffic,
|
|
|
|
|
* and all bus master traffic. And done so for both the interface
|
|
|
|
|
* and the stub usb_device (which we check here). But maybe it
|
|
|
|
|
* didn't; writing sysfs power/state files ignores such rules...
|
|
|
|
|
*/
|
|
|
|
|
retval = check_root_hub_suspended(dev);
|
|
|
|
|
if (retval)
|
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
|
|
/* We might already be suspended (runtime PM -- not yet written) */
|
|
|
|
|
if (pci_dev->current_state != PCI_D0)
|
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
|
|
if (hcd->driver->pci_suspend) {
|
|
|
|
|
retval = hcd->driver->pci_suspend(hcd, PMSG_SUSPEND);
|
|
|
|
|
suspend_report_result(hcd->driver->pci_suspend, retval);
|
|
|
|
|
if (retval)
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
synchronize_irq(pci_dev->irq);
|
|
|
|
|
|
|
|
|
|
/* Downstream ports from this root hub should already be quiesced, so
|
|
|
|
|
* there will be no DMA activity. Now we can shut down the upstream
|
|
|
|
|
* link (except maybe for PME# resume signaling). We'll enter a
|
|
|
|
|
* low power state during suspend_noirq, if the hardware allows.
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
retval = check_root_hub_suspended(dev);
|
|
|
|
|
if (retval)
|
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
|
|
pci_save_state(pci_dev);
|
|
|
|
|
|
|
|
|
|
/* If the root hub is HALTed rather than SUSPENDed,
|
|
|
|
|
* disallow remote wakeup.
|
|
|
|
|
*/
|
|
|
|
|
if (hcd->state == HC_STATE_HALT)
|
|
|
|
|
device_set_wakeup_enable(dev, 0);
|
|
|
|
|
dev_dbg(dev, "wakeup: %d\n", device_may_wakeup(dev));
|
|
|
|
|
|
|
|
|
|
/* Possibly enable remote wakeup,
|
|
|
|
|
* choose the appropriate low-power state, and go to that state.
|
|
|
|
|
*/
|
|
|
|
|
retval = pci_prepare_to_sleep(pci_dev);
|
|
|
|
|
if (retval == -EIO) { /* Low-power not supported */
|
|
|
|
|
dev_dbg(dev, "--> PCI D0 legacy\n");
|
|
|
|
|
retval = 0;
|
|
|
|
|
} else if (retval == 0) {
|
|
|
|
|
dev_dbg(dev, "--> PCI %s\n",
|
|
|
|
|
pci_power_name(pci_dev->current_state));
|
|
|
|
|
} else {
|
|
|
|
|
suspend_report_result(pci_prepare_to_sleep, retval);
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_PMAC
|
|
|
|
|
/* Disable ASIC clocks for USB */
|
|
|
|
|
if (machine_is(powermac)) {
|
|
|
|
|
struct device_node *of_node;
|
|
|
|
|
|
|
|
|
|
of_node = pci_device_to_OF_node(pci_dev);
|
|
|
|
|
if (of_node)
|
|
|
|
|
pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int hcd_pci_resume_noirq(struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
struct pci_dev *pci_dev = to_pci_dev(dev);
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_PMAC
|
|
|
|
|
/* Reenable ASIC clocks for USB */
|
|
|
|
|
if (machine_is(powermac)) {
|
|
|
|
|
struct device_node *of_node;
|
|
|
|
|
|
|
|
|
|
of_node = pci_device_to_OF_node(pci_dev);
|
|
|
|
|
if (of_node)
|
|
|
|
|
pmac_call_feature(PMAC_FTR_USB_ENABLE,
|
|
|
|
|
of_node, 0, 1);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
if (hcd->state != HC_STATE_SUSPENDED) {
|
|
|
|
|
dev_dbg(dev, "can't resume, not suspended!\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retval = pci_enable_device(pci_dev);
|
|
|
|
|
if (retval < 0) {
|
|
|
|
|
dev_err(dev, "can't re-enable after resume, %d!\n", retval);
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pci_set_master(pci_dev);
|
|
|
|
|
|
|
|
|
|
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
|
|
|
|
|
|
|
|
|
|
if (hcd->driver->pci_resume) {
|
|
|
|
|
retval = hcd->driver->pci_resume(hcd);
|
|
|
|
|
if (retval) {
|
|
|
|
|
dev_err(dev, "PCI post-resume error %d!\n", retval);
|
|
|
|
|
usb_hc_died(hcd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int hcd_pci_resume(struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
return resume_common(dev, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int hcd_pci_restore(struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
return resume_common(dev, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|