airoha: replace PCIe x2-mode patch with new pending version

The current x2-mode patch is mostly an hack ported from Airoha SDK. Replace
with in favor of a new version posted upstream that better implement
handling of reset and configuring x2-mode. This now use the standard
num-lanes property and dedicated PERSTOUT resets exported by the clk
driver.

All the DTS that used x2-mode are updated.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
This commit is contained in:
Christian Marangi
2026-07-14 14:27:46 +02:00
parent b496e07d0f
commit 7d56183d90
10 changed files with 411 additions and 512 deletions
@@ -359,10 +359,23 @@
};
&pcie0 {
airoha,x2-mode;
status = "okay";
reg = <0x0 0x1fc00000 0x0 0x1670>,
<0x0 0x1fc20000 0x0 0x1670>;
reg-names = "pcie-mac", "sec-pcie-mac";
resets = <&scuclk EN7581_PCIE0_RST>,
<&scuclk EN7581_PCIE1_RST>,
<&scuclk EN7581_PCIC_PERSTOUT0_RST>,
<&scuclk EN7581_PCIC_PERSTOUT1_RST>;
reset-names = "phy-lane0", "phy-lane1",
"perstout", "sec-perstout";
num-lanes = <2>;
pinctrl-names = "default";
pinctrl-0 = <&pcie1_rst_pins>;
status = "okay";
pcie@0,0 {
reg = <0x0000 0 0 0 0>;
@@ -401,6 +414,10 @@
};
};
&pcie1 {
status = "disabled";
};
&pcie2 {
pinctrl-names = "default";
pinctrl-0 = <&pcie2_rst_pins>;
+16 -1
View File
@@ -235,7 +235,18 @@
&pcie0 {
status = "okay";
airoha,x2-mode;
reg = <0x0 0x1fc00000 0x0 0x1670>,
<0x0 0x1fc20000 0x0 0x1670>;
reg-names = "pcie-mac", "sec-pcie-mac";
resets = <&scuclk EN7581_PCIE0_RST>,
<&scuclk EN7581_PCIE1_RST>,
<&scuclk EN7581_PCIC_PERSTOUT0_RST>,
<&scuclk EN7581_PCIC_PERSTOUT1_RST>;
reset-names = "phy-lane0", "phy-lane1",
"perstout", "sec-perstout";
num-lanes = <2>;
pinctrl-names = "default";
pinctrl-0 = <&pcie0_rst_pins>;
@@ -278,6 +289,10 @@
};
};
&pcie1 {
status = "disabled";
};
&pcie2 {
status = "okay";
+6 -16
View File
@@ -779,15 +779,11 @@
ranges = <0x02000000 0 0x20000000 0x0 0x20000000 0 0x4000000>;
resets = <&scuclk EN7581_PCIE0_RST>,
<&scuclk EN7581_PCIE1_RST>,
<&scuclk EN7581_PCIE2_RST>;
reset-names = "phy-lane0", "phy-lane1", "phy-lane2";
resets = <&scuclk EN7581_PCIE0_RST>, <&scuclk EN7581_PCIC_PERSTOUT0_RST>;
reset-names = "phy-lane0", "perstout";
mediatek,pbus-csr = <&pbus_csr 0x0 0x4>;
airoha,np-scu = <&scuclk>;
interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
bus-range = <0x00 0xff>;
#interrupt-cells = <1>;
@@ -824,10 +820,8 @@
ranges = <0x02000000 0 0x24000000 0x0 0x24000000 0 0x4000000>;
resets = <&scuclk EN7581_PCIE0_RST>,
<&scuclk EN7581_PCIE1_RST>,
<&scuclk EN7581_PCIE2_RST>;
reset-names = "phy-lane0", "phy-lane1", "phy-lane2";
resets = <&scuclk EN7581_PCIE1_RST>, <&scuclk EN7581_PCIC_PERSTOUT1_RST>;
reset-names = "phy-lane1", "perstout";
mediatek,pbus-csr = <&pbus_csr 0x8 0xc>;
@@ -867,15 +861,11 @@
ranges = <0x02000000 0 0x28000000 0x0 0x28000000 0 0x4000000>;
resets = <&scuclk EN7581_PCIE0_RST>,
<&scuclk EN7581_PCIE1_RST>,
<&scuclk EN7581_PCIE2_RST>;
reset-names = "phy-lane0", "phy-lane1", "phy-lane2";
resets = <&scuclk EN7581_PCIE2_RST>, <&scuclk EN7581_PCIC_PERSTOUT2_RST>;
reset-names = "phy-lane2", "perstout";
mediatek,pbus-csr = <&pbus_csr 0x10 0x14>;
airoha,np-scu = <&scuclk>;
interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
bus-range = <0x00 0xff>;
#interrupt-cells = <1>;
@@ -0,0 +1,26 @@
From 198c66755440564bec1bf16f37cfa9702394fd02 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Thu, 25 Jun 2026 13:48:34 +0200
Subject: [PATCH 1/3] dt-bindings: clock: airoha: Add additional reset for PCIe
PERSTOUT
Add additional reset to control PCIe PERSTOUT reset line for each of the 3
PCIe lines.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
include/dt-bindings/reset/airoha,en7581-reset.h | 4 ++++
1 file changed, 4 insertions(+)
--- a/include/dt-bindings/reset/airoha,en7581-reset.h
+++ b/include/dt-bindings/reset/airoha,en7581-reset.h
@@ -62,5 +62,9 @@
#define EN7581_CPU_TIMER_RST 50
#define EN7581_PCIE_HB_RST 51
#define EN7581_XPON_MAC_RST 52
+/* RST_PCIC */
+#define EN7581_PCIC_PERSTOUT0_RST 53
+#define EN7581_PCIC_PERSTOUT1_RST 54
+#define EN7581_PCIC_PERSTOUT2_RST 55
#endif /* __DT_BINDINGS_RESET_CONTROLLER_AIROHA_EN7581_H_ */
@@ -0,0 +1,101 @@
From 335db8dda524add9d8ec3bdd88eddc339d5d3439 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Thu, 25 Jun 2026 13:49:39 +0200
Subject: [PATCH 2/4] clk: en7523: add support for dedicated PCIe PERSTOUT
reset
Add support for resetting the PCIe lines with the PERSTOUT reset. These
special reset are controlled by the PCIC register and are specific to each
of the 3 PCIe lines.
Notice that reset logic is inverted for these bit where 0 is assert and 1
deassert. This is intenrally handled in the reset function.
PCI enable/disable are updated to drop PERSTOUT bits in favor dedicated
reset handling.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/clk/clk-en7523.c | 39 ++++++++++++++++++++++++++++-----------
1 file changed, 28 insertions(+), 11 deletions(-)
--- a/drivers/clk/clk-en7523.c
+++ b/drivers/clk/clk-en7523.c
@@ -451,6 +451,7 @@ static const struct en_clk_desc an7583_b
static const u16 en7581_rst_ofs[] = {
REG_RST_CTRL2,
REG_RST_CTRL1,
+ REG_NP_SCU_PCIC,
};
static const u16 en7523_rst_map[] = {
@@ -556,6 +557,11 @@ static const u16 en7581_rst_map[] = {
[EN7581_CPU_TIMER_RST] = RST_NR_PER_BANK + 28,
[EN7581_PCIE_HB_RST] = RST_NR_PER_BANK + 29,
[EN7581_XPON_MAC_RST] = RST_NR_PER_BANK + 31,
+
+ /* RST_PCIC */
+ [EN7581_PCIC_PERSTOUT0_RST] = 2 * RST_NR_PER_BANK + 29,
+ [EN7581_PCIC_PERSTOUT1_RST] = 2 * RST_NR_PER_BANK + 26,
+ [EN7581_PCIC_PERSTOUT2_RST] = 2 * RST_NR_PER_BANK + 16,
};
static const u16 an7583_rst_map[] = {
@@ -960,9 +966,7 @@ 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;
+ mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1;
regmap_set_bits(map, REG_PCI_CONTROL, mask);
return 0;
@@ -974,9 +978,7 @@ 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;
+ mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1;
regmap_clear_bits(map, REG_PCI_CONTROL, mask);
usleep_range(1000, 2000);
}
@@ -1027,9 +1029,16 @@ static int en7523_reset_update(struct re
{
struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev);
u32 addr = rst_data->bank_ofs[id / RST_NR_PER_BANK];
+ u32 val;
+
+ /* For PCIC reset logic is inverted, 0:assert 1:deassert*/
+ if (addr == REG_NP_SCU_PCIC)
+ val |= assert ? 0 : BIT(id % RST_NR_PER_BANK);
+ else
+ val |= assert ? BIT(id % RST_NR_PER_BANK) : 0;
regmap_update_bits(rst_data->map, addr, BIT(id % RST_NR_PER_BANK),
- assert ? BIT(id % RST_NR_PER_BANK) : 0);
+ val);
return 0;
}
@@ -1051,10 +1060,16 @@ static int en7523_reset_status(struct re
{
struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev);
u32 addr = rst_data->bank_ofs[id / RST_NR_PER_BANK];
+ bool inverted = false;
u32 val;
+ /* For PCIC reset logic is inverted, 0:assert 1:deassert*/
+ if (addr == REG_NP_SCU_PCIC)
+ inverted = true;
+
regmap_read(rst_data->map, addr, &val);
- return !!(val & BIT(id % RST_NR_PER_BANK));
+ val &= BIT(id % RST_NR_PER_BANK);
+ return inverted ? !val : !!val;
}
static int en7523_reset_xlate(struct reset_controller_dev *rcdev,
@@ -0,0 +1,218 @@
From 25984a8f0891959a47eff4aa00709de3c23c21e6 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Thu, 25 Jun 2026 16:20:16 +0200
Subject: [PATCH v4 4/4] PCI: mediatek-gen3: Add 2-lanes mode support for
Airoha AN7581
The Airoha AN7581 SoC supports configuring the first PCIe0 lane to 2-lanes
mode (2x link) by bonding it with the second PCIe lane (PCIe1). This is
done by configuring the PCIe MUX in the SCU register.
To correctly configure PCIe0 in 2x link, define in DT the following
additional property:
- additional reg, 'sec-pcie-mac' for the secondary PCIe.
- PERSTOUT reset for both main and secondary PCIE0, called 'perstout' and
'sec-perstout'
- airoha,scu property to correctly configure the SCU register for the PCIe
MUX
- 'num-lanes' set to '2' to enable PCIe0 in 2x link
In such configuration the EQ preset are configured to the same values.
To permit correct configuration of the PCIe line, additional logic is added
to assert and deassert the PERSTOUT resets.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/pci/controller/pcie-mediatek-gen3.c | 106 ++++++++++++++++----
1 file changed, 89 insertions(+), 17 deletions(-)
--- a/drivers/pci/controller/pcie-mediatek-gen3.c
+++ b/drivers/pci/controller/pcie-mediatek-gen3.c
@@ -31,6 +31,11 @@
#include "../pci.h"
+/* AN7581 SCU register */
+#define SCU_PCIC 0x88
+#define SCU_PCIC_PCIE_CTRL GENMASK(7, 0)
+
+/* PCIe register */
#define PCIE_BASE_CFG_REG 0x14
#define PCIE_BASE_CFG_SPEED GENMASK(15, 8)
@@ -130,6 +135,7 @@
#define PCIE_ATR_TLP_TYPE_IO PCIE_ATR_TLP_TYPE(2)
#define MAX_NUM_PHY_RESETS 3
+#define MAX_NUM_PERSTOUT_RESETS 2
#define PCIE_MTK_RESET_TIME_US 10
@@ -202,9 +208,11 @@ struct mtk_msi_set {
struct mtk_gen3_pcie {
struct device *dev;
void __iomem *base;
+ void __iomem *sec_base;
phys_addr_t reg_base;
struct reset_control *mac_reset;
struct reset_control_bulk_data phy_resets[MAX_NUM_PHY_RESETS];
+ struct reset_control_bulk_data perstout_resets[MAX_NUM_PERSTOUT_RESETS];
struct phy *phy;
struct clk_bulk_data *clks;
int num_clks;
@@ -893,6 +901,14 @@ static int mtk_pcie_parse_port(struct mt
return ret;
}
+ pcie->perstout_resets[0].id = "perstout";
+ pcie->perstout_resets[1].id = "sec-perstout";
+
+ ret = devm_reset_control_bulk_get_optional_exclusive(dev, MAX_NUM_PERSTOUT_RESETS,
+ pcie->perstout_resets);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get PERSTOUT bulk reset\n");
+
pcie->mac_reset = devm_reset_control_get_optional_exclusive(dev, "mac");
if (IS_ERR(pcie->mac_reset)) {
ret = PTR_ERR(pcie->mac_reset);
@@ -926,18 +942,38 @@ static int mtk_pcie_parse_port(struct mt
pcie->num_lanes = num_lanes;
}
+ /* Map secondary PCIe for 2-lanes mode for EN7581 */
+ if (pcie->num_lanes == 2 && device_is_compatible(dev, "airoha,en7581-pcie")) {
+ regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sec-pcie-mac");
+ if (!regs)
+ return -EINVAL;
+ pcie->sec_base = devm_ioremap_resource(dev, regs);
+ if (IS_ERR(pcie->sec_base))
+ return dev_err_probe(dev, PTR_ERR(pcie->sec_base), "failed to map secondary register base\n");
+ }
+
return 0;
}
static int mtk_pcie_en7581_power_up(struct mtk_gen3_pcie *pcie)
{
struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
+ unsigned int num_lanes = max(1, pcie->num_lanes);
+ struct regmap *pbus_regmap, *scu;
struct device *dev = pcie->dev;
struct resource_entry *entry;
- struct regmap *pbus_regmap;
u32 val, args[2], size;
resource_size_t addr;
- int err;
+ int i, err;
+
+ if (num_lanes > 2)
+ return dev_err_probe(dev, -EINVAL, "unsupported num-lanes, maximum 2 lanes supported\n");
+
+ if (num_lanes == 2) {
+ scu = syscon_regmap_lookup_by_phandle(dev->of_node, "airoha,scu");
+ if (IS_ERR(scu))
+ return dev_err_probe(dev, PTR_ERR(scu), "failed to map SCU regmap\n");
+ }
/*
* The controller may have been left out of reset by the bootloader
@@ -975,6 +1011,19 @@ static int mtk_pcie_en7581_power_up(stru
return err;
}
+ /* Assert PERSTOUT for all relevant lines */
+ err = reset_control_bulk_assert(MAX_NUM_PERSTOUT_RESETS,
+ pcie->perstout_resets);
+ if (err) {
+ dev_err(dev, "failed to assert PERSTOUTs\n");
+ return err;
+ }
+
+ /* Configure SCU MUX to disable PCIE1 for 2 lines mode */
+ if (num_lanes == 2)
+ regmap_update_bits(scu, SCU_PCIC, SCU_PCIC_PCIE_CTRL,
+ FIELD_PREP(SCU_PCIC_PCIE_CTRL, BIT(1)));
+
/*
* Unlike the other MediaTek Gen3 controllers, the Airoha EN7581
* requires PHY initialization and power-on before PHY reset deassert.
@@ -1007,18 +1056,6 @@ 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);
-
err = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks);
if (err) {
dev_err(dev, "failed to prepare clock\n");
@@ -1026,14 +1063,47 @@ 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.
+ * Old Airoha EN7581 clock driver performed PCIe reset via
+ * clk callbacks since it has a hw issue with PCIE_PE_RSTB signal.
+ * This is now handled by dedicated PERSTOUT resets with clk
+ * driver only enabling the refclk.
+ *
+ * Wait is still needed for refclk to stabilize
*/
msleep(PCIE_T_PVPERL_MS);
+ /* Configure all the lines to the same EQ config */
+ for (i = 0; i < num_lanes; i++) {
+ void __iomem *base = pcie->base;
+
+ if (i == 1)
+ base = pcie->sec_base;
+
+ 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, 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, base + PCIE_PIPE4_PIE8_REG);
+ }
+
+ /* Deassert PERSTOUT for all relevant lines */
+ err = reset_control_bulk_deassert(MAX_NUM_PERSTOUT_RESETS,
+ pcie->perstout_resets);
+ if (err) {
+ dev_err(dev, "failed to deassert PERSTOUTs\n");
+ goto err_perstout_deassert;
+ }
+
return 0;
+err_perstout_deassert:
+ clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
err_clk_prepare_enable:
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
@@ -1119,6 +1189,8 @@ static void mtk_pcie_power_down(struct m
phy_power_off(pcie->phy);
phy_exit(pcie->phy);
+ reset_control_bulk_assert(MAX_NUM_PERSTOUT_RESETS,
+ pcie->perstout_resets);
reset_control_bulk_assert(pcie->soc->phy_resets.num_resets,
pcie->phy_resets);
}
@@ -1,42 +0,0 @@
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
@@ -960,9 +960,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;
@@ -974,9 +976,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);
}
@@ -1,244 +0,0 @@
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
@@ -67,6 +67,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)
@@ -221,6 +229,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] */
@@ -969,6 +982,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");
@@ -1007,17 +1042,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) {
@@ -1026,12 +1072,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:
@@ -0,0 +1,25 @@
From 12047c34237be89c85a0e144c6c99281656a1153 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Thu, 4 Jun 2026 14:26:20 +0200
Subject: [PATCH] clk: en7523: reset PCIE HB on init
Reset PCIE on clk HW init to handle warm boot problem with PCIe.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/clk/clk-en7523.c | 7 +++++++
1 file changed, 7 insertions(+)
--- a/drivers/clk/clk-en7523.c
+++ b/drivers/clk/clk-en7523.c
@@ -1147,6 +1147,10 @@ static int en7581_clk_hw_init(struct pla
regmap_update_bits(clk_map, REG_NP_SCU_PCIC, REG_PCIE_CTRL,
FIELD_PREP(REG_PCIE_CTRL, 3));
+ /* Reset PCIE_HB */
+ regmap_set_bits(clk_map, REG_RST_CTRL1, BIT(29));
+ regmap_clear_bits(clk_map, REG_RST_CTRL1, BIT(29));
+
return en7581_reset_register(&pdev->dev, clk_map, en7581_rst_map,
ARRAY_SIZE(en7581_rst_map));
}
@@ -1,207 +0,0 @@
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(-)
--- 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);
}
}