--- /dev/null
+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);
+ }
+ }