]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: stmmac: Add glue driver for Motorcomm YT6801 ethernet controller
authorYao Zi <me@ziyao.cc>
Fri, 9 Jan 2026 09:34:45 +0000 (09:34 +0000)
committerJakub Kicinski <kuba@kernel.org>
Tue, 13 Jan 2026 03:20:56 +0000 (19:20 -0800)
Motorcomm YT6801 is a PCIe ethernet controller based on DWMAC4 IP. It
integrates an GbE phy, supporting WOL, VLAN tagging and various types
of offloading. It ships an on-chip eFuse for storing various vendor
configuration, including MAC address.

This patch adds basic glue code for the controller, allowing it to be
set up and transmit data at a reasonable speed. Features like WOL could
be implemented in the future.

Signed-off-by: Yao Zi <me@ziyao.cc>
Tested-by: Mingcong Bai <jeffbai@aosc.io>
Tested-by: Runhua He <hua@aosc.io>
Tested-by: Xi Ruoyao <xry111@xry111.site>
Reviewed-by: Sai Krishna <saikrishnag@marvell.com>
Link: https://patch.msgid.link/20260109093445.46791-4-me@ziyao.cc
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/stmicro/stmmac/Kconfig
drivers/net/ethernet/stmicro/stmmac/Makefile
drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c [new file with mode: 0644]

index 907fe2e927f015542b6a7228eae9a6c6945be3ac..07088d03dbab5bd1abf66e9460613b839c1d565e 100644 (file)
@@ -374,6 +374,15 @@ config DWMAC_LOONGSON
          This selects the LOONGSON PCI bus support for the stmmac driver,
          Support for ethernet controller on Loongson-2K1000 SoC and LS7A1000 bridge.
 
+config DWMAC_MOTORCOMM
+       tristate "Motorcomm PCI DWMAC support"
+       depends on PCI
+       select MOTORCOMM_PHY
+       select STMMAC_LIBPCI
+       help
+         This enables glue driver for Motorcomm DWMAC-based PCI Ethernet
+         controllers. Currently only YT6801 is supported.
+
 config STMMAC_PCI
        tristate "STMMAC PCI bus support"
        depends on PCI
index 7bf528731034b64281248bc5f39fd60e52d43e9c..c9263987ef8d220adae2855c0046760da49c6bf9 100644 (file)
@@ -48,4 +48,5 @@ obj-$(CONFIG_STMMAC_LIBPCI)   += stmmac_libpci.o
 obj-$(CONFIG_STMMAC_PCI)       += stmmac-pci.o
 obj-$(CONFIG_DWMAC_INTEL)      += dwmac-intel.o
 obj-$(CONFIG_DWMAC_LOONGSON)   += dwmac-loongson.o
+obj-$(CONFIG_DWMAC_MOTORCOMM)  += dwmac-motorcomm.o
 stmmac-pci-objs:= stmmac_pci.o
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
new file mode 100644 (file)
index 0000000..8b45b9c
--- /dev/null
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DWMAC glue driver for Motorcomm PCI Ethernet controllers
+ *
+ * Copyright (c) 2025-2026 Yao Zi <me@ziyao.cc>
+ */
+
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/stmmac.h>
+
+#include "dwmac4.h"
+#include "stmmac.h"
+#include "stmmac_libpci.h"
+
+#define DRIVER_NAME "dwmac-motorcomm"
+
+#define PCI_VENDOR_ID_MOTORCOMM                        0x1f0a
+
+/* Register definition */
+#define EPHY_CTRL                              0x1004
+/* Clearing this bit asserts resets for internal MDIO bus and PHY */
+#define  EPHY_MDIO_PHY_RESET                   BIT(0)
+#define OOB_WOL_CTRL                           0x1010
+#define  OOB_WOL_CTRL_DIS                      BIT(0)
+#define MGMT_INT_CTRL0                         0x1100
+#define INT_MODERATION                         0x1108
+#define  INT_MODERATION_RX                     GENMASK(11, 0)
+#define  INT_MODERATION_TX                     GENMASK(27, 16)
+#define EFUSE_OP_CTRL_0                                0x1500
+#define  EFUSE_OP_MODE                         GENMASK(1, 0)
+#define   EFUSE_OP_ROW_READ                    0x1
+#define  EFUSE_OP_START                                BIT(2)
+#define  EFUSE_OP_ADDR                         GENMASK(15, 8)
+#define EFUSE_OP_CTRL_1                                0x1504
+#define  EFUSE_OP_DONE                         BIT(1)
+#define  EFUSE_OP_RD_DATA                      GENMASK(31, 24)
+#define SYS_RESET                              0x152c
+#define  SYS_RESET_RESET                       BIT(31)
+#define GMAC_OFFSET                            0x2000
+
+/* Constants */
+#define EFUSE_READ_TIMEOUT_US                  20000
+#define EFUSE_PATCH_REGION_OFFSET              18
+#define EFUSE_PATCH_MAX_NUM                    39
+#define EFUSE_ADDR_MACA0LR                     0x1520
+#define EFUSE_ADDR_MACA0HR                     0x1524
+
+struct motorcomm_efuse_patch {
+       __le16 addr;
+       __le32 data;
+} __packed;
+
+struct dwmac_motorcomm_priv {
+       void __iomem *base;
+};
+
+static int motorcomm_efuse_read_byte(struct dwmac_motorcomm_priv *priv,
+                                    u8 offset, u8 *byte)
+{
+       u32 reg;
+       int ret;
+
+       writel(FIELD_PREP(EFUSE_OP_MODE, EFUSE_OP_ROW_READ)     |
+              FIELD_PREP(EFUSE_OP_ADDR, offset)                |
+              EFUSE_OP_START, priv->base + EFUSE_OP_CTRL_0);
+
+       ret = readl_poll_timeout(priv->base + EFUSE_OP_CTRL_1,
+                                reg, reg & EFUSE_OP_DONE, 2000,
+                                EFUSE_READ_TIMEOUT_US);
+
+       *byte = FIELD_GET(EFUSE_OP_RD_DATA, reg);
+
+       return ret;
+}
+
+static int motorcomm_efuse_read_patch(struct dwmac_motorcomm_priv *priv,
+                                     u8 index,
+                                     struct motorcomm_efuse_patch *patch)
+{
+       u8 *p = (u8 *)patch, offset;
+       int i, ret;
+
+       for (i = 0; i < sizeof(*patch); i++) {
+               offset = EFUSE_PATCH_REGION_OFFSET + sizeof(*patch) * index + i;
+
+               ret = motorcomm_efuse_read_byte(priv, offset, &p[i]);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int motorcomm_efuse_get_patch_value(struct dwmac_motorcomm_priv *priv,
+                                          u16 addr, u32 *value)
+{
+       struct motorcomm_efuse_patch patch;
+       int i, ret;
+
+       for (i = 0; i < EFUSE_PATCH_MAX_NUM; i++) {
+               ret = motorcomm_efuse_read_patch(priv, i, &patch);
+               if (ret)
+                       return ret;
+
+               if (patch.addr == 0) {
+                       return -ENOENT;
+               } else if (le16_to_cpu(patch.addr) == addr) {
+                       *value = le32_to_cpu(patch.data);
+                       return 0;
+               }
+       }
+
+       return -ENOENT;
+}
+
+static int motorcomm_efuse_read_mac(struct device *dev,
+                                   struct dwmac_motorcomm_priv *priv, u8 *mac)
+{
+       u32 maca0lr, maca0hr;
+       int ret;
+
+       ret = motorcomm_efuse_get_patch_value(priv, EFUSE_ADDR_MACA0LR,
+                                             &maca0lr);
+       if (ret)
+               return dev_err_probe(dev, ret,
+                                    "failed to read maca0lr from eFuse\n");
+
+       ret = motorcomm_efuse_get_patch_value(priv, EFUSE_ADDR_MACA0HR,
+                                             &maca0hr);
+       if (ret)
+               return dev_err_probe(dev, ret,
+                                    "failed to read maca0hr from eFuse\n");
+
+       mac[0] = FIELD_GET(GENMASK(15, 8), maca0hr);
+       mac[1] = FIELD_GET(GENMASK(7, 0), maca0hr);
+       mac[2] = FIELD_GET(GENMASK(31, 24), maca0lr);
+       mac[3] = FIELD_GET(GENMASK(23, 16), maca0lr);
+       mac[4] = FIELD_GET(GENMASK(15, 8), maca0lr);
+       mac[5] = FIELD_GET(GENMASK(7, 0), maca0lr);
+
+       return 0;
+}
+
+static void motorcomm_deassert_mdio_phy_reset(struct dwmac_motorcomm_priv *priv)
+{
+       u32 reg = readl(priv->base + EPHY_CTRL);
+
+       reg |= EPHY_MDIO_PHY_RESET;
+
+       writel(reg, priv->base + EPHY_CTRL);
+}
+
+static void motorcomm_reset(struct dwmac_motorcomm_priv *priv)
+{
+       u32 reg = readl(priv->base + SYS_RESET);
+
+       reg &= ~SYS_RESET_RESET;
+       writel(reg, priv->base + SYS_RESET);
+
+       reg |= SYS_RESET_RESET;
+       writel(reg, priv->base + SYS_RESET);
+
+       motorcomm_deassert_mdio_phy_reset(priv);
+}
+
+static void motorcomm_init(struct dwmac_motorcomm_priv *priv)
+{
+       writel(0x0, priv->base + MGMT_INT_CTRL0);
+
+       writel(FIELD_PREP(INT_MODERATION_RX, 200) |
+              FIELD_PREP(INT_MODERATION_TX, 200),
+              priv->base + INT_MODERATION);
+
+       /*
+        * OOB WOL must be disabled during normal operation, or DMA interrupts
+        * cannot be delivered to the host.
+        */
+       writel(OOB_WOL_CTRL_DIS, priv->base + OOB_WOL_CTRL);
+}
+
+static int motorcomm_resume(struct device *dev, void *bsp_priv)
+{
+       struct dwmac_motorcomm_priv *priv = bsp_priv;
+       int ret;
+
+       ret = stmmac_pci_plat_resume(dev, bsp_priv);
+       if (ret)
+               return ret;
+
+       /*
+        * When recovering from D3hot, EPHY_MDIO_PHY_RESET is automatically
+        * asserted, and must be deasserted for normal operation.
+        */
+       motorcomm_deassert_mdio_phy_reset(priv);
+       motorcomm_init(priv);
+
+       return 0;
+}
+
+static struct plat_stmmacenet_data *
+motorcomm_default_plat_data(struct pci_dev *pdev)
+{
+       struct plat_stmmacenet_data *plat;
+       struct device *dev = &pdev->dev;
+
+       plat = stmmac_plat_dat_alloc(dev);
+       if (!plat)
+               return NULL;
+
+       plat->mdio_bus_data = devm_kzalloc(dev, sizeof(*plat->mdio_bus_data),
+                                          GFP_KERNEL);
+       if (!plat->mdio_bus_data)
+               return NULL;
+
+       plat->dma_cfg = devm_kzalloc(dev, sizeof(*plat->dma_cfg), GFP_KERNEL);
+       if (!plat->dma_cfg)
+               return NULL;
+
+       plat->axi = devm_kzalloc(dev, sizeof(*plat->axi), GFP_KERNEL);
+       if (!plat->axi)
+               return NULL;
+
+       plat->dma_cfg->pbl              = DEFAULT_DMA_PBL;
+       plat->dma_cfg->pblx8            = true;
+       plat->dma_cfg->txpbl            = 32;
+       plat->dma_cfg->rxpbl            = 32;
+       plat->dma_cfg->eame             = true;
+       plat->dma_cfg->mixed_burst      = true;
+
+       plat->axi->axi_wr_osr_lmt       = 1;
+       plat->axi->axi_rd_osr_lmt       = 1;
+       plat->axi->axi_mb               = true;
+       plat->axi->axi_blen_regval      = DMA_AXI_BLEN4 | DMA_AXI_BLEN8 |
+                                         DMA_AXI_BLEN16 | DMA_AXI_BLEN32;
+
+       plat->bus_id            = pci_dev_id(pdev);
+       plat->phy_interface     = PHY_INTERFACE_MODE_GMII;
+       /*
+        * YT6801 requires an 25MHz clock input/oscillator to function, which
+        * is likely the source of CSR clock.
+        */
+       plat->clk_csr           = STMMAC_CSR_20_35M;
+       plat->tx_coe            = 1;
+       plat->rx_coe            = 1;
+       plat->clk_ref_rate      = 125000000;
+       plat->core_type         = DWMAC_CORE_GMAC4;
+       plat->suspend           = stmmac_pci_plat_suspend;
+       plat->resume            = motorcomm_resume;
+       plat->flags             = STMMAC_FLAG_TSO_EN |
+                                 STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP;
+
+       return plat;
+}
+
+static void motorcomm_free_irq(void *data)
+{
+       struct pci_dev *pdev = data;
+
+       pci_free_irq_vectors(pdev);
+}
+
+static int motorcomm_setup_irq(struct pci_dev *pdev,
+                              struct stmmac_resources *res,
+                              struct plat_stmmacenet_data *plat)
+{
+       int ret;
+
+       ret = pci_alloc_irq_vectors(pdev, 6, 6, PCI_IRQ_MSIX);
+       if (ret > 0) {
+               res->rx_irq[0]  = pci_irq_vector(pdev, 0);
+               res->tx_irq[0]  = pci_irq_vector(pdev, 4);
+               res->irq        = pci_irq_vector(pdev, 5);
+
+               plat->flags |= STMMAC_FLAG_MULTI_MSI_EN;
+       } else {
+               dev_info(&pdev->dev, "failed to allocate MSI-X vector: %d\n",
+                        ret);
+               dev_info(&pdev->dev, "try MSI instead\n");
+
+               ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+               if (ret < 0)
+                       return dev_err_probe(&pdev->dev, ret,
+                                            "failed to allocate MSI\n");
+
+               res->irq = pci_irq_vector(pdev, 0);
+       }
+
+       return devm_add_action_or_reset(&pdev->dev, motorcomm_free_irq, pdev);
+}
+
+static int motorcomm_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+       struct plat_stmmacenet_data *plat;
+       struct dwmac_motorcomm_priv *priv;
+       struct stmmac_resources res = {};
+       int ret;
+
+       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       plat = motorcomm_default_plat_data(pdev);
+       if (!plat)
+               return -ENOMEM;
+
+       plat->bsp_priv = priv;
+
+       ret = pcim_enable_device(pdev);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to enable device\n");
+
+       priv->base = pcim_iomap_region(pdev, 0, DRIVER_NAME);
+       if (IS_ERR(priv->base))
+               return dev_err_probe(&pdev->dev, PTR_ERR(priv->base),
+                                    "failed to map IO region\n");
+
+       pci_set_master(pdev);
+
+       /*
+        * Some PCIe addons cards based on YT6801 don't deliver MSI(X) with ASPM
+        * enabled. Sadly there isn't a reliable way to read out OEM of the
+        * card, so let's disable L1 state unconditionally for safety.
+        */
+       ret = pci_disable_link_state(pdev, PCIE_LINK_STATE_L1);
+       if (ret)
+               dev_warn(&pdev->dev, "failed to disable L1 state: %d\n", ret);
+
+       motorcomm_reset(priv);
+
+       ret = motorcomm_efuse_read_mac(&pdev->dev, priv, res.mac);
+       if (ret == -ENOENT) {
+               dev_warn(&pdev->dev, "eFuse contains no valid MAC address\n");
+               dev_warn(&pdev->dev, "fallback to random MAC address\n");
+
+               eth_random_addr(res.mac);
+       } else if (ret) {
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to read MAC address from eFuse\n");
+       }
+
+       ret = motorcomm_setup_irq(pdev, &res, plat);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to setup IRQ\n");
+
+       motorcomm_init(priv);
+
+       res.addr = priv->base + GMAC_OFFSET;
+
+       return stmmac_dvr_probe(&pdev->dev, plat, &res);
+}
+
+static void motorcomm_remove(struct pci_dev *pdev)
+{
+       stmmac_dvr_remove(&pdev->dev);
+}
+
+static const struct pci_device_id dwmac_motorcomm_pci_id_table[] = {
+       { PCI_DEVICE(PCI_VENDOR_ID_MOTORCOMM, 0x6801) },
+       { },
+};
+MODULE_DEVICE_TABLE(pci, dwmac_motorcomm_pci_id_table);
+
+static struct pci_driver dwmac_motorcomm_pci_driver = {
+       .name = DRIVER_NAME,
+       .id_table = dwmac_motorcomm_pci_id_table,
+       .probe = motorcomm_probe,
+       .remove = motorcomm_remove,
+       .driver = {
+               .pm = &stmmac_simple_pm_ops,
+       },
+};
+
+module_pci_driver(dwmac_motorcomm_pci_driver);
+
+MODULE_DESCRIPTION("DWMAC glue driver for Motorcomm PCI Ethernet controllers");
+MODULE_AUTHOR("Yao Zi <me@ziyao.cc>");
+MODULE_LICENSE("GPL");