summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/pci/pci-driver.c109
-rw-r--r--drivers/pci/pci.c2
-rw-r--r--drivers/pci/pci.h1
3 files changed, 74 insertions, 38 deletions
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index bfaa77d8853..750ee79c178 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -316,21 +316,10 @@ static void pci_device_shutdown(struct device *dev)
/*
* Default "suspend" method for devices that have no driver provided suspend,
- * or not even a driver at all (first part).
- */
-static void pci_default_pm_suspend_early(struct pci_dev *pci_dev)
-{
- /* If device is enabled at this point, disable it */
- pci_disable_enabled_device(pci_dev);
-}
-
-/*
- * Default "suspend" method for devices that have no driver provided suspend,
* or not even a driver at all (second part).
*/
static void pci_default_pm_suspend_late(struct pci_dev *pci_dev)
{
- pci_save_state(pci_dev);
/*
* mark its power state as "unknown", since we don't know if
* e.g. the BIOS will change its device state when we suspend.
@@ -341,16 +330,6 @@ static void pci_default_pm_suspend_late(struct pci_dev *pci_dev)
/*
* Default "resume" method for devices that have no driver provided resume,
- * or not even a driver at all (first part).
- */
-static void pci_default_pm_resume_early(struct pci_dev *pci_dev)
-{
- /* restore the PCI config space */
- pci_restore_state(pci_dev);
-}
-
-/*
- * Default "resume" method for devices that have no driver provided resume,
* or not even a driver at all (second part).
*/
static int pci_default_pm_resume_late(struct pci_dev *pci_dev)
@@ -379,9 +358,10 @@ static int pci_legacy_suspend(struct device *dev, pm_message_t state)
i = drv->suspend(pci_dev, state);
suspend_report_result(drv->suspend, i);
} else {
+ pci_save_state(pci_dev);
/*
- * For compatibility with existing code with legacy PM support
- * don't call pci_default_pm_suspend_early() here.
+ * This is for compatibility with existing code with legacy PM
+ * support.
*/
pci_default_pm_suspend_late(pci_dev);
}
@@ -410,7 +390,8 @@ static int pci_legacy_resume(struct device *dev)
if (drv && drv->resume) {
error = drv->resume(pci_dev);
} else {
- pci_default_pm_resume_early(pci_dev);
+ /* restore the PCI config space */
+ pci_restore_state(pci_dev);
error = pci_default_pm_resume_late(pci_dev);
}
return error;
@@ -429,22 +410,72 @@ static int pci_legacy_resume_early(struct device *dev)
/* Auxiliary functions used by the new power management framework */
+static int pci_restore_standard_config(struct pci_dev *pci_dev)
+{
+ struct pci_dev *parent = pci_dev->bus->self;
+ int error = 0;
+
+ /* Check if the device's bus is operational */
+ if (!parent || parent->current_state == PCI_D0) {
+ pci_restore_state(pci_dev);
+ pci_update_current_state(pci_dev, PCI_D0);
+ } else {
+ dev_warn(&pci_dev->dev, "unable to restore config, "
+ "bridge %s in low power state D%d\n", pci_name(parent),
+ parent->current_state);
+ pci_dev->current_state = PCI_UNKNOWN;
+ error = -EAGAIN;
+ }
+
+ return error;
+}
+
static bool pci_is_bridge(struct pci_dev *pci_dev)
{
return !!(pci_dev->subordinate);
}
+static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev)
+{
+ if (pci_restore_standard_config(pci_dev))
+ pci_fixup_device(pci_fixup_resume_early, pci_dev);
+}
+
static int pci_pm_default_resume(struct pci_dev *pci_dev)
{
+ /*
+ * pci_restore_standard_config() should have been called once already,
+ * but it would have failed if the device's parent bridge had not been
+ * in power state D0 at that time. Check it and try again if necessary.
+ */
+ if (pci_dev->current_state == PCI_UNKNOWN) {
+ int error = pci_restore_standard_config(pci_dev);
+ if (error)
+ return error;
+ }
+
+ pci_fixup_device(pci_fixup_resume, pci_dev);
+
if (!pci_is_bridge(pci_dev))
pci_enable_wake(pci_dev, PCI_D0, false);
return pci_default_pm_resume_late(pci_dev);
}
+static void pci_pm_default_suspend_generic(struct pci_dev *pci_dev)
+{
+ /* If device is enabled at this point, disable it */
+ pci_disable_enabled_device(pci_dev);
+ /*
+ * Save state with interrupts enabled, because in principle the bus the
+ * device is on may be put into a low power state after this code runs.
+ */
+ pci_save_state(pci_dev);
+}
+
static void pci_pm_default_suspend(struct pci_dev *pci_dev)
{
- pci_default_pm_suspend_early(pci_dev);
+ pci_pm_default_suspend_generic(pci_dev);
if (!pci_is_bridge(pci_dev))
pci_prepare_to_sleep(pci_dev);
@@ -529,12 +560,13 @@ static int pci_pm_resume(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
- pci_fixup_device(pci_fixup_resume, pci_dev);
-
if (drv && drv->pm) {
+ pci_fixup_device(pci_fixup_resume, pci_dev);
+
if (drv->pm->resume)
error = drv->pm->resume(dev);
} else if (pci_has_legacy_pm_support(pci_dev)) {
+ pci_fixup_device(pci_fixup_resume, pci_dev);
error = pci_legacy_resume(dev);
} else {
error = pci_pm_default_resume(pci_dev);
@@ -549,15 +581,16 @@ static int pci_pm_resume_noirq(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
- pci_fixup_device(pci_fixup_resume_early, to_pci_dev(dev));
-
if (drv && drv->pm) {
+ pci_fixup_device(pci_fixup_resume_early, pci_dev);
+
if (drv->pm->resume_noirq)
error = drv->pm->resume_noirq(dev);
} else if (pci_has_legacy_pm_support(pci_dev)) {
+ pci_fixup_device(pci_fixup_resume_early, pci_dev);
error = pci_legacy_resume_early(dev);
} else {
- pci_default_pm_resume_early(pci_dev);
+ pci_pm_default_resume_noirq(pci_dev);
}
return error;
@@ -589,7 +622,7 @@ static int pci_pm_freeze(struct device *dev)
error = pci_legacy_suspend(dev, PMSG_FREEZE);
pci_fixup_device(pci_fixup_suspend, pci_dev);
} else {
- pci_default_pm_suspend_early(pci_dev);
+ pci_pm_default_suspend_generic(pci_dev);
}
return error;
@@ -647,7 +680,7 @@ static int pci_pm_thaw_noirq(struct device *dev)
pci_fixup_device(pci_fixup_resume_early, to_pci_dev(dev));
error = pci_legacy_resume_early(dev);
} else {
- pci_default_pm_resume_early(pci_dev);
+ pci_update_current_state(pci_dev, PCI_D0);
}
return error;
@@ -698,12 +731,13 @@ static int pci_pm_restore(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
- pci_fixup_device(pci_fixup_resume, pci_dev);
-
if (drv && drv->pm) {
+ pci_fixup_device(pci_fixup_resume, pci_dev);
+
if (drv->pm->restore)
error = drv->pm->restore(dev);
} else if (pci_has_legacy_pm_support(pci_dev)) {
+ pci_fixup_device(pci_fixup_resume, pci_dev);
error = pci_legacy_resume(dev);
} else {
error = pci_pm_default_resume(pci_dev);
@@ -718,15 +752,16 @@ static int pci_pm_restore_noirq(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
- pci_fixup_device(pci_fixup_resume_early, pci_dev);
-
if (drv && drv->pm) {
+ pci_fixup_device(pci_fixup_resume_early, pci_dev);
+
if (drv->pm->restore_noirq)
error = drv->pm->restore_noirq(dev);
} else if (pci_has_legacy_pm_support(pci_dev)) {
+ pci_fixup_device(pci_fixup_resume_early, pci_dev);
error = pci_legacy_resume_early(dev);
} else {
- pci_default_pm_resume_early(pci_dev);
+ pci_pm_default_resume_noirq(pci_dev);
}
return error;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 6e309c8b47d..e491fdedf70 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -527,7 +527,7 @@ pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state)
* @dev: PCI device to handle.
* @state: State to cache in case the device doesn't have the PM capability
*/
-static void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
+void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
{
if (dev->pm_cap) {
u16 pmcsr;
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 881dc15f8ef..1351bb4addd 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -44,6 +44,7 @@ struct pci_platform_pm_ops {
};
extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
+extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
extern void pci_disable_enabled_device(struct pci_dev *dev);
extern void pci_pm_init(struct pci_dev *dev);
extern void platform_pci_wakeup_init(struct pci_dev *dev);