|
@@ -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
|