PCI: mediatek: Add support for EcoNet EN7528 SoC

Add support for the PCIe controller present in the EcoNet EN7528 (and
EN751221) SoCs.

The PCIe controllers present in these SoCs support 2.5 GT/s and 5 GT/s data
rates, but 5 GT/s require re-training after link up.

Co-developed-by: Ahmed Naseef <naseefkm@gmail.com>
Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
[mani: commit log]
Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
Link: https://patch.msgid.link/20260702150704.265282-1-cjd@cjdns.fr
This commit is contained in:
Caleb James DeLisle
2026-07-02 15:07:04 +00:00
committed by Manivannan Sadhasivam
parent dc59e4fea9
commit 2077ccb9ba
2 changed files with 155 additions and 1 deletions

View File

@@ -209,7 +209,7 @@ config PCI_MVEBU
config PCIE_MEDIATEK
tristate "MediaTek PCIe controller"
depends on ARCH_AIROHA || ARCH_MEDIATEK || COMPILE_TEST
depends on ARCH_AIROHA || ARCH_MEDIATEK || ECONET || COMPILE_TEST
depends on OF
depends on PCI_MSI
select IRQ_MSI_LIB

View File

@@ -10,11 +10,13 @@
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqchip/irq-msi-lib.h>
#include <linux/irqdomain.h>
#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/msi.h>
@@ -78,6 +80,7 @@
#define PCIE_CONF_VEND_ID 0x100
#define PCIE_CONF_DEVICE_ID 0x102
#define PCIE_CONF_REV_CLASS 0x104
#define PCIE_CONF_CLASS_ID 0x106
#define PCIE_INT_MASK 0x420
@@ -90,6 +93,11 @@
#define MSI_MASK BIT(23)
#define MTK_MSI_IRQS_NUM 32
#define EN7528_HOST_MODE 0x00804201
#define EN7528_LINKUP_REG 0x50
#define EN7528_RC0_LINKUP BIT(1)
#define EN7528_RC1_LINKUP BIT(2)
#define PCIE_AHB_TRANS_BASE0_L 0x438
#define PCIE_AHB_TRANS_BASE0_H 0x43c
#define AHB2PCIE_SIZE(x) ((x) & GENMASK(4, 0))
@@ -145,12 +153,15 @@ struct mtk_pcie_port;
* @MTK_PCIE_FIX_DEVICE_ID: host's device ID needed to be fixed
* @MTK_PCIE_NO_MSI: Bridge has no MSI support, and relies on an external block
* @MTK_PCIE_SKIP_RSTB: Skip calling RSTB bits on PCIe probe
* @MTK_PCIE_RETRAIN: Retrain link to bridge after startup because some
* Gen2-capable devices start as Gen1.
*/
enum mtk_pcie_quirks {
MTK_PCIE_FIX_CLASS_ID = BIT(0),
MTK_PCIE_FIX_DEVICE_ID = BIT(1),
MTK_PCIE_NO_MSI = BIT(2),
MTK_PCIE_SKIP_RSTB = BIT(3),
MTK_PCIE_RETRAIN = BIT(4),
};
/**
@@ -756,6 +767,134 @@ static int mtk_pcie_startup_port_v2(struct mtk_pcie_port *port)
return 0;
}
static int mtk_pcie_startup_port_en7528(struct mtk_pcie_port *port)
{
struct mtk_pcie *pcie = port->pcie;
struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
struct resource *mem = NULL;
struct resource_entry *entry;
u32 val, link_mask;
int err;
entry = resource_list_first_type(&host->windows, IORESOURCE_MEM);
if (entry)
mem = entry->res;
if (!mem)
return -EINVAL;
if (!pcie->cfg) {
dev_err(pcie->dev, "EN7528: pciecfg syscon not available\n");
return -EINVAL;
}
/* Assert all reset signals */
writel(0, port->base + PCIE_RST_CTRL);
/*
* Enable PCIe link down reset, if link status changed from link up to
* link down, this will reset MAC control registers and configuration
* space.
*/
writel(PCIE_LINKDOWN_RST_EN, port->base + PCIE_RST_CTRL);
msleep(PCIE_T_PVPERL_MS);
/* De-assert PHY, PE, PIPE, MAC and configuration reset */
val = readl(port->base + PCIE_RST_CTRL);
val |= PCIE_PHY_RSTB | PCIE_PERSTB | PCIE_PIPE_SRSTB |
PCIE_MAC_SRSTB | PCIE_CRSTB;
writel(val, port->base + PCIE_RST_CTRL);
writel(PCIE_CLASS_CODE | PCIE_REVISION_ID,
port->base + PCIE_CONF_REV_CLASS);
writel(EN7528_HOST_MODE, port->base);
link_mask = (port->slot == 0) ? EN7528_RC0_LINKUP : EN7528_RC1_LINKUP;
/* 100ms timeout value should be enough for Gen1/2 training */
err = regmap_read_poll_timeout(pcie->cfg, EN7528_LINKUP_REG, val,
!!(val & link_mask), 20,
PCI_PM_D3COLD_WAIT * USEC_PER_MSEC);
if (err) {
dev_err(pcie->dev, "EN7528: port%d link timeout\n", port->slot);
return -ETIMEDOUT;
}
/* Activate INTx interrupts */
val = readl(port->base + PCIE_INT_MASK);
val &= ~INTX_MASK;
writel(val, port->base + PCIE_INT_MASK);
if (IS_ENABLED(CONFIG_PCI_MSI))
mtk_pcie_enable_msi(port);
/* Set AHB to PCIe translation windows */
val = lower_32_bits(mem->start) |
AHB2PCIE_SIZE(fls(resource_size(mem)));
writel(val, port->base + PCIE_AHB_TRANS_BASE0_L);
val = upper_32_bits(mem->start);
writel(val, port->base + PCIE_AHB_TRANS_BASE0_H);
writel(WIN_ENABLE, port->base + PCIE_AXI_WINDOW0);
if (!IS_BUILTIN(CONFIG_PCIE_MEDIATEK))
dev_info(pcie->dev,
"module not built-in, Gen2 unavailable even if supported\n");
return 0;
}
/**
* mtk_pcie_retrain - retrain the root bridge link if needed
* @dev: The device, for use in logging
* @host: The host bridge which contains the link
*
* Due to what is likely a hardware bug, some devices (notably EcoNet) start up
* as Gen1, and must be retrained once after initial configuration in order to
* reach Gen2.
*
* These devices always self-identify as Gen2 capable, but sometimes the PHY is
* only capable of Gen1 operation, and sometimes the PCIe card (e.g. wifi) is
* only Gen1 capable. Therefore it is most convenient to retrain every port
* after startup.
*/
static int mtk_pcie_retrain(struct device *dev, struct pci_host_bridge *host)
{
struct pci_dev *rp;
int ret = -ENOENT;
u16 lnksta = 0;
u32 speed;
/* Should already have been warned about during startup_port */
if (!IS_BUILTIN(CONFIG_PCIE_MEDIATEK))
return 0;
guard(rwsem_read)(&pci_bus_sem);
for_each_pci_bridge(rp, host->bus) {
if (pci_pcie_type(rp) != PCI_EXP_TYPE_ROOT_PORT)
continue;
#if IS_BUILTIN(CONFIG_PCIE_MEDIATEK)
ret = pcie_retrain_link(rp, true);
#endif
if (ret)
return dev_err_probe(&rp->dev, ret,
"failed to retrain port\n");
pcie_capability_read_word(rp, PCI_EXP_LNKSTA, &lnksta);
speed = lnksta & PCI_EXP_LNKSTA_CLS;
pci_info(rp, "link retrained, speed %s\n",
pci_speed_string(pcie_link_speed[speed]));
}
return 0;
}
static void __iomem *mtk_pcie_map_bus(struct pci_bus *bus,
unsigned int devfn, int where)
{
@@ -1169,6 +1308,13 @@ static int mtk_pcie_probe(struct platform_device *pdev)
if (err)
goto put_resources;
/*
* Ignore error because pci_host_probe() was already called, and in any
* case it is possible that the port will still work as Gen1.
*/
if (pcie->soc->quirks & MTK_PCIE_RETRAIN)
mtk_pcie_retrain(dev, host);
return 0;
put_resources:
@@ -1290,8 +1436,16 @@ static const struct mtk_pcie_soc mtk_pcie_soc_mt7629 = {
.quirks = MTK_PCIE_FIX_CLASS_ID | MTK_PCIE_FIX_DEVICE_ID,
};
static const struct mtk_pcie_soc mtk_pcie_soc_en7528 = {
.ops = &mtk_pcie_ops_v2,
.startup = mtk_pcie_startup_port_en7528,
.setup_irq = mtk_pcie_setup_irq,
.quirks = MTK_PCIE_RETRAIN,
};
static const struct of_device_id mtk_pcie_ids[] = {
{ .compatible = "airoha,an7583-pcie", .data = &mtk_pcie_soc_an7583 },
{ .compatible = "econet,en7528-pcie", .data = &mtk_pcie_soc_en7528 },
{ .compatible = "mediatek,mt2701-pcie", .data = &mtk_pcie_soc_v1 },
{ .compatible = "mediatek,mt7623-pcie", .data = &mtk_pcie_soc_v1 },
{ .compatible = "mediatek,mt2712-pcie", .data = &mtk_pcie_soc_mt2712 },