The driver writes Gen1 into the Supported Link Speeds field of the root
port's Link Capabilities and into the Target Link Speed of Link
Control 2, so the K1's 5 GT/s capable ports never train above 2.5 GT/s,
and an operating system that keeps the root port as found cannot
retrain them faster either. It also writes them at the fixed offsets
0x7c and 0xa0 instead of through the PCI Express capability.

Linux's DesignWare core leaves both fields alone unless the device tree
limits the speed with "max-link-speed", and then writes that value to
both. Do the same, finding the registers through the capability list,
and drop the driver's copies of the speed definitions that
pcie_dw_common.h already provides.

Signed-off-by: Dave Klotz <[email protected]>
---
 drivers/pci/pcie_dw_spacemit.c | 48 ++++++++++++++++++++++++----------
 drivers/pci/pcie_dw_spacemit.h |  7 -----
 2 files changed, 34 insertions(+), 21 deletions(-)

diff --git a/drivers/pci/pcie_dw_spacemit.c b/drivers/pci/pcie_dw_spacemit.c
index 3394ddbd18..6ac1d6bea4 100644
--- a/drivers/pci/pcie_dw_spacemit.c
+++ b/drivers/pci/pcie_dw_spacemit.c
@@ -40,6 +40,7 @@ struct pcie_dw_spacemit {
        struct clk_bulk         clks;
        struct reset_ctl_bulk   rsts;
        struct udevice          *vpcie3v3;      /* slot supply, if described */
+       u32                     max_link_speed; /* 0: hardware default */
 };
 
 static inline u32 spacemit_pcie_readl(struct pcie_dw_spacemit *pcie, u32 
offset)
@@ -58,21 +59,33 @@ static inline u32 spacemit_pcie_phy_ahb_readl(struct 
pcie_dw_spacemit *pcie, u32
        return readl(pcie->phy_ahb + offset);
 }
 
-static void pcie_dw_configure(struct pcie_dw_spacemit *pci, u32 cap_speed)
+static void pcie_dw_configure(struct pcie_dw_spacemit *pci)
 {
-       u32 val;
+       void __iomem *cap;
+       u32 speed = pci->max_link_speed;
+       u16 lnkctl2;
+       u32 lnkcap;
 
-       dw_pcie_dbi_write_enable(&pci->dw, true);
+       cap = pci->dw.dbi_base + pcie_dw_find_capability(&pci->dw,
+                                                        PCI_CAP_ID_EXP);
 
-       val = readl(pci->dw.dbi_base + PCIE_LINK_CAPABILITY);
-       val &= ~TARGET_LINK_SPEED_MASK;
-       val |= cap_speed;
-       writel(val, pci->dw.dbi_base + PCIE_LINK_CAPABILITY);
+       dw_pcie_dbi_write_enable(&pci->dw, true);
 
-       val = readl(pci->dw.dbi_base + PCIE_LINK_CTL_2);
-       val &= ~TARGET_LINK_SPEED_MASK;
-       val |= cap_speed;
-       writel(val, pci->dw.dbi_base + PCIE_LINK_CTL_2);
+       /*
+        * Keep the hardware's supported speeds unless the device tree limits
+        * them with "max-link-speed", as Linux's DesignWare core does.
+        */
+       if (speed) {
+               lnkcap = readl(cap + PCI_EXP_LNKCAP);
+               lnkcap &= ~PCI_EXP_LNKCAP_SLS;
+               lnkcap |= speed;
+               writel(lnkcap, cap + PCI_EXP_LNKCAP);
+
+               lnkctl2 = readw(cap + PCI_EXP_LNKCTL2);
+               lnkctl2 &= ~PCI_EXP_LNKCTL2_TLS;
+               lnkctl2 |= speed;
+               writew(lnkctl2, cap + PCI_EXP_LNKCTL2);
+       }
 
        dw_pcie_dbi_write_enable(&pci->dw, false);
 }
@@ -105,12 +118,12 @@ static int wait_link_up(struct pcie_dw_spacemit *pci)
        return 1;
 }
 
-static int pcie_dw_spacemit_pcie_link_up(struct pcie_dw_spacemit *pci, u32 
cap_speed)
+static int pcie_dw_spacemit_pcie_link_up(struct pcie_dw_spacemit *pci)
 {
        u32 reg;
 
        /* DW pre link configurations */
-       pcie_dw_configure(pci, cap_speed);
+       pcie_dw_configure(pci);
 
        /* Initiate link training */
        reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
@@ -315,7 +328,7 @@ static int pcie_dw_spacemit_probe(struct udevice *dev)
        pcie_dw_setup_host(&pci->dw);
        pcie_dw_init_id(pci);
 
-       if (!pcie_dw_spacemit_pcie_link_up(pci, LINK_SPEED_GEN_1)) {
+       if (!pcie_dw_spacemit_pcie_link_up(pci)) {
                printf("PCIE-%d: Link down\n", dev_seq(dev));
                return -ENODEV;
        }
@@ -380,6 +393,13 @@ static int pcie_dw_spacemit_of_to_plat(struct udevice *dev)
                return -EINVAL;
        pcie->apmu_offset = args.args[0];
 
+       pcie->max_link_speed = dev_read_u32_default(dev, "max-link-speed", 0);
+       if (pcie->max_link_speed > LINK_SPEED_GEN_4) {
+               dev_warn(dev, "invalid max-link-speed %u, ignored\n",
+                        pcie->max_link_speed);
+               pcie->max_link_speed = 0;
+       }
+
        /* Derive port ID from DBI base address */
        pcie->port_id = (dbi_addr - SPACEMIT_PCIE_DBI_BASE) / 
SPACEMIT_PCIE_DBI_STRIDE;
 
diff --git a/drivers/pci/pcie_dw_spacemit.h b/drivers/pci/pcie_dw_spacemit.h
index 6f198f7d58..21686de246 100644
--- a/drivers/pci/pcie_dw_spacemit.h
+++ b/drivers/pci/pcie_dw_spacemit.h
@@ -12,13 +12,6 @@
 #include <linux/bitops.h>
 
 /* PCIe DW common registers */
-#define PCIE_LINK_CAPABILITY           0x7c
-#define PCIE_LINK_CTL_2                        0xa0
-#define TARGET_LINK_SPEED_MASK         0xf
-#define LINK_SPEED_GEN_1               0x1
-#define LINK_SPEED_GEN_2               0x2
-#define LINK_SPEED_GEN_3               0x3
-
 #define PCIE_MISC_CONTROL_1_OFF        0x8bc
 #define PCIE_DBI_RO_WR_EN              BIT(0)
 
-- 
2.55.0


Reply via email to