summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/pci/pci-driver.c160
-rw-r--r--drivers/pci/pci.c43
-rw-r--r--drivers/pci/pci.h1
-rw-r--r--include/linux/pci.h9
-rw-r--r--kernel/power/Kconfig5
5 files changed, 184 insertions, 34 deletions
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index e5d47be3c6d7..f9a0aec3abcf 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/cpu.h>
+#include <linux/pm_runtime.h>
#include "pci.h"
struct pci_dynid {
@@ -404,6 +405,35 @@ static void pci_device_shutdown(struct device *dev)
pci_msix_shutdown(pci_dev);
}
+#ifdef CONFIG_PM_OPS
+
+/* Auxiliary functions used for system resume and run-time resume. */
+
+/**
+ * pci_restore_standard_config - restore standard config registers of PCI device
+ * @pci_dev: PCI device to handle
+ */
+static int pci_restore_standard_config(struct pci_dev *pci_dev)
+{
+ pci_update_current_state(pci_dev, PCI_UNKNOWN);
+
+ if (pci_dev->current_state != PCI_D0) {
+ int error = pci_set_power_state(pci_dev, PCI_D0);
+ if (error)
+ return error;
+ }
+
+ return pci_restore_state(pci_dev);
+}
+
+static void pci_pm_default_resume_early(struct pci_dev *pci_dev)
+{
+ pci_restore_standard_config(pci_dev);
+ pci_fixup_device(pci_fixup_resume_early, pci_dev);
+}
+
+#endif
+
#ifdef CONFIG_PM_SLEEP
/*
@@ -520,29 +550,6 @@ static int pci_legacy_resume(struct device *dev)
/* Auxiliary functions used by the new power management framework */
-/**
- * pci_restore_standard_config - restore standard config registers of PCI device
- * @pci_dev: PCI device to handle
- */
-static int pci_restore_standard_config(struct pci_dev *pci_dev)
-{
- pci_update_current_state(pci_dev, PCI_UNKNOWN);
-
- if (pci_dev->current_state != PCI_D0) {
- int error = pci_set_power_state(pci_dev, PCI_D0);
- if (error)
- return error;
- }
-
- return pci_restore_state(pci_dev);
-}
-
-static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev)
-{
- pci_restore_standard_config(pci_dev);
- pci_fixup_device(pci_fixup_resume_early, pci_dev);
-}
-
static void pci_pm_default_resume(struct pci_dev *pci_dev)
{
pci_fixup_device(pci_fixup_resume, pci_dev);
@@ -581,6 +588,17 @@ static int pci_pm_prepare(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
+ /*
+ * PCI devices suspended at run time need to be resumed at this
+ * point, because in general it is necessary to reconfigure them for
+ * system suspend. Namely, if the device is supposed to wake up the
+ * system from the sleep state, we may need to reconfigure it for this
+ * purpose. In turn, if the device is not supposed to wake up the
+ * system from the sleep state, we'll have to prevent it from signaling
+ * wake-up.
+ */
+ pm_runtime_resume(dev);
+
if (drv && drv->pm && drv->pm->prepare)
error = drv->pm->prepare(dev);
@@ -595,6 +613,13 @@ static void pci_pm_complete(struct device *dev)
drv->pm->complete(dev);
}
+#else /* !CONFIG_PM_SLEEP */
+
+#define pci_pm_prepare NULL
+#define pci_pm_complete NULL
+
+#endif /* !CONFIG_PM_SLEEP */
+
#ifdef CONFIG_SUSPEND
static int pci_pm_suspend(struct device *dev)
@@ -681,7 +706,7 @@ static int pci_pm_resume_noirq(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
- pci_pm_default_resume_noirq(pci_dev);
+ pci_pm_default_resume_early(pci_dev);
if (pci_has_legacy_pm_support(pci_dev))
return pci_legacy_resume_early(dev);
@@ -879,7 +904,7 @@ static int pci_pm_restore_noirq(struct device *dev)
struct device_driver *drv = dev->driver;
int error = 0;
- pci_pm_default_resume_noirq(pci_dev);
+ pci_pm_default_resume_early(pci_dev);
if (pci_has_legacy_pm_support(pci_dev))
return pci_legacy_resume_early(dev);
@@ -931,6 +956,84 @@ static int pci_pm_restore(struct device *dev)
#endif /* !CONFIG_HIBERNATION */
+#ifdef CONFIG_PM_RUNTIME
+
+static int pci_pm_runtime_suspend(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+ pci_power_t prev = pci_dev->current_state;
+ int error;
+
+ if (!pm || !pm->runtime_suspend)
+ return -ENOSYS;
+
+ error = pm->runtime_suspend(dev);
+ suspend_report_result(pm->runtime_suspend, error);
+ if (error)
+ return error;
+
+ pci_fixup_device(pci_fixup_suspend, pci_dev);
+
+ if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
+ && pci_dev->current_state != PCI_UNKNOWN) {
+ WARN_ONCE(pci_dev->current_state != prev,
+ "PCI PM: State of device not saved by %pF\n",
+ pm->runtime_suspend);
+ return 0;
+ }
+
+ if (!pci_dev->state_saved)
+ pci_save_state(pci_dev);
+
+ pci_finish_runtime_suspend(pci_dev);
+
+ return 0;
+}
+
+static int pci_pm_runtime_resume(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (!pm || !pm->runtime_resume)
+ return -ENOSYS;
+
+ pci_pm_default_resume_early(pci_dev);
+ __pci_enable_wake(pci_dev, PCI_D0, true, false);
+ pci_fixup_device(pci_fixup_resume, pci_dev);
+
+ return pm->runtime_resume(dev);
+}
+
+static int pci_pm_runtime_idle(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (!pm)
+ return -ENOSYS;
+
+ if (pm->runtime_idle) {
+ int ret = pm->runtime_idle(dev);
+ if (ret)
+ return ret;
+ }
+
+ pm_runtime_suspend(dev);
+
+ return 0;
+}
+
+#else /* !CONFIG_PM_RUNTIME */
+
+#define pci_pm_runtime_suspend NULL
+#define pci_pm_runtime_resume NULL
+#define pci_pm_runtime_idle NULL
+
+#endif /* !CONFIG_PM_RUNTIME */
+
+#ifdef CONFIG_PM_OPS
+
const struct dev_pm_ops pci_dev_pm_ops = {
.prepare = pci_pm_prepare,
.complete = pci_pm_complete,
@@ -946,15 +1049,18 @@ const struct dev_pm_ops pci_dev_pm_ops = {
.thaw_noirq = pci_pm_thaw_noirq,
.poweroff_noirq = pci_pm_poweroff_noirq,
.restore_noirq = pci_pm_restore_noirq,
+ .runtime_suspend = pci_pm_runtime_suspend,
+ .runtime_resume = pci_pm_runtime_resume,
+ .runtime_idle = pci_pm_runtime_idle,
};
#define PCI_PM_OPS_PTR (&pci_dev_pm_ops)
-#else /* !CONFIG_PM_SLEEP */
+#else /* !COMFIG_PM_OPS */
#define PCI_PM_OPS_PTR NULL
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !COMFIG_PM_OPS */
/**
* __pci_register_driver - register a new pci driver
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index df55a2f351b3..d62a5de81672 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1302,9 +1302,10 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
}
/**
- * pci_enable_wake - enable PCI device as wakeup event source
+ * __pci_enable_wake - enable PCI device as wakeup event source
* @dev: PCI device affected
* @state: PCI state from which device will issue wakeup events
+ * @runtime: True if the events are to be generated at run time
* @enable: True to enable event generation; false to disable
*
* This enables the device as a wakeup event source, or disables it.
@@ -1320,11 +1321,12 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
* Error code depending on the platform is returned if both the platform and
* the native mechanism fail to enable the generation of wake-up events
*/
-int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable)
+int __pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+ bool runtime, bool enable)
{
int ret = 0;
- if (enable && !device_may_wakeup(&dev->dev))
+ if (enable && !runtime && !device_may_wakeup(&dev->dev))
return -EINVAL;
/* Don't do the same thing twice in a row for one device. */
@@ -1344,19 +1346,24 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable)
pci_pme_active(dev, true);
else
ret = 1;
- error = platform_pci_sleep_wake(dev, true);
+ error = runtime ? platform_pci_run_wake(dev, true) :
+ platform_pci_sleep_wake(dev, true);
if (ret)
ret = error;
if (!ret)
dev->wakeup_prepared = true;
} else {
- platform_pci_sleep_wake(dev, false);
+ if (runtime)
+ platform_pci_run_wake(dev, false);
+ else
+ platform_pci_sleep_wake(dev, false);
pci_pme_active(dev, false);
dev->wakeup_prepared = false;
}
return ret;
}
+EXPORT_SYMBOL(__pci_enable_wake);
/**
* pci_wake_from_d3 - enable/disable device to wake up from D3_hot or D3_cold
@@ -1466,6 +1473,31 @@ int pci_back_from_sleep(struct pci_dev *dev)
}
/**
+ * pci_finish_runtime_suspend - Carry out PCI-specific part of runtime suspend.
+ * @dev: PCI device being suspended.
+ *
+ * Prepare @dev to generate wake-up events at run time and put it into a low
+ * power state.
+ */
+int pci_finish_runtime_suspend(struct pci_dev *dev)
+{
+ pci_power_t target_state = pci_target_state(dev);
+ int error;
+
+ if (target_state == PCI_POWER_ERROR)
+ return -EIO;
+
+ __pci_enable_wake(dev, target_state, true, pci_dev_run_wake(dev));
+
+ error = pci_set_power_state(dev, target_state);
+
+ if (error)
+ __pci_enable_wake(dev, target_state, true, false);
+
+ return error;
+}
+
+/**
* pci_dev_run_wake - Check if device can generate run-time wake-up events.
* @dev: Device to check.
*
@@ -2978,7 +3010,6 @@ EXPORT_SYMBOL(pci_save_state);
EXPORT_SYMBOL(pci_restore_state);
EXPORT_SYMBOL(pci_pme_capable);
EXPORT_SYMBOL(pci_pme_active);
-EXPORT_SYMBOL(pci_enable_wake);
EXPORT_SYMBOL(pci_wake_from_d3);
EXPORT_SYMBOL(pci_target_state);
EXPORT_SYMBOL(pci_prepare_to_sleep);
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 286c50821949..4eb10f48d270 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -55,6 +55,7 @@ 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 bool pci_check_pme_status(struct pci_dev *dev);
+extern int pci_finish_runtime_suspend(struct pci_dev *dev);
extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
extern void pci_pme_wakeup_bus(struct pci_bus *bus);
extern void pci_pm_init(struct pci_dev *dev);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 3f787ce78bd1..1a29f9f6b2dc 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -777,13 +777,20 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);
bool pci_pme_capable(struct pci_dev *dev, pci_power_t state);
void pci_pme_active(struct pci_dev *dev, bool enable);
-int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable);
+int __pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+ bool runtime, bool enable);
int pci_wake_from_d3(struct pci_dev *dev, bool enable);
pci_power_t pci_target_state(struct pci_dev *dev);
int pci_prepare_to_sleep(struct pci_dev *dev);
int pci_back_from_sleep(struct pci_dev *dev);
bool pci_dev_run_wake(struct pci_dev *dev);
+static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+ bool enable)
+{
+ return __pci_enable_wake(dev, state, false, enable);
+}
+
/* For use by arch with custom probe code */
void set_pcie_port_type(struct pci_dev *pdev);
void set_pcie_hotplug_bridge(struct pci_dev *pdev);
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 91e09d3b2eb2..4c9cffcf69c7 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -222,3 +222,8 @@ config PM_RUNTIME
and the bus type drivers of the buses the devices are on are
responsible for the actual handling of the autosuspend requests and
wake-up events.
+
+config PM_OPS
+ bool
+ depends on PM_SLEEP || PM_RUNTIME
+ default y