]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
airoha: fix PERST deassert in PCIe driver
authorKenneth Kasilag <kenneth@kasilag.me>
Mon, 6 Apr 2026 23:04:38 +0000 (23:04 +0000)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Wed, 3 Jun 2026 07:06:33 +0000 (09:06 +0200)
Due to hardware bugs, the PCIE Gen3 IP in Airoha AN7581 requires a
special reset procedure:
> MAC reset asserted thru the SCU block
> PERST asserted thru the SCU for the desired PCIE lane
> PHY initialization runs, clocks enabled
> MAC reset deasserted
> EQ config (if needed) written
> PERST deasserted

The existing code currently toggles PERST for all three PCIE
ports every time mtk_pcie_en7581_power_up is called, resulting
in PCIE link down issues.

This issue was discovered during porting of the 6.18 kernel to
AN7581. It is not entirely clear how the hardware seemed to
function under kernel 6.12 with similar driver code, it is
presumed that differences in assert/deassert times for PERST in
6.18 changed which exposed the bug.

Signed-off-by: Kenneth Kasilag <kenneth@kasilag.me>
Link: https://github.com/openwrt/openwrt/pull/21019
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
target/linux/airoha/patches-6.18/913-pcie-mediatek-gen3-fix-x2-mode-PERST-deassert.patch [new file with mode: 0644]

diff --git a/target/linux/airoha/patches-6.18/913-pcie-mediatek-gen3-fix-x2-mode-PERST-deassert.patch b/target/linux/airoha/patches-6.18/913-pcie-mediatek-gen3-fix-x2-mode-PERST-deassert.patch
new file mode 100644 (file)
index 0000000..2be755f
--- /dev/null
@@ -0,0 +1,209 @@
+From fde8e40b0bf72436ce75d21e8778049368ea200f Mon Sep 17 00:00:00 2001
+From: Kenneth Kasilag <kenneth@kasilag.me>
+Date: Mon, 6 Apr 2026 23:04:38 +0000
+Subject: [PATCH] airoha: fix PERST deassert in PCIe driver
+
+Due to hardware bugs, the PCIE Gen3 IP in Airoha AN7581 requires a
+special reset procedure:
+> MAC reset asserted thru the SCU block
+> PERST asserted thru the SCU for the desired PCIE lane
+> PHY initialization runs, clocks enabled
+> MAC reset deasserted
+> EQ config (if needed) written
+> PERST deasserted
+
+The existing code currently toggles PERST for all three PCIE
+ports every time mtk_pcie_en7581_power_up is called, resulting
+in PCIE link down issues.
+
+This issue was discovered during porting of the 6.18 kernel to
+AN7581. It is not entirely clear how the hardware seemed to
+function under kernel 6.12 with similar driver code, it is
+presumed that differences in assert/deassert times for PERST in
+6.18 changed which exposed the bug.
+
+Signed-off-by: Kenneth Kasilag <kenneth@kasilag.me>
+---
+ drivers/pci/controller/pcie-mediatek-gen3.c | 124 ++++++++-------
+ 1 file changed, 86 insertions(+), 38 deletions(-)
+
+diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c
+index afcd4343293e0f..cf7d5272eadb6e 100644
+--- a/drivers/pci/controller/pcie-mediatek-gen3.c
++++ b/drivers/pci/controller/pcie-mediatek-gen3.c
+@@ -72,8 +72,21 @@
+ /* EN7581 NP_SCU register offsets for x2 link init */
+ #define NP_SCU_LANE_CFG0              0x830
++#define NP_SCU_XSI_MAC_RST            BIT(7)
++#define NP_SCU_XSI_PHY_RST            BIT(8)
++#define NP_SCU_PCIE2_RST              BIT(27)
+ #define NP_SCU_LANE_CFG1              0x834
++#define NP_SCU_PCIE0_RST              BIT(26)
++#define NP_SCU_PCIE1_RST              BIT(27)
+ #define NP_SCU_CTRL_REG                       0x88
++#define NP_SCU_PERSTOUT                       BIT(29)
++#define NP_SCU_PERSTOUT1              BIT(26)
++#define NP_SCU_PERSTOUT2              BIT(16)
++#define NP_SCU_PERST_ALL              (NP_SCU_PERSTOUT | \
++                                       NP_SCU_PERSTOUT1 | \
++                                       NP_SCU_PERSTOUT2)
++#define NP_SCU_SERDES_MUX_MASK                GENMASK(1, 0)
++#define NP_SCU_SERDES_MUX_X2          2
+ #define PCIE_LINK_STATUS_REG          0x154
+ #define PCIE_PORT_LINKUP              BIT(8)
+@@ -1004,6 +1017,23 @@ static int mtk_pcie_en7581_power_up(stru
+                       dev_info(dev, "x2 mode: sister MAC mapped\n");
+       }
++      /* Select the correct reset for each PCIe port */
++      u32 perst_mask = NP_SCU_PERST_ALL;
++      if (pcie->x2_mode)
++              perst_mask = NP_SCU_PERSTOUT | NP_SCU_PERSTOUT1;
++      else if (pcie->reg_base == 0x1fc00000)
++              perst_mask = NP_SCU_PERSTOUT;
++      else if (pcie->reg_base == 0x1fc20000)
++              perst_mask = NP_SCU_PERSTOUT1;
++      else if (pcie->reg_base == 0x1fc40000)
++              perst_mask = NP_SCU_PERSTOUT2;
++
++      /* Assert selected PERST prior to PHY init */
++      if (pcie->np_scu) {
++              regmap_update_bits(pcie->np_scu, NP_SCU_CTRL_REG, perst_mask, 0);
++              msleep(1);
++      }
++
+       err = phy_set_mode(pcie->phy, PHY_MODE_PCIE);
+       if (err) {
+               dev_err(dev, "failed to set PHY mode\n");
+@@ -1043,26 +1073,22 @@ static int mtk_pcie_en7581_power_up(stru
+       pm_runtime_get_sync(dev);
+       /*
+-       * 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.
++       * EN7581 x2: Do 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 */
++              /* Assert serdes reset on lanes 0 and 1 */
+               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));
++                                 NP_SCU_PCIE0_RST | NP_SCU_PCIE1_RST,
++                                 NP_SCU_PCIE0_RST | NP_SCU_PCIE1_RST);
+               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);
++                                 NP_SCU_SERDES_MUX_MASK,
++                                 NP_SCU_SERDES_MUX_X2);
++              msleep(1);
+       }
+       err = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks);
+@@ -1085,11 +1111,9 @@ static int mtk_pcie_en7581_power_up(stru
+                */
+               msleep(30);
+-              /* Deassert serdes reset on all lanes */
++              /* Deassert serdes reset on lanes 0 and 1 */
+               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);
++                                 NP_SCU_PCIE0_RST | NP_SCU_PCIE1_RST, 0);
+               /* Clear SETTING_REG bit 13 for x2 mode on both MACs */
+               val = readl_relaxed(pcie->base + PCIE_SETTING_REG);
+@@ -1109,59 +1133,16 @@ static int mtk_pcie_en7581_power_up(stru
+               if (pcie->sister_base)
+                       writel_relaxed(0x1018020f, pcie->sister_base + 0x338);
+-              /* Deassert PERST for all ports - link training starts */
++              /* Deassert PERST on selected port - 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));
++                                 perst_mask,
++                                 perst_mask);
+               /* 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");
+-              }
++              msleep(1000);
+-              dev_info(dev, "x2: init complete, PERST deasserted\n");
++              dev_info(dev, "x2: init complete\n");
+       } else {
+               /*
+                * Non-x2 mode: standard EQ config then deassert PERST.
+@@ -1178,11 +1159,11 @@ static int mtk_pcie_en7581_power_up(stru
+                     FIELD_PREP(PCIE_K_FINETUNE_MAX, 0xf);
+               writel_relaxed(val, pcie->base + PCIE_PIPE4_PIE8_REG);
+-              /* Deassert PERST for all ports */
++              /* Deassert PERST on selected port */
+               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));
++                                         perst_mask,
++                                         perst_mask);
+                       msleep(100);
+               }
+       }