airoha: fix EN7581 PCIe initialization and add x2 link support

Fix two hardware initialization issues in the EN7581 PCIe controller
and add support for x2 (2-lane) link mode.

Fixes:

The upstream EN7581 PCIe initialization writes EQ presets and PIPE
configuration registers before clk_bulk_prepare_enable(). Since the
MAC clocks are not yet running at that point, these register writes
are silently dropped, leaving the hardware with default values. This
can cause link training failures or suboptimal equalization.

Additionally, after link training the MAC may only advertise Gen1-Gen2
capability in the Link Capabilities 2 register despite the PHY being
configured for Gen3. A serdes reset toggle forces the MAC to re-read
PHY capability, recovering Gen3 8GT/s link speed.

Both issues are addressed by separating PERST from the clock callbacks
(patch 911), allowing the PCIe controller driver to properly sequence
PERST, clock enable, and register writes (patch 912).

New feature:

PCIe x2 mode support for EN7581 using the NP_SCU system controller
for serdes mux routing, PERST management, and lane configuration.
Both bonded MACs are configured for x2 operation with proper EQ
presets before link training begins.

Signed-off-by: Ryan Chen <rchen14b@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/21978
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Ryan Chen 2026-02-10 22:25:01 -06:00 committed by Robert Marko
parent cd39bc2c5b
commit 8b9bd686e7
3 changed files with 295 additions and 0 deletions

View file

@ -723,6 +723,11 @@
mediatek,pbus-csr = <&pbus_csr 0x0 0x4>;
airoha,chip-scu = <&chip_scu>;
airoha,np-scu = <&scuclk>;
airoha,x2-mode;
airoha,serdes-lanes-mask = <0x3>;
interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
bus-range = <0x00 0xff>;
#interrupt-cells = <1>;
@ -809,6 +814,10 @@
mediatek,pbus-csr = <&pbus_csr 0x10 0x14>;
airoha,chip-scu = <&chip_scu>;
airoha,serdes-lanes-mask = <0x4>;
airoha,np-scu = <&scuclk>;
interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
bus-range = <0x00 0xff>;
#interrupt-cells = <1>;

View file

@ -0,0 +1,42 @@
From: Ryan Chen <rchen14b@gmail.com>
Subject: clk: en7581: Separate PERST from refclk in PCIe clock callbacks
The EN7581 PCIe clock enable/disable callbacks currently toggle both
PERST (reset) and reference clock signals together. This prevents the
PCIe controller driver from properly sequencing PERST relative to MAC
register configuration, which is required for x2 link mode.
Separate the two concerns: clock callbacks only manage reference clocks
(REFCLK_EN0/EN1), while PERST (PERSTOUT/PERSTOUT1/PERSTOUT2) is left
to the PCIe controller driver to manage directly via the NP_SCU regmap.
Signed-off-by: Ryan Chen <rchen14b@gmail.com>
--- a/drivers/clk/clk-en7523.c
+++ b/drivers/clk/clk-en7523.c
@@ -961,9 +961,11 @@ static int en7581_pci_enable(struct clk_
struct regmap *map = cg->map;
u32 mask;
- mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 |
- REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 |
- REG_PCI_CONTROL_PERSTOUT;
+ /* Only enable reference clocks - PERST is managed separately by the
+ * PCIe controller driver to allow proper sequencing of MAC register
+ * configuration between PERST assert and deassert.
+ */
+ mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1;
regmap_set_bits(map, REG_PCI_CONTROL, mask);
return 0;
@@ -975,9 +977,8 @@ static void en7581_pci_disable(struct cl
struct regmap *map = cg->map;
u32 mask;
- mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 |
- REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 |
- REG_PCI_CONTROL_PERSTOUT;
+ /* Only disable reference clocks - PCIe driver manages PERST */
+ mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1;
regmap_clear_bits(map, REG_PCI_CONTROL, mask);
usleep_range(1000, 2000);
}

View file

@ -0,0 +1,244 @@
From: Ryan Chen <rchen14b@gmail.com>
Subject: PCI: mediatek-gen3: Add PCIe x2 link support for Airoha EN7581
The Airoha EN7581 SoC supports PCIe x2 mode where two PCIe lanes are
bonded together for a single x2 link. This requires coordination with
the NP_SCU system controller for serdes mux routing, PERST management,
and lane configuration.
The x2 initialization sequence:
1. Assert serdes reset and PERST before enabling clocks
2. Configure serdes mux for 2-lane mode
3. Enable reference clocks, deassert serdes reset
4. Clear x1 mode bit and write EQ presets on both MACs
5. Deassert PERST to start link training
After initial link training, if the link negotiates Gen2 instead of
Gen3, a serdes reset toggle forces the MAC to re-discover the PHY
Gen3 capability from the Link Capabilities 2 register, allowing the
link to retrain at Gen3 x2 (8 GT/s).
Signed-off-by: Ryan Chen <rchen14b@gmail.com>
--- a/drivers/pci/controller/pcie-mediatek-gen3.c
+++ b/drivers/pci/controller/pcie-mediatek-gen3.c
@@ -61,6 +61,14 @@
#define PCIE_LTSSM_STATE(val) ((val & PCIE_LTSSM_STATE_MASK) >> 24)
#define PCIE_LTSSM_STATE_L2_IDLE 0x14
+/* EN7581 x2 mode support */
+#define PCIE_SETTING_REG_X1_MODE BIT(13)
+
+/* EN7581 NP_SCU register offsets for x2 link init */
+#define NP_SCU_LANE_CFG0 0x830
+#define NP_SCU_LANE_CFG1 0x834
+#define NP_SCU_CTRL_REG 0x88
+
#define PCIE_LINK_STATUS_REG 0x154
#define PCIE_PORT_LINKUP BIT(8)
@@ -205,6 +213,11 @@ struct mtk_gen3_pcie {
DECLARE_BITMAP(msi_irq_in_use, PCIE_MSI_IRQS_NUM);
const struct mtk_gen3_pcie_pdata *soc;
+
+ /* EN7581 x2 mode support */
+ struct regmap *np_scu;
+ bool x2_mode;
+ void __iomem *sister_base;
};
/* LTSSM state in PCIE_LTSSM_STATUS_REG bit[28:24] */
@@ -925,6 +938,28 @@ static int mtk_pcie_en7581_power_up(stru
size = lower_32_bits(resource_size(entry->res));
regmap_write(pbus_regmap, args[1], GENMASK(31, __fls(size)));
+ /* Lookup NP_SCU regmap for x2 mode support */
+ pcie->np_scu = syscon_regmap_lookup_by_phandle(dev->of_node, "airoha,np-scu");
+ if (IS_ERR(pcie->np_scu)) {
+ dev_dbg(dev, "np_scu not available, x2 mode disabled\n");
+ pcie->np_scu = NULL;
+ }
+
+ /* Check for x2 mode property */
+ pcie->x2_mode = of_property_read_bool(dev->of_node, "airoha,x2-mode");
+ if (pcie->x2_mode)
+ dev_info(dev, "x2 mode enabled\n");
+
+ /* Map sister MAC for x2 mode (MAC1 at +0x20000) */
+ if (pcie->x2_mode) {
+ pcie->sister_base = devm_ioremap(dev,
+ pcie->reg_base + 0x20000, 0x20000);
+ if (!pcie->sister_base)
+ dev_warn(dev, "failed to map sister MAC for x2\n");
+ else
+ dev_info(dev, "x2 mode: sister MAC mapped\n");
+ }
+
err = phy_set_mode(pcie->phy, PHY_MODE_PCIE);
if (err) {
dev_err(dev, "failed to set PHY mode\n");
@@ -962,17 +997,28 @@ static int mtk_pcie_en7581_power_up(stru
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
- val = FIELD_PREP(PCIE_VAL_LN0_DOWNSTREAM, 0x47) |
- FIELD_PREP(PCIE_VAL_LN1_DOWNSTREAM, 0x47) |
- FIELD_PREP(PCIE_VAL_LN0_UPSTREAM, 0x41) |
- FIELD_PREP(PCIE_VAL_LN1_UPSTREAM, 0x41);
- writel_relaxed(val, pcie->base + PCIE_EQ_PRESET_01_REG);
-
- val = PCIE_K_PHYPARAM_QUERY | PCIE_K_QUERY_TIMEOUT |
- FIELD_PREP(PCIE_K_PRESET_TO_USE_16G, 0x80) |
- FIELD_PREP(PCIE_K_PRESET_TO_USE, 0x2) |
- FIELD_PREP(PCIE_K_FINETUNE_MAX, 0xf);
- writel_relaxed(val, pcie->base + PCIE_PIPE4_PIE8_REG);
+ /*
+ * EN7581 x2: Assert PERST and serdes reset before enabling clocks
+ * so that MAC registers can be configured while devices are held
+ * in reset, ensuring link trains with the correct x2 settings.
+ */
+ if (pcie->x2_mode && pcie->np_scu) {
+ /* Assert serdes reset on all lanes */
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG1,
+ BIT(26) | BIT(27), BIT(26) | BIT(27));
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG0,
+ BIT(27), BIT(27));
+ msleep(100);
+
+ /* Assert PERST on all ports */
+ regmap_update_bits(pcie->np_scu, NP_SCU_CTRL_REG,
+ BIT(16) | BIT(26) | BIT(29), 0);
+
+ /* Set serdes mux for 2-lane mode (bits[1:0] = 2) */
+ regmap_update_bits(pcie->np_scu, NP_SCU_CTRL_REG,
+ BIT(0) | BIT(1), BIT(1));
+ mdelay(1);
+ }
err = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks);
if (err) {
@@ -981,12 +1027,121 @@ static int mtk_pcie_en7581_power_up(stru
}
/*
- * Airoha EN7581 performs PCIe reset via clk callbacks since it has a
- * hw issue with PCIE_PE_RSTB signal. Add wait for the time needed to
- * complete the PCIe reset.
+ * Airoha EN7581: clock enable only provides refclk (patch 911).
+ * For x2, PERST + serdes are already asserted above.
+ * Wait for refclk to stabilize.
*/
msleep(PCIE_T_PVPERL_MS);
+ if (pcie->x2_mode && pcie->np_scu) {
+ /*
+ * EN7581 x2: PERST asserted, serdes reset asserted,
+ * refclk now stable. Complete the init sequence.
+ */
+ msleep(30);
+
+ /* Deassert serdes reset on all lanes */
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG1,
+ BIT(26) | BIT(27), 0);
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG0,
+ BIT(27), 0);
+
+ /* Clear SETTING_REG bit 13 for x2 mode on both MACs */
+ val = readl_relaxed(pcie->base + PCIE_SETTING_REG);
+ writel_relaxed(val & ~PCIE_SETTING_REG_X1_MODE,
+ pcie->base + PCIE_SETTING_REG);
+ if (pcie->sister_base) {
+ val = readl_relaxed(pcie->sister_base + 0x80);
+ writel_relaxed(val & ~PCIE_SETTING_REG_X1_MODE,
+ pcie->sister_base + 0x80);
+ }
+
+ /* EQ presets on both MACs */
+ writel_relaxed(0x41474147, pcie->base + PCIE_EQ_PRESET_01_REG);
+ if (pcie->sister_base)
+ writel_relaxed(0x41474147, pcie->sister_base + 0x100);
+ writel_relaxed(0x1018020f, pcie->base + PCIE_PIPE4_PIE8_REG);
+ if (pcie->sister_base)
+ writel_relaxed(0x1018020f, pcie->sister_base + 0x338);
+
+ /* Deassert PERST for all ports - link training starts */
+ msleep(10);
+ regmap_update_bits(pcie->np_scu, NP_SCU_CTRL_REG,
+ BIT(16) | BIT(26) | BIT(29),
+ BIT(16) | BIT(26) | BIT(29));
+
+ /* Wait for link training to complete */
+ msleep(800);
+
+ /*
+ * Check if link trained at Gen3. If not, toggle serdes
+ * reset to force MAC to re-discover PHY Gen3 capability.
+ * Without this, MAC only advertises Gen1-Gen2 in LnkCap2.
+ */
+ val = readl_relaxed(pcie->base + PCIE_LINK_STATUS_REG);
+ if (val & PCIE_PORT_LINKUP) {
+ void __iomem *cfg = pcie->base + PCIE_CFG_OFFSET_ADDR;
+ u8 cap_ptr;
+ int speed = 0;
+
+ /* Walk PCI cap list to find PCIe cap (ID=0x10) */
+ cap_ptr = readl_relaxed(cfg + PCI_CAPABILITY_LIST) & 0xFF;
+ while (cap_ptr >= 0x40) {
+ u32 hdr = readl_relaxed(cfg + cap_ptr);
+ if ((hdr & 0xFF) == PCI_CAP_ID_EXP) {
+ u32 lnk = readl_relaxed(cfg + cap_ptr + PCI_EXP_LNKCTL);
+ speed = (lnk >> 16) & PCI_EXP_LNKSTA_CLS;
+ break;
+ }
+ cap_ptr = (hdr >> 8) & 0xFF;
+ }
+
+ if (speed > 0 && speed < 3) {
+ dev_info(dev, "x2: link at Gen%d, toggling serdes for Gen3\n", speed);
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG0,
+ BIT(7) | BIT(8), BIT(7) | BIT(8));
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG1,
+ BIT(26) | BIT(27), BIT(26) | BIT(27));
+ msleep(1000);
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG0,
+ BIT(7) | BIT(8), 0);
+ regmap_update_bits(pcie->np_scu, NP_SCU_LANE_CFG1,
+ BIT(26) | BIT(27), 0);
+ msleep(2000);
+ dev_info(dev, "x2: serdes toggle done, link retraining\n");
+ } else {
+ dev_info(dev, "x2: link at Gen%d, no toggle needed\n", speed);
+ }
+ } else {
+ dev_info(dev, "x2: link not up after init, skipping speed check\n");
+ }
+
+ dev_info(dev, "x2: init complete, PERST deasserted\n");
+ } else {
+ /*
+ * Non-x2 mode: standard EQ config then deassert PERST.
+ */
+ val = FIELD_PREP(PCIE_VAL_LN0_DOWNSTREAM, 0x47) |
+ FIELD_PREP(PCIE_VAL_LN1_DOWNSTREAM, 0x47) |
+ FIELD_PREP(PCIE_VAL_LN0_UPSTREAM, 0x41) |
+ FIELD_PREP(PCIE_VAL_LN1_UPSTREAM, 0x41);
+ writel_relaxed(val, pcie->base + PCIE_EQ_PRESET_01_REG);
+
+ val = PCIE_K_PHYPARAM_QUERY | PCIE_K_QUERY_TIMEOUT |
+ FIELD_PREP(PCIE_K_PRESET_TO_USE_16G, 0x80) |
+ FIELD_PREP(PCIE_K_PRESET_TO_USE, 0x2) |
+ FIELD_PREP(PCIE_K_FINETUNE_MAX, 0xf);
+ writel_relaxed(val, pcie->base + PCIE_PIPE4_PIE8_REG);
+
+ /* Deassert PERST for all ports */
+ if (pcie->np_scu) {
+ regmap_update_bits(pcie->np_scu, NP_SCU_CTRL_REG,
+ BIT(16) | BIT(26) | BIT(29),
+ BIT(16) | BIT(26) | BIT(29));
+ msleep(100);
+ }
+ }
+
return 0;
err_clk_prepare_enable: