Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Yao Zi 1736 99.37% 1 50.00%
Johan Alvarado 11 0.63% 1 50.00%
Total 1747 2


// 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/delay.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);

	/*
	 * After system reset, the eFuse controller needs time to load
	 * its internal data. Without this delay, eFuse reads return
	 * all zeros, causing MAC address detection to fail.
	 */
	usleep_range(2000, 5000);

	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");