[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID:
<BYAPR18MB3735FED933ED0F7536C07A59A081A@BYAPR18MB3735.namprd18.prod.outlook.com>
Date: Mon, 12 Jan 2026 06:10:26 +0000
From: Sai Krishna Gajula <saikrishnag@...vell.com>
To: Yao Zi <me@...ao.cc>, Andrew Lunn <andrew+netdev@...n.ch>,
"David S.
Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>, Jakub
Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>, Frank
<Frank.Sae@...or-comm.com>,
Heiner Kallweit <hkallweit1@...il.com>,
Russell
King <linux@...linux.org.uk>,
"Russell King (Oracle)"
<rmk+kernel@...linux.org.uk>,
Vladimir Oltean <vladimir.oltean@....com>,
Chen-Yu Tsai <wens@...e.org>, Jisheng Zhang <jszhang@...nel.org>,
Furong Xu
<0x1207@...il.com>
CC: "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
"netdev@...r.kernel.org" <netdev@...r.kernel.org>,
Mingcong Bai
<jeffbai@...c.io>, Kexy Biscuit <kexybiscuit@...c.io>,
Runhua He
<hua@...c.io>, Xi Ruoyao <xry111@...111.site>
Subject: RE: [PATCH RESEND net-next v6 2/3] net: stmmac: Add glue driver for
Motorcomm YT6801 ethernet controller
> -----Original Message-----
> From: Yao Zi <me@...ao.cc>
> Sent: Friday, January 9, 2026 3:05 PM
> To: Andrew Lunn <andrew+netdev@...n.ch>; David S. Miller
> <davem@...emloft.net>; Eric Dumazet <edumazet@...gle.com>; Jakub
> Kicinski <kuba@...nel.org>; Paolo Abeni <pabeni@...hat.com>; Frank
> <Frank.Sae@...or-comm.com>; Heiner Kallweit <hkallweit1@...il.com>;
> Russell King <linux@...linux.org.uk>; Russell King (Oracle)
> <rmk+kernel@...linux.org.uk>; Vladimir Oltean
> <vladimir.oltean@....com>; Chen-Yu Tsai <wens@...e.org>; Jisheng Zhang
> <jszhang@...nel.org>; Furong Xu <0x1207@...il.com>
> Cc: linux-kernel@...r.kernel.org; netdev@...r.kernel.org; Mingcong Bai
> <jeffbai@...c.io>; Kexy Biscuit <kexybiscuit@...c.io>; Yao Zi <me@...ao.cc>;
> Runhua He <hua@...c.io>; Xi Ruoyao <xry111@...111.site>
> Subject: [PATCH RESEND net-next v6 2/3] net: stmmac: Add glue
> driver for Motorcomm YT6801 ethernet controller
>
> 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.
> 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@...ao.cc>
> Tested-by: Mingcong Bai <jeffbai@...c.io>
> Tested-by: Runhua He <hua@...c.io>
> Tested-by: Xi Ruoyao <xry111@...111.site>
> ---
> drivers/net/ethernet/stmicro/stmmac/Kconfig | 9 +
> drivers/net/ethernet/stmicro/stmmac/Makefile | 1 +
> .../ethernet/stmicro/stmmac/dwmac-motorcomm.c | 384
> ++++++++++++++++++
> 3 files changed, 394 insertions(+)
> create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-
> motorcomm.c
>
> diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig
> b/drivers/net/ethernet/stmicro/stmmac/Kconfig
> index 907fe2e927f0..07088d03dbab 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
> +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
> @@ -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
> diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile
> b/drivers/net/ethernet/stmicro/stmmac/Makefile
> index 7bf528731034..c9263987ef8d 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/Makefile
> +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile
> @@ -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
> index 000000000000..8b45b9cf7202
> --- /dev/null
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
> @@ -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@...ao.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) {
Minor nit
This format breaks kernel style function definition, use as below ( "{" in next line)
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) {
This format breaks kernel style function definition, use as below ( "{" in next line), same in other places.
> + 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);
> + }
If possible, add enum for MAX MSIX count (in this case 6 ), or dynamic values as per hardware.
> +
> + 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@...ao.cc>");
> +MODULE_LICENSE("GPL");
> --
> 2.52.0
>
Reviewed-by: Sai Krishna <saikrishnag@...vell.com>
Powered by blists - more mailing lists