|
|
@@ -0,0 +1,327 @@
|
|
|
+--- a/drivers/pci/controller/Kconfig
|
|
|
++++ b/drivers/pci/controller/Kconfig
|
|
|
+@@ -187,7 +187,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
|
|
|
+ help
|
|
|
+--- a/arch/mips/econet/Kconfig
|
|
|
++++ b/arch/mips/econet/Kconfig
|
|
|
+@@ -28,9 +28,11 @@ choice
|
|
|
+ bool "EN7528 family"
|
|
|
+ select COMMON_CLK
|
|
|
+ select CPU_LITTLE_ENDIAN
|
|
|
++ select HAVE_PCI
|
|
|
+ select IRQ_MIPS_CPU
|
|
|
+ select MIPS_CPU_SCACHE
|
|
|
+ select MIPS_GIC
|
|
|
++ select PCI_DRIVERS_GENERIC
|
|
|
+ select SMP
|
|
|
+ select SMP_UP
|
|
|
+ select SYS_SUPPORTS_HIGHMEM
|
|
|
+--- a/drivers/pci/controller/pcie-mediatek.c
|
|
|
++++ b/drivers/pci/controller/pcie-mediatek.c
|
|
|
+@@ -76,6 +76,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
|
|
|
+@@ -88,6 +89,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))
|
|
|
+@@ -748,6 +754,86 @@ static int mtk_pcie_startup_port_v2(stru
|
|
|
+ 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);
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Described in PCIe CEM specification sections 2.2 (PERST# Signal) and
|
|
|
++ * 2.2.1 (Initial Power-Up (G3 to S0)). The deassertion of PERST#
|
|
|
++ * should be delayed 100ms (TPVPERL) for the power and clock to become
|
|
|
++ * stable.
|
|
|
++ */
|
|
|
++ msleep(100);
|
|
|
++
|
|
|
++ /* 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,
|
|
|
++ 100 * USEC_PER_MSEC);
|
|
|
++ if (err) {
|
|
|
++ dev_err(pcie->dev, "EN7528: port%d link timeout\n", port->slot);
|
|
|
++ return -ETIMEDOUT;
|
|
|
++ }
|
|
|
++
|
|
|
++ /* Set INTx mask */
|
|
|
++ 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)
|
|
|
+ {
|
|
|
+@@ -1114,6 +1200,20 @@ static int mtk_pcie_probe(struct platfor
|
|
|
+ if (err)
|
|
|
+ goto put_resources;
|
|
|
+
|
|
|
++ /* Retrain Gen1 links to reach Gen2 where supported */
|
|
|
++ if (pcie->soc->startup == mtk_pcie_startup_port_en7528) {
|
|
|
++ struct pci_bus *bus = host->bus;
|
|
|
++ struct pci_dev *rc = NULL;
|
|
|
++
|
|
|
++ while ((rc = pci_get_class(PCI_CLASS_BRIDGE_PCI << 8, rc))) {
|
|
|
++ if (rc->bus != bus)
|
|
|
++ continue;
|
|
|
++ if (!pcie_retrain_link(rc, true))
|
|
|
++ dev_info(dev, "port%d link retrained\n",
|
|
|
++ PCI_SLOT(rc->devfn));
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ put_resources:
|
|
|
+@@ -1223,12 +1323,19 @@ static const struct mtk_pcie_soc mtk_pci
|
|
|
+ .setup_irq = mtk_pcie_setup_irq,
|
|
|
+ };
|
|
|
+
|
|
|
++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,
|
|
|
++};
|
|
|
++
|
|
|
+ static const struct of_device_id mtk_pcie_ids[] = {
|
|
|
+ { .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 },
|
|
|
+ { .compatible = "mediatek,mt7622-pcie", .data = &mtk_pcie_soc_mt7622 },
|
|
|
+ { .compatible = "mediatek,mt7629-pcie", .data = &mtk_pcie_soc_mt7629 },
|
|
|
++ { .compatible = "econet,en7528-pcie", .data = &mtk_pcie_soc_en7528 },
|
|
|
+ {},
|
|
|
+ };
|
|
|
+ MODULE_DEVICE_TABLE(of, mtk_pcie_ids);
|
|
|
+--- a/drivers/phy/Kconfig
|
|
|
++++ b/drivers/phy/Kconfig
|
|
|
+@@ -82,6 +82,17 @@ config PHY_AIROHA_PCIE
|
|
|
+ This driver create the basic PHY instance and provides initialize
|
|
|
+ callback for PCIe GEN3 port.
|
|
|
+
|
|
|
++config PHY_EN7528_PCIE
|
|
|
++ tristate "EcoNet EN7528 PCIe PHY Driver"
|
|
|
++ depends on ECONET || COMPILE_TEST
|
|
|
++ depends on OF
|
|
|
++ select GENERIC_PHY
|
|
|
++ select REGMAP_MMIO
|
|
|
++ help
|
|
|
++ Say Y here to add support for EcoNet EN7528 PCIe PHY driver.
|
|
|
++ This driver provides PHY initialization for the two PCIe ports
|
|
|
++ on EN7528 SoC.
|
|
|
++
|
|
|
+ source "drivers/phy/allwinner/Kconfig"
|
|
|
+ source "drivers/phy/amlogic/Kconfig"
|
|
|
+ source "drivers/phy/broadcom/Kconfig"
|
|
|
+--- a/drivers/phy/Makefile
|
|
|
++++ b/drivers/phy/Makefile
|
|
|
+@@ -11,6 +11,7 @@ obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
|
|
|
+ obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
|
|
|
+ obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
|
|
|
+ obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
|
|
|
++obj-$(CONFIG_PHY_EN7528_PCIE) += phy-en7528-pcie.o
|
|
|
+ obj-y += allwinner/ \
|
|
|
+ amlogic/ \
|
|
|
+ broadcom/ \
|
|
|
+--- /dev/null
|
|
|
++++ b/drivers/phy/phy-en7528-pcie.c
|
|
|
+@@ -0,0 +1,119 @@
|
|
|
++// SPDX-License-Identifier: GPL-2.0+
|
|
|
++/*
|
|
|
++ * Copyright (C) 2026 Ahmed Naseef <[email protected]>
|
|
|
++ *
|
|
|
++ * EcoNet EN7528 PCIe PHY Driver
|
|
|
++ */
|
|
|
++
|
|
|
++#include <linux/bitops.h>
|
|
|
++#include <linux/module.h>
|
|
|
++#include <linux/of.h>
|
|
|
++#include <linux/phy/phy.h>
|
|
|
++#include <linux/platform_device.h>
|
|
|
++#include <linux/regmap.h>
|
|
|
++
|
|
|
++struct en7528_pcie_phy_data {
|
|
|
++ u32 reg;
|
|
|
++ u32 mask;
|
|
|
++ u32 val;
|
|
|
++ u32 max_reg;
|
|
|
++};
|
|
|
++
|
|
|
++struct en7528_pcie_phy {
|
|
|
++ struct regmap *regmap;
|
|
|
++ const struct en7528_pcie_phy_data *data;
|
|
|
++};
|
|
|
++
|
|
|
++/* Port 0 PHY: set LCDDS_CLK_PH_INV for PLL operation */
|
|
|
++static const struct en7528_pcie_phy_data en7528_phy_port0 = {
|
|
|
++ .reg = 0x4a0,
|
|
|
++ .mask = BIT(5),
|
|
|
++ .val = BIT(5),
|
|
|
++ .max_reg = 0x4a0,
|
|
|
++};
|
|
|
++
|
|
|
++/* Port 1 PHY: Rx impedance tuning, target R -5 Ohm */
|
|
|
++static const struct en7528_pcie_phy_data en7528_phy_port1 = {
|
|
|
++ .reg = 0xb2c,
|
|
|
++ .mask = GENMASK(13, 12),
|
|
|
++ .val = BIT(12),
|
|
|
++ .max_reg = 0xb2c,
|
|
|
++};
|
|
|
++
|
|
|
++static int en7528_pcie_phy_init(struct phy *phy)
|
|
|
++{
|
|
|
++ struct en7528_pcie_phy *ephy = phy_get_drvdata(phy);
|
|
|
++ const struct en7528_pcie_phy_data *data = ephy->data;
|
|
|
++
|
|
|
++ return regmap_update_bits(ephy->regmap, data->reg,
|
|
|
++ data->mask, data->val);
|
|
|
++}
|
|
|
++
|
|
|
++static const struct phy_ops en7528_pcie_phy_ops = {
|
|
|
++ .init = en7528_pcie_phy_init,
|
|
|
++ .owner = THIS_MODULE,
|
|
|
++};
|
|
|
++
|
|
|
++static int en7528_pcie_phy_probe(struct platform_device *pdev)
|
|
|
++{
|
|
|
++ struct device *dev = &pdev->dev;
|
|
|
++ const struct en7528_pcie_phy_data *data;
|
|
|
++ struct regmap_config regmap_config = {
|
|
|
++ .reg_bits = 32,
|
|
|
++ .val_bits = 32,
|
|
|
++ .reg_stride = 4,
|
|
|
++ };
|
|
|
++ struct phy_provider *provider;
|
|
|
++ struct en7528_pcie_phy *ephy;
|
|
|
++ void __iomem *base;
|
|
|
++ struct phy *phy;
|
|
|
++
|
|
|
++ data = of_device_get_match_data(dev);
|
|
|
++ if (!data)
|
|
|
++ return -EINVAL;
|
|
|
++
|
|
|
++ ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
|
|
|
++ if (!ephy)
|
|
|
++ return -ENOMEM;
|
|
|
++
|
|
|
++ ephy->data = data;
|
|
|
++
|
|
|
++ base = devm_platform_ioremap_resource(pdev, 0);
|
|
|
++ if (IS_ERR(base))
|
|
|
++ return PTR_ERR(base);
|
|
|
++
|
|
|
++ regmap_config.max_register = data->max_reg;
|
|
|
++ ephy->regmap = devm_regmap_init_mmio(dev, base, ®map_config);
|
|
|
++ if (IS_ERR(ephy->regmap))
|
|
|
++ return PTR_ERR(ephy->regmap);
|
|
|
++
|
|
|
++ phy = devm_phy_create(dev, dev->of_node, &en7528_pcie_phy_ops);
|
|
|
++ if (IS_ERR(phy))
|
|
|
++ return PTR_ERR(phy);
|
|
|
++
|
|
|
++ phy_set_drvdata(phy, ephy);
|
|
|
++
|
|
|
++ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
|
++
|
|
|
++ return PTR_ERR_OR_ZERO(provider);
|
|
|
++}
|
|
|
++
|
|
|
++static const struct of_device_id en7528_pcie_phy_ids[] = {
|
|
|
++ { .compatible = "econet,en7528-pcie-phy0", .data = &en7528_phy_port0 },
|
|
|
++ { .compatible = "econet,en7528-pcie-phy1", .data = &en7528_phy_port1 },
|
|
|
++ { /* sentinel */ }
|
|
|
++};
|
|
|
++MODULE_DEVICE_TABLE(of, en7528_pcie_phy_ids);
|
|
|
++
|
|
|
++static struct platform_driver en7528_pcie_phy_driver = {
|
|
|
++ .probe = en7528_pcie_phy_probe,
|
|
|
++ .driver = {
|
|
|
++ .name = "en7528-pcie-phy",
|
|
|
++ .of_match_table = en7528_pcie_phy_ids,
|
|
|
++ },
|
|
|
++};
|
|
|
++module_platform_driver(en7528_pcie_phy_driver);
|
|
|
++
|
|
|
++MODULE_AUTHOR("Ahmed Naseef <[email protected]>");
|
|
|
++MODULE_DESCRIPTION("EcoNet EN7528 PCIe PHY driver");
|
|
|
++MODULE_LICENSE("GPL v2");
|