diff mbox series

[6/9] pci: spacemit: do not force the link to 2.5 GT/s

Message ID bbfa4cbc6c21effbb507644cfa94cfbad4ab6d82.1790975598.git.DaveWK225@protonmail.com
State New
Delegated to: Tim Ouyang
Headers show
Series pci: spacemit: fixes for the K1 PCIe host driver | expand

Commit Message

Dave Klotz Oct. 2, 2026, 9:31 p.m. UTC
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 <DaveWK225@protonmail.com>
---
 drivers/pci/pcie_dw_spacemit.c | 48 ++++++++++++++++++++++++----------
 drivers/pci/pcie_dw_spacemit.h |  7 -----
 2 files changed, 34 insertions(+), 21 deletions(-)
diff mbox series

Patch

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)