PCI: tegra: Add PCIe Gen2 link speed support [Linux 5.3]

This Linux kernel change "PCI: tegra: Add PCIe Gen2 link speed support" is included in the Linux 5.3 release. This change is authored by Manikanta Maddireddy <mmaddireddy [at] nvidia.com> on Tue Jun 18 23:31:45 2019 +0530. The commit for this change in Linux stable tree is 538123a (patch).

PCI: tegra: Add PCIe Gen2 link speed support

Tegra124, Tegra132, Tegra210 and Tegra186 support Gen2 link speed. After
PCIe link is up in Gen1, set target link speed as Gen2 and retrain link.
Link switches to Gen2 speed if Gen2 capable end point is connected,
otherwise the link stays in Gen1.

Per PCIe 4.0r0.9 sec 7.6.3.7 implementation note, driver needs to wait for
PCIe LTSSM to come back from recovery before retraining the link.

Signed-off-by: Manikanta Maddireddy <mmaddireddy@nvidia.com>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Acked-by: Thierry Reding <treding@nvidia.com>

There are 64 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/pci/controller/pci-tegra.c | 64 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c
index c86e657..dfb13ae 100644
--- a/drivers/pci/controller/pci-tegra.c
+++ b/drivers/pci/controller/pci-tegra.c
@@ -191,6 +191,8 @@
 #define  RP_LINK_CONTROL_STATUS_DL_LINK_ACTIVE 0x20000000
 #define  RP_LINK_CONTROL_STATUS_LINKSTAT_MASK  0x3fff0000

+#define RP_LINK_CONTROL_STATUS_2       0x000000b0
+
 #define PADS_CTL_SEL       0x0000009c

 #define PADS_CTL       0x000000a0
@@ -226,6 +228,7 @@
 #define PADS_REFCLK_CFG_DRVI_SHIFT     12 /* 15:12 */

 #define PME_ACK_TIMEOUT 10000
+#define LINK_RETRAIN_TIMEOUT 100000 /* in usec */

 struct tegra_msi {
    struct msi_controller chip;
@@ -2108,6 +2111,64 @@ static bool tegra_pcie_port_check_link(struct tegra_pcie_port *port)
    return false;
 }

+static void tegra_pcie_change_link_speed(struct tegra_pcie *pcie)
+{
+   struct device *dev = pcie->dev;
+   struct tegra_pcie_port *port;
+   ktime_t deadline;
+   u32 value;
+
+   list_for_each_entry(port, &pcie->ports, list) {
+       /*
+        * "Supported Link Speeds Vector" in "Link Capabilities 2"
+        * is not supported by Tegra. tegra_pcie_change_link_speed()
+        * is called only for Tegra chips which support Gen2.
+        * So there no harm if supported link speed is not verified.
+        */
+       value = readl(port->base + RP_LINK_CONTROL_STATUS_2);
+       value &= ~PCI_EXP_LNKSTA_CLS;
+       value |= PCI_EXP_LNKSTA_CLS_5_0GB;
+       writel(value, port->base + RP_LINK_CONTROL_STATUS_2);
+
+       /*
+        * Poll until link comes back from recovery to avoid race
+        * condition.
+        */
+       deadline = ktime_add_us(ktime_get(), LINK_RETRAIN_TIMEOUT);
+
+       while (ktime_before(ktime_get(), deadline)) {
+           value = readl(port->base + RP_LINK_CONTROL_STATUS);
+           if ((value & PCI_EXP_LNKSTA_LT) == 0)
+               break;
+
+           usleep_range(2000, 3000);
+       }
+
+       if (value & PCI_EXP_LNKSTA_LT)
+           dev_warn(dev, "PCIe port %u link is in recovery\n",
+                port->index);
+
+       /* Retrain the link */
+       value = readl(port->base + RP_LINK_CONTROL_STATUS);
+       value |= PCI_EXP_LNKCTL_RL;
+       writel(value, port->base + RP_LINK_CONTROL_STATUS);
+
+       deadline = ktime_add_us(ktime_get(), LINK_RETRAIN_TIMEOUT);
+
+       while (ktime_before(ktime_get(), deadline)) {
+           value = readl(port->base + RP_LINK_CONTROL_STATUS);
+           if ((value & PCI_EXP_LNKSTA_LT) == 0)
+               break;
+
+           usleep_range(2000, 3000);
+       }
+
+       if (value & PCI_EXP_LNKSTA_LT)
+           dev_err(dev, "failed to retrain link of port %u\n",
+               port->index);
+   }
+}
+
 static void tegra_pcie_enable_ports(struct tegra_pcie *pcie)
 {
    struct device *dev = pcie->dev;
@@ -2132,6 +2193,9 @@ static void tegra_pcie_enable_ports(struct tegra_pcie *pcie)
        tegra_pcie_port_disable(port);
        tegra_pcie_port_free(port);
    }
+
+   if (pcie->soc->has_gen2)
+       tegra_pcie_change_link_speed(pcie);
 }

 static void tegra_pcie_disable_ports(struct tegra_pcie *pcie)

Leave a Reply

Your email address will not be published. Required fields are marked *