Add support for the PCIe present on the EcoNet EN7528 (and EN751221) SoCs.
These SoCs have a mix of Gen1 and Gen2 capable ports, but the Gen2 ports
require re-training after startup.
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>
---
drivers/pci/controller/Kconfig | 2 +-
drivers/pci/controller/pcie-mediatek.c | 133 +++++++++++++++++++++++++
2 files changed, 134 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
index 686349e09cd3..5808d5e407fd 100644
--- a/drivers/pci/controller/Kconfig
+++ b/drivers/pci/controller/Kconfig
@@ -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
diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c
index 75722524fe74..915a35825ce1 100644
--- a/drivers/pci/controller/pcie-mediatek.c
+++ b/drivers/pci/controller/pcie-mediatek.c
@@ -7,6 +7,7 @@
* Honghui Zhang <honghui.zhang@mediatek.com>
*/
+#include <asm-generic/errno-base.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
@@ -14,6 +15,7 @@
#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>
@@ -77,6 +79,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
@@ -89,6 +92,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))
@@ -148,12 +156,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: Re-train 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),
};
/**
@@ -753,6 +764,80 @@ 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);
+
+ return 0;
+}
+
static void __iomem *mtk_pcie_map_bus(struct pci_bus *bus,
unsigned int devfn, int where)
{
@@ -1149,6 +1234,46 @@ static int mtk_pcie_probe(struct platform_device *pdev)
if (err)
goto put_resources;
+ /* EN7528 PCIe initially comes up as Gen1 even if Gen2 is supported.
+ * The cannonical way to achieve Gen2 is to re-train the link
+ * immediately after setup. However, to save a lot of duplicated code
+ * we use pcie_retrain_link() which is usable once we have the pci_dev
+ * struct for the bridge, i.e. after pci_host_probe(). */
+ if (pcie->soc->quirks & MTK_PCIE_RETRAIN) {
+ int slot = of_get_pci_domain_nr(dev->of_node);
+ struct pci_dev *rc = NULL;
+ int ret = -ENOENT;
+
+ if (slot >= 0)
+ rc = pci_get_slot(host->bus, PCI_DEVFN(slot, 0));
+
+ if (rc) {
+ ret = -EOPNOTSUPP;
+
+ /* pcie_retrain_link() is not an exported symbol but
+ * this driver supports being built as a loadable
+ * module. Someone using this on an EN7528 should make
+ * it builtin, or accept Gen1 PCI. */
+#if IS_BUILTIN(CONFIG_PCIE_MEDIATEK)
+ ret = pcie_retrain_link(rc, true);
+#endif
+ }
+
+ if (ret) {
+ dev_info(dev, "port%d failed to retrain %pe\n", slot,
+ ERR_PTR(ret));
+ } else {
+ u16 lnksta;
+ u32 speed;
+
+ pcie_capability_read_word(rc, PCI_EXP_LNKSTA, &lnksta);
+ speed = lnksta & PCI_EXP_LNKSTA_CLS;
+
+ dev_info(dev, "port%d link retrained, speed %s\n", slot,
+ pci_speed_string(pcie_link_speed[speed]));
+ }
+ }
+
return 0;
put_resources:
@@ -1264,8 +1389,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 },
--
2.39.5
© 2016 - 2026 Red Hat, Inc.