From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on sa.local.altlinux.org X-Spam-Level: X-Spam-Status: No, score=-3.4 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,RP_MATCHES_RCVD autolearn=ham autolearn_force=no version=3.4.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=basealt.ru; s=dkim; t=1772188374; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Uh9fuAgzoDVuV/5re1CmJuB6b8YDhtoU5k+G66SXHCM=; b=Fil2nYtH9VFS20UKr7Zj6QK2hsn1UZZqsmEBP12N1IA4uCkTRDlKmEjHs6Kllf3I84hIA8 5g0BTi5A6+26ZX2U+nFeh2ljrtDRWsQ8IqNtGVZg+nFhrjttEz1W/YBiHUX52lI/KNYSeU KOfAQohAn/NRtHbQKQh+X1mbYdsc7Z+bEU/2X36dYchE/x9yHoiVITtXIzhtOHtTJcSuKC y5pnkGQK+AnsMTU3Hf4fBXoPjl3qUqZzkOeLpnfvwRm02FYyUXG3egdwhvW5ED7QjVuGI9 5TzmOl8Z9+cwexzXLZs2mM5TsNDSBpUilTAFBEBsOL8a5hrUPkc+gAAWWaN8rQ== From: Daniil Gnusarev To: gnusarevda@basealt.ru, devel-kernel@lists.altlinux.org Date: Fri, 27 Feb 2026 14:32:10 +0400 Message-ID: <20260227103236.785736-10-gnusarevda@basealt.ru> X-Mailer: git-send-email 2.42.2 In-Reply-To: <20260227103236.785736-1-gnusarevda@basealt.ru> References: <20260227103236.785736-1-gnusarevda@basealt.ru> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [d-kernel] [PATCH 09/35] net: stmmac: support of Baikal-BE1000 SoCs GMAC X-BeenThere: devel-kernel@lists.altlinux.org X-Mailman-Version: 2.1.12 Precedence: list Reply-To: ALT Linux kernel packages development List-Id: ALT Linux kernel packages development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 27 Feb 2026 10:33:04 -0000 X-List-Received-Date: Fri, 27 Feb 2026 10:33:04 -0000 X-List-Received-Date: Fri, 27 Feb 2026 10:33:04 -0000 Archived-At: List-Archive: List-Post: The Gigabit Ethernet Controller available in the Baikal-M SoC is a Synopsys DesignWare MAC IP core, already supported by the stmmac driver. This add Baikal Electronics DWMAC specific glue layer. Taken from SDK ARM64-2403-6.6. Updated for version 6.18. Signed-off-by: Daniil Gnusarev Co-developed-by: Dmitry Dunaev Co-developed-by: Alexey Sheplyakov Do-not-upstream: this is a feature of Baikal-M --- drivers/net/ethernet/stmicro/stmmac/Kconfig | 8 + drivers/net/ethernet/stmicro/stmmac/Makefile | 1 + .../ethernet/stmicro/stmmac/dwmac-baikal.c | 500 ++++++++++++++++++ .../ethernet/stmicro/stmmac/dwmac1000_core.c | 1 + net/ethernet/eth.c | 1 + 5 files changed, 511 insertions(+) create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 9507131875b2ca..3f1a3248fe15d6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -67,6 +67,14 @@ config DWMAC_ANARION This selects the Anarion SoC glue layer support for the stmmac driver. +config DWMAC_BAIKAL + tristate "Baikal Electronics DWMAC support" + depends on OF + help + Support for Baikal Electronics DWMAC Ethernet. + + This selects the Baikal SoC glue layer support for the stmmac driver. + config DWMAC_INGENIC tristate "Ingenic MAC support" default MACH_INGENIC diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index 51e068e26ce499..4dada88828ef3a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -14,6 +14,7 @@ stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o # Ordering matters. Generic driver must be last. obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o obj-$(CONFIG_DWMAC_ANARION) += dwmac-anarion.o +obj-$(CONFIG_DWMAC_BAIKAL) += dwmac-baikal.o obj-$(CONFIG_DWMAC_INGENIC) += dwmac-ingenic.o obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c new file mode 100644 index 00000000000000..788ad01e623615 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Baikal Electronics DWMAC specific glue layer + * + * Copyright (C) 2015-2022 Baikal Electronics, JSC + * Authors: Dmitry Dunaev + * Alexey Sheplyakov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stmmac.h" +#include "stmmac_platform.h" +#include "common.h" +#include "dwmac_dma.h" + +#define MAC_GPIO 0x00e0 /* GPIO register */ +#define MAC_GPIO_GPO BIT(8) /* Output port */ + +#define BAIKAL_SMC_GMAC_DIV2_ENABLE 0xC2000500 +#define BAIKAL_SMC_GMAC_DIV2_DISABLE 0xC2000501 + +struct baikal_gmac { + struct device *dev; + u64 base; + struct clk *axi_clk; + struct clk *tx2_clk; + int has_aux_div2; + bool is_fixed_stmmac_clk; +}; + +static int baikal_gmac_dma_reset(void __iomem *ioaddr) +{ + int err; + u32 value; + + /* DMA SW reset */ + value = readl(ioaddr + DMA_BUS_MODE); + value |= DMA_BUS_MODE_SFT_RESET; + writel(value, ioaddr + DMA_BUS_MODE); + + /* Software DMA reset also resets MAC, so GP_OUT is set to zero. + * Which resets PHY as a side effect (if GP_OUT is connected directly + * to PHY reset). + * TODO: read the PHY reset duration from the device tree. + * Meanwhile use 100 milliseconds which seems to be enough for + * most PHYs + */ + usleep_range(100000, 120000); + + /* Clear PHY reset */ + value = readl(ioaddr + MAC_GPIO); + value |= MAC_GPIO_GPO; + writel(value, ioaddr + MAC_GPIO); + + /* Many PHYs need ~100 milliseconds to calm down after PHY reset + * has been cleared. And check for DMA reset below might return + * much earlier (i.e. in ~20 milliseconds). As a result reading + * PHY registers (after this function returns) might return garbage. + * Wait a bit to avoid the problem. + */ + usleep_range(100000, 150000); + + err = readl_poll_timeout(ioaddr + DMA_BUS_MODE, value, + !(value & DMA_BUS_MODE_SFT_RESET), + 10000, 1000000); + if (err) + return -EBUSY; + + return 0; +} + +static struct mac_device_info *baikal_gmac_setup(void *ppriv) +{ + struct stmmac_dma_ops *dma; + struct mac_device_info *mac, *old_mac; + struct stmmac_priv *priv = ppriv; + struct gpio_desc *reset_gpio; + int err; + u32 value; + + mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL); + if (!mac) + return NULL; + + dma = devm_kzalloc(priv->device, sizeof(*dma), GFP_KERNEL); + if (!dma) + return NULL; + + /* Clear PHY reset */ + value = readl(priv->ioaddr + MAC_GPIO); + value |= MAC_GPIO_GPO; + writel(value, priv->ioaddr + MAC_GPIO); + reset_gpio = devm_gpiod_get_optional(priv->device, + "snps,reset", + GPIOD_OUT_LOW); + + err = readl_poll_timeout(priv->ioaddr + DMA_BUS_MODE, value, + !(value & DMA_BUS_MODE_SFT_RESET), + 10000, 1000000); + + if (reset_gpio) + devm_gpiod_put(priv->device, reset_gpio); + + if (err) { + dev_err(priv->device, "SW reset is not cleared: error %d", err); + return NULL; + } + + *dma = dwmac1000_dma_ops; + dma->reset = baikal_gmac_dma_reset; + mac->dma = dma; + old_mac = priv->hw; + priv->hw = mac; + err = dwmac1000_setup(priv); + priv->hw = old_mac; + if (err) { + dev_err(priv->device, + "%s: dwmac1000_setup failed with error %d", + __func__, err); + return NULL; + } + + return mac; +} + +static void baikal_gmac_fix_mac_speed(void *priv, int speed, unsigned int mode) +{ + struct arm_smccc_res res; + struct baikal_gmac *gmac = priv; + unsigned long tx2_clk_freq = 0; + + switch (speed) { + case SPEED_1000: + tx2_clk_freq = 250000000; + if (gmac->has_aux_div2) { + arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_DISABLE, + gmac->base, 0, 0, 0, 0, 0, 0, &res); + } + break; + case SPEED_100: + tx2_clk_freq = 50000000; + if (gmac->has_aux_div2) { + arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_DISABLE, + gmac->base, 0, 0, 0, 0, 0, 0, &res); + } + break; + case SPEED_10: + tx2_clk_freq = 5000000; + if (gmac->has_aux_div2) { + tx2_clk_freq *= 2; + arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_ENABLE, + gmac->base, 0, 0, 0, 0, 0, 0, &res); + } + break; + } + + if (gmac->tx2_clk && tx2_clk_freq) + clk_set_rate(gmac->tx2_clk, tx2_clk_freq); +} + +#ifdef CONFIG_ACPI +static struct plat_stmmacenet_data *baikal_stmmac_probe_config(struct device *dev, + const char **mac, + bool *is_fixed_stmmac_clk) +{ + struct plat_stmmacenet_data *plat_dat; + u8 nvmem_mac[ETH_ALEN]; + int ret; + u32 clock_rate; + bool is_fixed_clk = false; + + plat_dat = devm_kzalloc(dev, sizeof(*plat_dat), GFP_KERNEL); + if (!plat_dat) + return ERR_PTR(-ENOMEM); + + ret = nvmem_get_mac_address(dev, &nvmem_mac); + if (ret) { + if (ret == -EPROBE_DEFER) + return ERR_PTR(ret); + *mac = NULL; + } else { + *mac = devm_kmemdup(dev, nvmem_mac, ETH_ALEN, GFP_KERNEL); + } + + plat_dat->phy_interface = device_get_phy_mode(dev); + if (plat_dat->phy_interface < 0) + return NULL; + + if (device_property_read_u32(dev, "max-speed", &plat_dat->max_speed)) + plat_dat->max_speed = -1; + + plat_dat->bus_id = ACPI_COMPANION(dev)->pnp.instance_no; + + ret = device_property_read_u32(dev, "reg", &plat_dat->phy_addr); + if (ret) { + dev_err(dev, "couldn't get reg property\n"); + return ERR_PTR(ret); + } + + if (plat_dat->phy_addr >= PHY_MAX_ADDR) { + dev_err(dev, "PHY address %i is too large\n", + plat_dat->phy_addr); + return ERR_PTR(-EINVAL); + } + + plat_dat->mdio_bus_data = devm_kzalloc(dev, sizeof(*plat_dat->mdio_bus_data), + GFP_KERNEL); + if (!plat_dat->mdio_bus_data) + return ERR_PTR(-ENOMEM); + + plat_dat->mdio_bus_data->needs_reset = true; + plat_dat->maxmtu = JUMBO_LEN; + plat_dat->multicast_filter_bins = HASH_TABLE_SIZE; + plat_dat->unicast_filter_entries = 1; + plat_dat->bugged_jumbo = 1; /* TODO: is it really required? */ + + plat_dat->dma_cfg = devm_kzalloc(dev, sizeof(*plat_dat->dma_cfg), + GFP_KERNEL); + if (!plat_dat->dma_cfg) + return ERR_PTR(-ENOMEM); + + plat_dat->dma_cfg->pbl = DEFAULT_DMA_PBL; + device_property_read_u32(dev, "snps,txpbl", &plat_dat->dma_cfg->txpbl); + device_property_read_u32(dev, "snps,rxpbl", &plat_dat->dma_cfg->rxpbl); + plat_dat->dma_cfg->fixed_burst = device_property_read_bool(dev, "snps,fixed-burst"); + + plat_dat->axi = devm_kzalloc(dev, sizeof(*plat_dat->axi), GFP_KERNEL); + if (!plat_dat->axi) + return ERR_PTR(-ENOMEM); + + device_property_read_u32_array(dev, "snps,blen", + plat_dat->axi->axi_blen, AXI_BLEN); + + plat_dat->rx_queues_to_use = 1; + plat_dat->tx_queues_to_use = 1; + plat_dat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB; + plat_dat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB; + + if (device_property_read_u32(dev, "stmmac-clk", &clock_rate)) + plat_dat->clk_ptp_rate = 50000000; + else + plat_dat->clk_ptp_rate = clock_rate; + + plat_dat->stmmac_clk = devm_clk_get(dev, STMMAC_RESOURCE_NAME); + if (IS_ERR(plat_dat->stmmac_clk)) { + if (!plat_dat->clk_ptp_rate) { + dev_err(dev, "stmmaceth clock and 'stmmac-clk' property are missed simultaneously\n"); + return ERR_PTR(-EINVAL); + } + + plat_dat->stmmac_clk = clk_register_fixed_rate(NULL, dev_name(dev), + NULL, 0, plat_dat->clk_ptp_rate); + if (IS_ERR(plat_dat->stmmac_clk)) + return ERR_CAST(plat_dat->stmmac_clk); + + is_fixed_clk = true; + } else { + if (!plat_dat->clk_ptp_rate) + plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->stmmac_clk); + } + + plat_dat->clk_ptp_ref = devm_clk_get(dev, "ptp_ref"); + if (IS_ERR(plat_dat->clk_ptp_ref)) + plat_dat->clk_ptp_ref = NULL; + else + plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->clk_ptp_ref); + + clk_prepare_enable(plat_dat->stmmac_clk); + + plat_dat->stmmac_rst = devm_reset_control_get(dev, + STMMAC_RESOURCE_NAME); + if (IS_ERR(plat_dat->stmmac_rst)) + plat_dat->stmmac_rst = NULL; + + plat_dat->mdio_bus_data->phy_mask = ~0; + + if (device_get_child_node_count(dev) != 1) { + clk_disable_unprepare(plat_dat->stmmac_clk); + if (is_fixed_clk) + clk_unregister_fixed_rate(plat_dat->stmmac_clk); + return ERR_PTR(-EINVAL); + } + + *is_fixed_stmmac_clk = is_fixed_clk; + + return plat_dat; +} + +static int baikal_add_mdio_phy(struct device *dev) +{ + struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev)); + struct fwnode_handle *fwnode = device_get_next_child_node(dev, NULL); + struct phy_device *phy; + int ret; + + phy = get_phy_device(priv->mii, priv->plat->phy_addr, 0); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy->irq = priv->mii->irq[priv->plat->phy_addr]; + phy->mdio.dev.fwnode = fwnode; + + ret = phy_device_register(phy); + if (ret) { + phy_device_free(phy); + return ret; + } + + return 0; +} +#else +static struct plat_stmmacenet_data *baikal_stmmac_probe_config(struct device *dev, + const char **mac, + bool *is_fixed_stmmac_clk) +{ + return NULL; +} + +static int baikal_add_mdio_phy(struct device *dev) +{ + return 0; +} +#endif + +static int baikal_gmac_probe(struct platform_device *pdev) +{ + struct plat_stmmacenet_data *plat_dat; + struct stmmac_resources stmmac_res; + struct resource *res; + struct baikal_gmac *gmac; + struct device_node *dn = NULL; + const char *str = NULL; + bool is_fixed_stmmac_clk = false; + int ret; + + if (acpi_disabled) { + ret = stmmac_get_platform_resources(pdev, &stmmac_res); + if (ret) + return ret; + } else { + memset(&stmmac_res, 0, sizeof(stmmac_res)); + stmmac_res.irq = platform_get_irq(pdev, 0); + if (stmmac_res.irq < 0) + return stmmac_res.irq; + + stmmac_res.wol_irq = stmmac_res.irq; + stmmac_res.addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(stmmac_res.addr)) + return PTR_ERR(stmmac_res.addr); + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_warn(&pdev->dev, "no suitable DMA available\n"); + return ret; + } + + if (pdev->dev.of_node) { + plat_dat = devm_stmmac_probe_config_dt(pdev, (u8 *)&stmmac_res.mac); + if (IS_ERR(plat_dat)) { + dev_err(&pdev->dev, "dt configuration failed\n"); + return PTR_ERR(plat_dat); + } + } else if (!acpi_disabled) { + plat_dat = baikal_stmmac_probe_config(&pdev->dev, + (const char **)&stmmac_res.mac, + &is_fixed_stmmac_clk); + if (IS_ERR(plat_dat)) { + dev_err(&pdev->dev, "acpi configuration failed\n"); + return PTR_ERR(plat_dat); + } + + dn = kzalloc(sizeof(struct device_node), GFP_KERNEL); + if (!dn) { + ret = -ENOMEM; + goto err_remove_config_dt; + } + + plat_dat->phy_node = dn; + } else { + plat_dat = dev_get_platdata(&pdev->dev); + if (!plat_dat) { + dev_err(&pdev->dev, "no platform data provided\n"); + return -EINVAL; + } + + /* Set default value for multicast hash bins */ + plat_dat->multicast_filter_bins = HASH_TABLE_SIZE; + + /* Set default value for unicast filter entries */ + plat_dat->unicast_filter_entries = 1; + } + + gmac = devm_kzalloc(&pdev->dev, sizeof(*gmac), GFP_KERNEL); + if (!gmac) { + ret = -ENOMEM; + goto err_remove_config_dt; + } + + gmac->dev = &pdev->dev; + gmac->tx2_clk = devm_clk_get(gmac->dev, "tx2_clk"); + if (IS_ERR(gmac->tx2_clk)) { + dev_warn(&pdev->dev, "couldn't get TX2 clock\n"); + gmac->tx2_clk = NULL; + } + + gmac->axi_clk = devm_clk_get(gmac->dev, "axi_clk"); + if (IS_ERR(gmac->axi_clk)) { + dev_warn(&pdev->dev, "couldn't get AXI clock\n"); + gmac->axi_clk = NULL; + } else { + clk_set_rate(gmac->axi_clk, 300000000); + } + + if (!acpi_disabled) + device_property_read_string(&pdev->dev, "compatible", &str); + + if ((gmac->dev->of_node && + of_device_is_compatible(gmac->dev->of_node, "baikal,bs1000-gmac")) || + (str && strcasecmp(str, "baikal,bs1000-gmac") == 0)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gmac->base = res->start; + gmac->has_aux_div2 = 1; + } else { + gmac->has_aux_div2 = 0; + } + + plat_dat->fix_mac_speed = baikal_gmac_fix_mac_speed; + plat_dat->bsp_priv = gmac; + plat_dat->core_type = DWMAC_CORE_GMAC; + plat_dat->bugged_jumbo = 1; /* TODO: is it really required? */ + plat_dat->setup = baikal_gmac_setup; + + ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); + if (ret) + goto err_remove_config_dt; + + if (!acpi_disabled) { + ret = baikal_add_mdio_phy(&pdev->dev); + if (ret) + goto err_remove_config_dt; + } + + gmac->is_fixed_stmmac_clk = is_fixed_stmmac_clk; + + kfree(dn); + return 0; + +err_remove_config_dt: + if (is_fixed_stmmac_clk) + clk_unregister_fixed_rate(plat_dat->stmmac_clk); + + kfree(dn); + return ret; +} + +static void baikal_gmac_remove(struct platform_device *pdev) +{ + struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(&pdev->dev)); + struct plat_stmmacenet_data *plat = priv->plat; + struct baikal_gmac *gmac = plat->bsp_priv; + + if (gmac->is_fixed_stmmac_clk) { + clk_disable_unprepare(plat->stmmac_clk); + clk_unregister_fixed_rate(plat->stmmac_clk); + plat->stmmac_clk = NULL; + } + + stmmac_pltfr_remove(pdev); +} + +static const struct of_device_id baikal_gmac_dwmac_match[] = { + { .compatible = "baikal,bm1000-gmac" }, + { .compatible = "baikal,bs1000-gmac" }, + { } +}; +MODULE_DEVICE_TABLE(of, baikal_gmac_dwmac_match); + +static struct platform_driver baikal_gmac_dwmac_driver = { + .probe = baikal_gmac_probe, + .remove = baikal_gmac_remove, + .driver = { + .name = "baikal-gmac-dwmac", + .pm = &stmmac_pltfr_pm_ops, + .of_match_table = of_match_ptr(baikal_gmac_dwmac_match) + } +}; +module_platform_driver(baikal_gmac_dwmac_driver); + +MODULE_DESCRIPTION("Baikal DWMAC specific glue driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index fe776ddf688952..a430f3e348ba33 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -539,6 +539,7 @@ int dwmac1000_setup(struct stmmac_priv *priv) return 0; } +EXPORT_SYMBOL_GPL(dwmac1000_setup); /* DWMAC 1000 HW Timestaming ops */ diff --git a/net/ethernet/eth.c b/net/ethernet/eth.c index 43e211e611b169..890d7c99336813 100644 --- a/net/ethernet/eth.c +++ b/net/ethernet/eth.c @@ -558,6 +558,7 @@ int nvmem_get_mac_address(struct device *dev, void *addrbuf) return 0; } +EXPORT_SYMBOL(nvmem_get_mac_address); static int fwnode_get_mac_addr(struct fwnode_handle *fwnode, const char *name, char *addr) -- 2.42.2