PCI: Disable PTM during suspend to save power
authorDavid E. Box <david.e.box@linux.intel.com>
Mon, 7 Dec 2020 22:39:51 +0000 (14:39 -0800)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 10 Dec 2020 20:45:14 +0000 (14:45 -0600)
There are systems (for example, Intel based mobile platforms since Coffee
Lake) where the power drawn while suspended can be significantly reduced by
disabling Precision Time Measurement (PTM) on PCIe root ports as this
allows the port to enter a lower-power PM state and the SoC to reach a
lower-power idle state. To save this power, disable the PTM feature on root
ports during pci_prepare_to_sleep() and pci_finish_runtime_suspend().  The
feature will be returned to its previous state during restore and error
recovery.

Suggested-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=209361
Link: https://lore.kernel.org/r/20201207223951.19667-2-david.e.box@linux.intel.com
Reported-by: Len Brown <len.brown@intel.com>
Signed-off-by: David E. Box <david.e.box@linux.intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/pci.c
drivers/pci/pci.h
drivers/pci/pcie/ptm.c

index 12ba6351c05b406e247321e40594b31decf8e9a4..71dd5d7cbdeda1efac19ba2567fe72a153022a70 100644 (file)
@@ -2608,12 +2608,24 @@ int pci_prepare_to_sleep(struct pci_dev *dev)
        if (target_state == PCI_POWER_ERROR)
                return -EIO;
 
+       /*
+        * There are systems (for example, Intel mobile chips since Coffee
+        * Lake) where the power drawn while suspended can be significantly
+        * reduced by disabling PTM on PCIe root ports as this allows the
+        * port to enter a lower-power PM state and the SoC to reach a
+        * lower-power idle state as a whole.
+        */
+       if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT)
+               pci_disable_ptm(dev);
+
        pci_enable_wake(dev, target_state, wakeup);
 
        error = pci_set_power_state(dev, target_state);
 
-       if (error)
+       if (error) {
                pci_enable_wake(dev, target_state, false);
+               pci_restore_ptm_state(dev);
+       }
 
        return error;
 }
@@ -2651,12 +2663,23 @@ int pci_finish_runtime_suspend(struct pci_dev *dev)
 
        dev->runtime_d3cold = target_state == PCI_D3cold;
 
+       /*
+        * There are systems (for example, Intel mobile chips since Coffee
+        * Lake) where the power drawn while suspended can be significantly
+        * reduced by disabling PTM on PCIe root ports as this allows the
+        * port to enter a lower-power PM state and the SoC to reach a
+        * lower-power idle state as a whole.
+        */
+       if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT)
+               pci_disable_ptm(dev);
+
        __pci_enable_wake(dev, target_state, pci_dev_run_wake(dev));
 
        error = pci_set_power_state(dev, target_state);
 
        if (error) {
                pci_enable_wake(dev, target_state, false);
+               pci_restore_ptm_state(dev);
                dev->runtime_d3cold = false;
        }
 
index e0f9e7259e47bad7e96c55485225d5ea96ce20da..f38ba693c69a3cd85ba0de989c86120cddb20ee9 100644 (file)
@@ -519,9 +519,11 @@ static inline int pci_iov_bus_range(struct pci_bus *bus)
 #ifdef CONFIG_PCIE_PTM
 void pci_save_ptm_state(struct pci_dev *dev);
 void pci_restore_ptm_state(struct pci_dev *dev);
+void pci_disable_ptm(struct pci_dev *dev);
 #else
 static inline void pci_save_ptm_state(struct pci_dev *dev) { }
 static inline void pci_restore_ptm_state(struct pci_dev *dev) { }
+static inline void pci_disable_ptm(struct pci_dev *dev) { }
 #endif
 
 unsigned long pci_cardbus_resource_alignment(struct resource *);
index 6b24a1c9327a696c775a143cc118493996abffb8..95d4eef2c9e86fb9182e136a261ee0e1809619cf 100644 (file)
@@ -29,6 +29,23 @@ static void pci_ptm_info(struct pci_dev *dev)
                 dev->ptm_root ? " (root)" : "", clock_desc);
 }
 
+void pci_disable_ptm(struct pci_dev *dev)
+{
+       int ptm;
+       u16 ctrl;
+
+       if (!pci_is_pcie(dev))
+               return;
+
+       ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM);
+       if (!ptm)
+               return;
+
+       pci_read_config_word(dev, ptm + PCI_PTM_CTRL, &ctrl);
+       ctrl &= ~(PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT);
+       pci_write_config_word(dev, ptm + PCI_PTM_CTRL, ctrl);
+}
+
 void pci_save_ptm_state(struct pci_dev *dev)
 {
        int ptm;