summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRajat Jain <rajatja@google.com>2017-01-02 22:34:14 -0800
committerBjorn Helgaas <bhelgaas@google.com>2017-02-14 17:44:30 -0600
commitaeda9adebab8b5bdd90576e3065a1f3f948279ad (patch)
tree4176535f3725b735d975ffb09449381537b07dc2
parentf1f0366dd6be9624f7d355b72cc909ab821eb4c0 (diff)
PCI/ASPM: Configure L1 substate settings
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>
-rw-r--r--drivers/pci/pcie/aspm.c106
1 files changed, 104 insertions, 2 deletions
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index b3451cb0472b..ceb2395c37ae 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -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