diff options
| -rw-r--r-- | drivers/pci/pcie/aspm.c | 132 |
1 files changed, 90 insertions, 42 deletions
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 9b3548544a85..95ac34a34bd5 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -839,6 +839,49 @@ static void aspm_l1ss_init(struct pcie_link_state *link) #define FLAG(x, y, d) (((x) & (PCIE_LINK_STATE_##y)) ? d : "") +/* Configure the ASPM L1 substates. Caller must disable L1 first. */ +static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state) +{ + u32 val = 0; + struct pci_dev *child = link->downstream, *parent = link->pdev; + + if (state & PCIE_LINK_STATE_L1_1) + val |= PCI_L1SS_CTL1_ASPM_L1_1; + if (state & PCIE_LINK_STATE_L1_2) + val |= PCI_L1SS_CTL1_ASPM_L1_2; + if (state & PCIE_LINK_STATE_L1_1_PCIPM) + val |= PCI_L1SS_CTL1_PCIPM_L1_1; + if (state & PCIE_LINK_STATE_L1_2_PCIPM) + val |= PCI_L1SS_CTL1_PCIPM_L1_2; + + /* + * PCIe r6.2, sec 5.5.4, rules for enabling L1 PM Substates: + * - Clear L1.x enable bits at child first, then at parent + * - Set L1.x enable bits at parent first, then at child + * - ASPM/PCIPM L1.2 must be disabled while programming timing + * parameters + */ + + /* Disable all L1 substates */ + pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1SS_MASK, 0); + pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1SS_MASK, 0); + + /* Enable what we need to enable */ + pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1SS_MASK, val); + pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1SS_MASK, val); +} + +static bool pcie_link_has_aspm_override(const struct pcie_link_state *link, + const char *aspm) +{ + return (device_property_present(&link->pdev->dev, aspm) || + device_property_present(&link->downstream->dev, aspm)); +} + static void pcie_aspm_override_default_link_state(struct pcie_link_state *link) { struct pci_dev *pdev = link->downstream; @@ -846,6 +889,36 @@ static void pcie_aspm_override_default_link_state(struct pcie_link_state *link) /* For devicetree platforms, enable L0s and L1 by default */ if (of_have_populated_dt()) { + bool no_l0s = pcie_link_has_aspm_override(link, "aspm-no-l0s"); + bool no_l1 = pcie_link_has_aspm_override(link, "aspm-no-l1"); + bool no_l1ss = pcie_link_has_aspm_override(link, "aspm-no-l1ss"); + + if (no_l0s) { + link->aspm_support &= ~PCIE_LINK_STATE_L0S; + link->aspm_default &= ~PCIE_LINK_STATE_L0S; + link->aspm_enabled &= ~PCIE_LINK_STATE_L0S; + } + + /* + * Clear L1SS in hardware before updating aspm_support. Once + * aspm_capable is derived from aspm_support, pcie_config_aspm_link() + * skips pcie_config_aspm_l1ss() entirely via the aspm_capable guard, + * leaving firmware-enabled L1SS substates active in hardware. + * This applies equally when disabling L1 (which implies L1SS). + */ + if ((no_l1 || no_l1ss) && (link->aspm_enabled & PCIE_LINK_STATE_L1SS)) + pcie_config_aspm_l1ss(link, 0); + + if (no_l1) { + link->aspm_support &= ~(PCIE_LINK_STATE_L1 | PCIE_LINK_STATE_L1SS); + link->aspm_default &= ~(PCIE_LINK_STATE_L1 | PCIE_LINK_STATE_L1SS); + link->aspm_enabled &= ~(PCIE_LINK_STATE_L1 | PCIE_LINK_STATE_L1SS); + } else if (no_l1ss) { + link->aspm_support &= ~PCIE_LINK_STATE_L1SS; + link->aspm_default &= ~PCIE_LINK_STATE_L1SS; + link->aspm_enabled &= ~PCIE_LINK_STATE_L1SS; + } + if (link->aspm_support & PCIE_LINK_STATE_L0S) link->aspm_default |= PCIE_LINK_STATE_L0S; if (link->aspm_support & PCIE_LINK_STATE_L1) @@ -926,9 +999,25 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) aspm_l1ss_init(link); - /* Restore L0s/L1 if they were enabled */ + /* Save default state */ + link->aspm_default = link->aspm_enabled; + + pcie_aspm_override_default_link_state(link); + + /* + * Restore L0s/L1 if they were enabled, but don't restore any + * state a Devicetree override just disabled in aspm_support above. + */ if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, child_lnkctl) || FIELD_GET(PCI_EXP_LNKCTL_ASPMC, parent_lnkctl)) { + if (!(link->aspm_support & PCIE_LINK_STATE_L0S)) { + child_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L0S; + parent_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L0S; + } + if (!(link->aspm_support & PCIE_LINK_STATE_L1)) { + child_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L1; + parent_lnkctl &= ~PCI_EXP_LNKCTL_ASPM_L1; + } pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_ASPMC, parent_lnkctl & PCI_EXP_LNKCTL_ASPMC); @@ -938,11 +1027,6 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) child_lnkctl & PCI_EXP_LNKCTL_ASPMC); } - /* Save default state */ - link->aspm_default = link->aspm_enabled; - - pcie_aspm_override_default_link_state(link); - /* Setup initial capable state. Will be updated later */ link->aspm_capable = link->aspm_support; @@ -956,42 +1040,6 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) } } -/* Configure the ASPM L1 substates. Caller must disable L1 first. */ -static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state) -{ - u32 val = 0; - struct pci_dev *child = link->downstream, *parent = link->pdev; - - if (state & PCIE_LINK_STATE_L1_1) - val |= PCI_L1SS_CTL1_ASPM_L1_1; - if (state & PCIE_LINK_STATE_L1_2) - val |= PCI_L1SS_CTL1_ASPM_L1_2; - if (state & PCIE_LINK_STATE_L1_1_PCIPM) - val |= PCI_L1SS_CTL1_PCIPM_L1_1; - if (state & PCIE_LINK_STATE_L1_2_PCIPM) - val |= PCI_L1SS_CTL1_PCIPM_L1_2; - - /* - * PCIe r6.2, sec 5.5.4, rules for enabling L1 PM Substates: - * - Clear L1.x enable bits at child first, then at parent - * - Set L1.x enable bits at parent first, then at child - * - ASPM/PCIPM L1.2 must be disabled while programming timing - * parameters - */ - - /* Disable all L1 substates */ - pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1, - PCI_L1SS_CTL1_L1SS_MASK, 0); - pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, - PCI_L1SS_CTL1_L1SS_MASK, 0); - - /* Enable what we need to enable */ - pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, - PCI_L1SS_CTL1_L1SS_MASK, val); - pci_clear_and_set_config_dword(child, child->l1ss + PCI_L1SS_CTL1, - PCI_L1SS_CTL1_L1SS_MASK, val); -} - static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val) { pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL, |
