blob: 562930d25f7e5d883472b676ef678ce57c24965a [file] [log] [blame]
From f4349ee3d05bbcec3c2327fc9baa3b4b35d47ab8 Mon Sep 17 00:00:00 2001
From: Sukumar Ghorai <sukumar.ghorai@intel.com>
Date: Fri, 18 Aug 2023 08:25:47 -0700
Subject: [PATCH] BACKPORT: FROMLIST: PCI/ASPM: Add back L1 PM Substate save
and restore
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Commit a7152be79b62 ("Revert "PCI/ASPM: Save L1 PM Substates Capability
for suspend/resume"") reverted saving and restoring of ASPM L1 Substates
due to a regression that caused resume from suspend to fail on certain
systems. However, we never added this capability back and this is now
causing systems fail to enter low power CPU states, drawing more power
from the battery.
The original revert mentioned that we restore L1 PM substate configuration
even though ASPM L1 may already be enabled. This is due the fact that
the pci_restore_aspm_l1ss_state() was called before
pci_restore_pcie_state().
Try to enable this functionality again by:
1) Moving the restore happen after PCIe capability is restored
following PCIe r6.0, sec 5.5.4.
2) Following the PCIe spec more closely to restore L1 PM substate
configuration (program enable bits last).
3) Adding denylist that skips the restoring on the ASUS system where
the original regression happened, just in case.
- Write L1 enables on the upstream component first then downstream
(this is taken care by the parent child order of the Linux PM).
- Program L1 SS before L1 enables
- Program L1 SS enables after rest of the fields in the capability
BUG=b:290138639
TEST='lspci -vvv | egrep '^[0-9a-f]|L1SubCtl1:|L1SubCap:'
And verify ASPM_L1.2+ ASPM_L1.1+ are enabled after suspend/resume.
UPSTREAM-TASK=b:297527919
(am from https://patchwork.kernel.org/patch/13294023)
Reported-by: Koba Ko <koba.ko@canonical.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217321
Cc: Tasev Nikola <tasev.stefanoska@skynet.be>
Cc: Mark Enriquez <enriquezmark36@gmail.com>
Cc: Thomas Witt <kernel@witt.link>
Change-Id: Ib9965c3fe6cb43987dd2fdec3dc80bbcc102ad4e
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Sukumar GhoraI <sukumar.ghorai@intel.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/4794589
Reviewed-by: Paz Zcharya <pazz@google.com>
Reviewed-by: Stanisław Kardach <skardach@google.com>
---
drivers/pci/pci.c | 18 ++++++-
drivers/pci/pci.h | 4 ++
drivers/pci/pcie/aspm.c | 109 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 129 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 9b1fb7e13be4b71479a969e9265f209622850bee..06cb8067ac7f185f47a32b44e1d022372d2d9a4d 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1580,7 +1580,7 @@ static void pci_restore_pcie_state(struct pci_dev *dev)
{
int i = 0;
struct pci_cap_saved_state *save_state;
- u16 *cap;
+ u16 *cap, val;
save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP);
if (!save_state)
@@ -1595,7 +1595,14 @@ static void pci_restore_pcie_state(struct pci_dev *dev)
cap = (u16 *)&save_state->cap.data[0];
pcie_capability_write_word(dev, PCI_EXP_DEVCTL, cap[i++]);
- pcie_capability_write_word(dev, PCI_EXP_LNKCTL, cap[i++]);
+ /*
+ * Restoring ASPM L1 substates has special requirements
+ * according to the PCIe spec 6.0. So we restore here only the
+ * LNKCTL register with the ASPM control field clear. ASPM will
+ * be restored in pci_restore_aspm_state().
+ */
+ val = cap[i++] & ~PCI_EXP_LNKCTL_ASPMC;
+ pcie_capability_write_word(dev, PCI_EXP_LNKCTL, val);
pcie_capability_write_word(dev, PCI_EXP_SLTCTL, cap[i++]);
pcie_capability_write_word(dev, PCI_EXP_RTCTL, cap[i++]);
pcie_capability_write_word(dev, PCI_EXP_DEVCTL2, cap[i++]);
@@ -1706,6 +1713,7 @@ int pci_save_state(struct pci_dev *dev)
pci_save_ltr_state(dev);
pci_save_dpc_state(dev);
pci_save_aer_state(dev);
+ pci_save_aspm_state(dev);
pci_save_ptm_state(dev);
return pci_save_vc_state(dev);
}
@@ -1818,6 +1826,7 @@ void pci_restore_state(struct pci_dev *dev)
pci_restore_rebar_state(dev);
pci_restore_dpc_state(dev);
pci_restore_ptm_state(dev);
+ pci_restore_aspm_state(dev);
pci_aer_clear_status(dev);
pci_restore_aer_state(dev);
@@ -3510,6 +3519,11 @@ void pci_allocate_cap_save_buffers(struct pci_dev *dev)
if (error)
pci_err(dev, "unable to allocate suspend buffer for LTR\n");
+ error = pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_L1SS,
+ 2 * sizeof(u32));
+ if (error)
+ pci_err(dev, "unable to allocate suspend buffer for ASPM-L1SS\n");
+
pci_allocate_vc_save_buffers(dev);
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index f1dde81503b53533261786acf64f4f7f7451621f..e5fb2309b6cad01ae726b25b96d0ab101a3c50b5 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -570,10 +570,14 @@ int pcie_retrain_link(struct pci_dev *pdev, bool use_lt);
void pcie_aspm_init_link_state(struct pci_dev *pdev);
void pcie_aspm_exit_link_state(struct pci_dev *pdev);
void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
+void pci_save_aspm_state(struct pci_dev *pdev);
+void pci_restore_aspm_state(struct pci_dev *pdev);
#else
static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { }
static inline void pcie_aspm_exit_link_state(struct pci_dev *pdev) { }
static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
+static inline void pci_save_aspm_state(struct pci_dev *pdev) { }
+static inline void pci_restore_aspm_state(struct pci_dev *pdev) { }
#endif
#ifdef CONFIG_PCIE_ECRC
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 50b04ae5c394e3691d3b100eeed3918cd7be9f4c..28eff3c02dcd2eea3c35aeffb2bbeba43fad2342 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -7,6 +7,7 @@
* Copyright (C) Shaohua Li (shaohua.li@intel.com)
*/
+#include <linux/dmi.h>
#include <linux/bitfield.h>
#include <linux/kernel.h>
#include <linux/limits.h>
@@ -719,6 +720,114 @@ static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
PCI_L1SS_CTL1_L1SS_MASK, val);
}
+void pci_save_aspm_state(struct pci_dev *pdev)
+{
+ struct pci_cap_saved_state *save_state;
+ u16 l1ss = pdev->l1ss;
+ u32 *cap;
+
+ /*
+ * Save L1 substate configuration. The ASPM L0s/L1 configuration
+ * is already saved in pci_save_pcie_state().
+ */
+ if (!l1ss)
+ return;
+
+ save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_L1SS);
+ if (!save_state)
+ return;
+
+ cap = (u32 *)&save_state->cap.data[0];
+ pci_read_config_dword(pdev, l1ss + PCI_L1SS_CTL2, cap++);
+ pci_read_config_dword(pdev, l1ss + PCI_L1SS_CTL1, cap++);
+}
+
+/*
+ * Do not restore L1 substates for the below systems even if BIOS has
+ * enabled it initially. This breaks resume from suspend otherwise on
+ * these.
+ */
+static const struct dmi_system_id aspm_l1ss_denylist[] = {
+ {
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=216782 */
+ .ident = "ASUS UX305FA",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "UX305FA"),
+ },
+ },
+ { }
+};
+
+static void pcie_restore_aspm_l1ss(struct pci_dev *pdev)
+{
+ struct pci_cap_saved_state *save_state;
+ u32 *cap, ctl1, ctl2, l1_2_enable;
+ u16 l1ss = pdev->l1ss;
+
+ if (!l1ss)
+ return;
+
+ if (dmi_check_system(aspm_l1ss_denylist)) {
+ pci_dbg(pdev, "skipping restoring L1 substates on this system\n");
+ return;
+ }
+
+ save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_L1SS);
+ if (!save_state)
+ return;
+
+ cap = (u32 *)&save_state->cap.data[0];
+ ctl2 = *cap++;
+ ctl1 = *cap;
+
+ /*
+ * In addition, Common_Mode_Restore_Time and LTR_L1.2_THRESHOLD
+ * in PCI_L1SS_CTL1 must be programmed *before* setting the L1.2
+ * enable bits, even though they're all in PCI_L1SS_CTL1.
+ */
+ l1_2_enable = ctl1 & PCI_L1SS_CTL1_L1_2_MASK;
+ ctl1 &= ~PCI_L1SS_CTL1_L1_2_MASK;
+
+ /* Write back without enables first (above we cleared them in ctl1) */
+ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL1, ctl1);
+ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL2, ctl2);
+
+ /* Then write back the enables */
+ if (l1_2_enable)
+ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL1,
+ ctl1 | l1_2_enable);
+}
+
+void pci_restore_aspm_state(struct pci_dev *pdev)
+{
+ struct pci_cap_saved_state *save_state;
+ u16 *cap, val, tmp;
+
+ save_state = pci_find_saved_cap(pdev, PCI_CAP_ID_EXP);
+ if (!save_state)
+ return;
+
+ cap = (u16 *)&save_state->cap.data[0];
+ /*
+ * Must match the ordering in pci_save/restore_pcie_state().
+ * This is PCI_EXP_LNKCTL.
+ */
+ val = cap[1] & PCI_EXP_LNKCTL_ASPMC;
+ if (!val)
+ return;
+
+ /*
+ * We restore L1 substate configuration first before enabling L1
+ * as the PCIe spec 6.0 sec 5.5.4 suggests.
+ * */
+ pcie_restore_aspm_l1ss(pdev);
+
+ pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &tmp);
+ /* Re-enable L0s/L1 */
+ pcie_capability_write_word(pdev, PCI_EXP_LNKCTL, tmp | val);
+}
+
static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
{
pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL,
--
2.43.0.rc2.451.g8631bc7472-goog