aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/pci/pci.c83
-rw-r--r--include/linux/pci.h2
2 files changed, 85 insertions, 0 deletions
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index a6b1b6f96ab..1b0ec45e993 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1147,6 +1147,87 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
}
/**
+ * pci_prepare_to_sleep - prepare PCI device for system-wide transition into
+ * a sleep state
+ * @dev: Device to handle.
+ *
+ * Choose the power state appropriate for the device depending on whether
+ * it can wake up the system and/or is power manageable by the platform
+ * (PCI_D3hot is the default) and put the device into that state.
+ */
+int pci_prepare_to_sleep(struct pci_dev *dev)
+{
+ pci_power_t target_state = PCI_D3hot;
+ int pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ int error;
+
+ if (platform_pci_power_manageable(dev)) {
+ /*
+ * Call the platform to choose the target state of the device
+ * and enable wake-up from this state if supported.
+ */
+ pci_power_t state = platform_pci_choose_state(dev);
+
+ switch (state) {
+ case PCI_POWER_ERROR:
+ case PCI_UNKNOWN:
+ break;
+ case PCI_D1:
+ case PCI_D2:
+ if (pci_no_d1d2(dev))
+ break;
+ default:
+ target_state = state;
+ pci_enable_wake(dev, target_state, true);
+ }
+ } else if (device_may_wakeup(&dev->dev)) {
+ /*
+ * Find the deepest state from which the device can generate
+ * wake-up events, make it the target state and enable device
+ * to generate PME#.
+ */
+ u16 pmc;
+
+ if (!pm)
+ return -EIO;
+
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+ if (pmc & PCI_PM_CAP_PME_MASK) {
+ if (!(pmc & PCI_PM_CAP_PME_D3)) {
+ /* Device cannot generate PME# from D3_hot */
+ if (pmc & PCI_PM_CAP_PME_D2)
+ target_state = PCI_D2;
+ else if (pmc & PCI_PM_CAP_PME_D1)
+ target_state = PCI_D1;
+ else
+ target_state = PCI_D0;
+ }
+ pci_pme_active(dev, pm, true);
+ }
+ }
+
+ error = pci_set_power_state(dev, target_state);
+
+ if (error)
+ pci_enable_wake(dev, target_state, false);
+
+ return error;
+}
+
+/**
+ * pci_back_from_sleep - turn PCI device on during system-wide transition into
+ * the working state a sleep state
+ * @dev: Device to handle.
+ *
+ * Disable device's sytem wake-up capability and put it into D0.
+ */
+int pci_back_from_sleep(struct pci_dev *dev)
+{
+ pci_enable_wake(dev, PCI_D0, false);
+ return pci_set_power_state(dev, PCI_D0);
+}
+
+/**
* pci_pm_init - Initialize PM functions of given PCI device
* @dev: PCI device to handle.
*/
@@ -1853,5 +1934,7 @@ EXPORT_SYMBOL(pci_set_power_state);
EXPORT_SYMBOL(pci_save_state);
EXPORT_SYMBOL(pci_restore_state);
EXPORT_SYMBOL(pci_enable_wake);
+EXPORT_SYMBOL(pci_prepare_to_sleep);
+EXPORT_SYMBOL(pci_back_from_sleep);
EXPORT_SYMBOL_GPL(pci_set_pcie_reset_state);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 4c80dc3f299..52ac06dcce9 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -632,6 +632,8 @@ int pci_restore_state(struct pci_dev *dev);
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);
int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable);
+int pci_prepare_to_sleep(struct pci_dev *dev);
+int pci_back_from_sleep(struct pci_dev *dev);
/* Functions for PCI Hotplug drivers to use */
int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);