PCI/ASPM: Configure L1 substate settings
authorRajat Jain <rajatja@google.com>
Tue, 3 Jan 2017 06:34:14 +0000 (22:34 -0800)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 14 Feb 2017 23:44:30 +0000 (17:44 -0600)
Configure the L1 substate settings on the upstream and downstream devices,
while taking care of the rules dictated by the PCIe spec.

[bhelgaas: drop "inline"]
Signed-off-by: Rajat Jain <rajatja@google.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/pcie/aspm.c

index b3451cb..ceb2395 100644 (file)
@@ -588,6 +588,92 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
        }
 }
 
+static void pci_clear_and_set_dword(struct pci_dev *pdev, int pos,
+                                   u32 clear, u32 set)
+{
+       u32 val;
+
+       pci_read_config_dword(pdev, pos, &val);
+       val &= ~clear;
+       val |= set;
+       pci_write_config_dword(pdev, pos, val);
+}
+
+/* Configure the ASPM L1 substates */
+static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
+{
+       u32 val, enable_req;
+       struct pci_dev *child = link->downstream, *parent = link->pdev;
+       u32 up_cap_ptr = link->l1ss.up_cap_ptr;
+       u32 dw_cap_ptr = link->l1ss.dw_cap_ptr;
+
+       enable_req = (link->aspm_enabled ^ state) & state;
+
+       /*
+        * Here are the rules specified in the PCIe spec for enabling L1SS:
+        * - When enabling L1.x, enable bit at parent first, then at child
+        * - When disabling L1.x, disable bit at child first, then at parent
+        * - When enabling ASPM L1.x, need to disable L1
+        *   (at child followed by parent).
+        * - The ASPM/PCIPM L1.2 must be disabled while programming timing
+        *   parameters
+        *
+        * To keep it simple, disable all L1SS bits first, and later enable
+        * what is needed.
+        */
+
+       /* Disable all L1 substates */
+       pci_clear_and_set_dword(child, dw_cap_ptr + PCI_L1SS_CTL1,
+                               PCI_L1SS_CTL1_L1SS_MASK, 0);
+       pci_clear_and_set_dword(parent, up_cap_ptr + PCI_L1SS_CTL1,
+                               PCI_L1SS_CTL1_L1SS_MASK, 0);
+       /*
+        * If needed, disable L1, and it gets enabled later
+        * in pcie_config_aspm_link().
+        */
+       if (enable_req & (ASPM_STATE_L1_1 | ASPM_STATE_L1_2)) {
+               pcie_capability_clear_and_set_word(child, PCI_EXP_LNKCTL,
+                                                  PCI_EXP_LNKCTL_ASPM_L1, 0);
+               pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
+                                                  PCI_EXP_LNKCTL_ASPM_L1, 0);
+       }
+
+       if (enable_req & ASPM_STATE_L1_2_MASK) {
+
+               /* Program T_pwr_on in both ports */
+               pci_write_config_dword(parent, up_cap_ptr + PCI_L1SS_CTL2,
+                                      link->l1ss.ctl2);
+               pci_write_config_dword(child, dw_cap_ptr + PCI_L1SS_CTL2,
+                                      link->l1ss.ctl2);
+
+               /* Program T_cmn_mode in parent */
+               pci_clear_and_set_dword(parent, up_cap_ptr + PCI_L1SS_CTL1,
+                                       0xFF00, link->l1ss.ctl1);
+
+               /* Program LTR L1.2 threshold in both ports */
+               pci_clear_and_set_dword(parent, dw_cap_ptr + PCI_L1SS_CTL1,
+                                       0xE3FF0000, link->l1ss.ctl1);
+               pci_clear_and_set_dword(child, dw_cap_ptr + PCI_L1SS_CTL1,
+                                       0xE3FF0000, link->l1ss.ctl1);
+       }
+
+       val = 0;
+       if (state & ASPM_STATE_L1_1)
+               val |= PCI_L1SS_CTL1_ASPM_L1_1;
+       if (state & ASPM_STATE_L1_2)
+               val |= PCI_L1SS_CTL1_ASPM_L1_2;
+       if (state & ASPM_STATE_L1_1_PCIPM)
+               val |= PCI_L1SS_CTL1_PCIPM_L1_1;
+       if (state & ASPM_STATE_L1_2_PCIPM)
+               val |= PCI_L1SS_CTL1_PCIPM_L1_2;
+
+       /* Enable what we need to enable */
+       pci_clear_and_set_dword(parent, up_cap_ptr + PCI_L1SS_CTL1,
+                               PCI_L1SS_CAP_L1_PM_SS, val);
+       pci_clear_and_set_dword(child, dw_cap_ptr + PCI_L1SS_CTL1,
+                               PCI_L1SS_CAP_L1_PM_SS, val);
+}
+
 static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
 {
        pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL,
@@ -597,11 +683,23 @@ static void pcie_config_aspm_dev(struct pci_dev *pdev, u32 val)
 static void pcie_config_aspm_link(struct pcie_link_state *link, u32 state)
 {
        u32 upstream = 0, dwstream = 0;
-       struct pci_dev *child, *parent = link->pdev;
+       struct pci_dev *child = link->downstream, *parent = link->pdev;
        struct pci_bus *linkbus = parent->subordinate;
 
-       /* Nothing to do if the link is already in the requested state */
+       /* Enable only the states that were not explicitly disabled */
        state &= (link->aspm_capable & ~link->aspm_disable);
+
+       /* Can't enable any substates if L1 is not enabled */
+       if (!(state & ASPM_STATE_L1))
+               state &= ~ASPM_STATE_L1SS;
+
+       /* Spec says both ports must be in D0 before enabling PCI PM substates*/
+       if (parent->current_state != PCI_D0 || child->current_state != PCI_D0) {
+               state &= ~ASPM_STATE_L1_SS_PCIPM;
+               state |= (link->aspm_enabled & ASPM_STATE_L1_SS_PCIPM);
+       }
+
+       /* Nothing to do if the link is already in the requested state */
        if (link->aspm_enabled == state)
                return;
        /* Convert ASPM state to upstream/downstream ASPM register state */
@@ -613,6 +711,10 @@ static void pcie_config_aspm_link(struct pcie_link_state *link, u32 state)
                upstream |= PCI_EXP_LNKCTL_ASPM_L1;
                dwstream |= PCI_EXP_LNKCTL_ASPM_L1;
        }
+
+       if (link->aspm_capable & ASPM_STATE_L1SS)
+               pcie_config_aspm_l1ss(link, state);
+
        /*
         * Spec 2.0 suggests all functions should be configured the
         * same setting for ASPM. Enabling ASPM L1 should be done in