--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Qualcomm IPQ5018 DWMAC glue layer
+ *
+ * Copyright (C) 2026 The OpenWrt Project
+ */
+
+#include <linux/clk.h>
+#include <linux/of_mdio.h>
+#include <linux/pcs/pcs.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define MAX_FRAME_SIZE 16383 /* 14 bits */
+/* MAX_MTU = (MAX_FRAME_SIZE - ETH_HLEN - ETH_FCS_LEN - (2 * VLAN_HLEN)) */
+#define MAX_MTU 16357
+
+static struct clk_bulk_data ipq5018_gmac_clks[] = {
+ { .id = "stmmaceth" },
+ { .id = "pclk" },
+ { .id = "ptp_ref" },
+ { .id = "ahb" },
+ { .id = "axi" },
+ { .id = "rx" },
+ { .id = "tx" },
+};
+
+struct ipq5018_gmac {
+ struct device *dev;
+ struct clk *rx_clk;
+ struct clk *tx_clk;
+};
+
+static void ipq5018_gmac_fix_speed(void *priv, unsigned int speed, unsigned int mode)
+{
+ struct ipq5018_gmac *gmac = priv;
+ unsigned long rate;
+
+ switch(speed) {
+ case SPEED_10:
+ rate = 2500000;
+ break;
+ case SPEED_100:
+ rate = 25000000;
+ break;
+ case SPEED_1000:
+ rate = 125000000;
+ break;
+ case SPEED_2500:
+ rate = 312500000;
+ break;
+ default:
+ dev_err(gmac->dev, "Unsupported speed: %d\n", speed);
+ rate = 125000000;
+ break;
+ }
+
+ clk_set_rate(gmac->rx_clk, rate);
+ clk_set_rate(gmac->tx_clk, rate);
+}
+
+static int ipq5018_gmac_pcs_init(struct stmmac_priv *priv)
+{
+ struct phylink_pcs *pcs;
+ int ret;
+
+ pcs = fwnode_pcs_get(dev_fwnode(priv->device), 0);
+ if (!pcs)
+ return 0;
+ else if (IS_ERR(pcs)) {
+ ret = PTR_ERR(pcs);
+ if (ret == -ENOENT)
+ return 0;
+ dev_err(priv->device, "failed to parse PCS from fwnode\n");
+ return ret;
+ }
+
+ priv->hw->phylink_pcs = pcs;
+
+ return 0;
+}
+
+static struct phylink_pcs *ipq5018_gmac_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ if (priv->hw->phylink_pcs)
+ return priv->hw->phylink_pcs;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static void ipq5018_gmac_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
+ unsigned long *interfaces)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ /*
+ * IPQ5018 has two GMACs:
+ * - GMAC0 supports SGMII and is wired to the SoC's internal GE PHY
+ * - GMAC1 supports SGMII and 2500BaseX, configurable by the UNIPHY PCS
+ */
+ __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
+ if (priv->hw->phylink_pcs) {
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+
+ /*
+ * Synopsys DWMAC IP version 3.7 is limited to 1 Gpbs.
+ * This vendor specific implementation supports 2.5 Gbps, so override
+ * the default mac link capabilities.
+ */
+ mac->link.caps |= MAC_2500FD;
+ }
+}
+
+static int ipq5018_gmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct ipq5018_gmac *gmac;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get stmmac platform resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR_OR_NULL(plat_dat))
+ return dev_err_probe(dev, PTR_ERR(plat_dat),
+ "failed to parse stmmac dt parameters\n");
+
+ gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to allocate priv\n");
+
+ gmac->dev = dev;
+
+ gmac->rx_clk = devm_clk_get(dev, "rx");
+ if (IS_ERR(gmac->rx_clk))
+ return dev_err_probe(dev, PTR_ERR(gmac->rx_clk),
+ "failed to get RX clock\n");
+
+ gmac->tx_clk = devm_clk_get(dev, "tx");
+ if (IS_ERR(gmac->tx_clk))
+ return dev_err_probe(dev, PTR_ERR(gmac->tx_clk),
+ "failed to get TX clock\n");
+
+ ret = devm_clk_bulk_get(dev, ARRAY_SIZE(ipq5018_gmac_clks),
+ ipq5018_gmac_clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+ ret = clk_bulk_prepare_enable(ARRAY_SIZE(ipq5018_gmac_clks),
+ ipq5018_gmac_clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable clocks\n");
+
+ plat_dat->bsp_priv = gmac;
+ plat_dat->max_speed = 2500;
+ plat_dat->maxmtu = MAX_MTU;
+ plat_dat->rx_fifo_size = MAX_FRAME_SIZE;
+ plat_dat->tx_fifo_size = MAX_FRAME_SIZE;
+ plat_dat->get_interfaces = ipq5018_gmac_get_interfaces;
+ plat_dat->fix_mac_speed = ipq5018_gmac_fix_speed;
+ plat_dat->pcs_init = ipq5018_gmac_pcs_init;
+ plat_dat->select_pcs = ipq5018_gmac_select_pcs;
+
+ return stmmac_dvr_probe(dev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id ipq5018_gmac_dwmac_match[] = {
+ { .compatible = "qcom,ipq5018-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ipq5018_gmac_dwmac_match);
+
+static struct platform_driver ipq5018_gmac_dwmac_driver = {
+ .probe = ipq5018_gmac_probe,
+ .driver = {
+ .name = "ipq5018-gmac-dwmac",
+ .of_match_table = ipq5018_gmac_dwmac_match,
+ },
+};
+module_platform_driver(ipq5018_gmac_dwmac_driver);
+
+MODULE_DESCRIPTION("Qualcomm IPQ5018 DWMAC glue layer");
+MODULE_LICENSE("GPL");
\ No newline at end of file
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/qcom,ipq5018-gmac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ5018 DWMAC glue layer (GMAC)
+
+maintainers:
+ - George Moussalem <george.moussalem@outlook.com>
+
+description: |
+ This file documents platform glue layer for Qualcomm IPQ5018 DWMAC (GMAC)
+ The MAC supports speeds of 10/100/1000/2500 Mbps and supports SGMII and
+ 2500base-x interface modes. The IPQ5018 SoC has two GMACs:
+ The first MAC (GMAC0) is connected to an the SoC's internal IPQ5018 ethernet
+ GE PHY while the second MAC is connected to the SoC's UNIPHY PCS which is turn
+ can be wired to an external switch or PHY.
+ This MAC is of Synopsys DesignWare IP version 3.70a.
+
+# We need a select here so we don't match all nodes with 'snps,dwmac'
+select:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - qcom,ipq5018-gmac
+ required:
+ - compatible
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - qcom,ipq5018-gmac
+ - const: snps,dwmac
+
+ reg: true
+
+ reg-names:
+ items:
+ - const: stmmaceth
+
+ clocks:
+ minItems: 7
+
+ clock-names:
+ contains:
+ enum:
+ - stmmaceth
+ - pclk
+ - ptp_ref
+ - ahb
+ - axi
+ - rx
+ - tx
+
+ resets: true
+
+ reset-names:
+ items:
+ - const: stmmaceth
+
+ interrupts:
+ minItems: 1
+
+ interrupt-names:
+ items:
+ - const: macirq
+
+ pcs-handle:
+ $ref: ethernet-controller.yaml#/properties/pcs-handle
+ description:
+ A reference to an optional PCS PHY device (GMAC1 <-> UNIPHY PCS)
+
+required:
+ - compatible
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+ - interrupts
+ - interrupt-names
+
+allOf:
+ - $ref: snps,dwmac.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,gcc-ipq5018.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/reset/qcom,gcc-ipq5018.h>
+
+ gmac1: ethernet@39d00000 {
+ compatible = "qcom,ipq5018-gmac", "snps,dwmac";
+ reg = <0x39d00000 0x10000>;
+ reg-names = "stmmaceth";
+ clocks = <&gcc GCC_GMAC1_SYS_CLK>,
+ <&gcc GCC_GMAC1_CFG_CLK>,
+ <&gcc GCC_GMAC1_PTP_CLK>,
+ <&gcc GCC_SNOC_GMAC1_AHB_CLK>,
+ <&gcc GCC_SNOC_GMAC1_AXI_CLK>,
+ <&gcc GCC_GMAC1_RX_CLK>,
+ <&gcc GCC_GMAC1_TX_CLK>;
+ clock-names = "stmmaceth",
+ "pclk",
+ "ptp_ref",
+ "ahb",
+ "axi",
+ "rx",
+ "tx";
+ assigned-clocks = <&gcc GCC_GMAC1_SYS_CLK>,
+ <&gcc GCC_GMAC1_CFG_CLK>,
+ <&gcc GCC_GMAC1_PTP_CLK>,
+ <&gcc GCC_SNOC_GMAC1_AHB_CLK>,
+ <&gcc GCC_SNOC_GMAC1_AXI_CLK>;
+ assigned-clock-rates = <240000000>,
+ <240000000>,
+ <240000000>,
+ <240000000>,
+ <240000000>;
+ resets = <&gcc GCC_GMAC1_BCR>;
+ reset-names = "stmmaceth";
+ interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+
+ phy-mode = "sgmii";
+ pcs-handle = <&uniphy0>;
+ };
--- /dev/null
+From ca732e990fc8222a2d6782ae750304719e212fe8 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 1 May 2025 12:45:11 +0100
+Subject: [PATCH] net: stmmac: add get_interfaces() platform method
+
+Add a get_interfaces() platform method to allow platforms to indicate
+to phylink which interface modes they support - which then allows
+phylink to validate on initialisation that the configured PHY interface
+mode is actually supported.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1uASLn-0021Qd-Mi@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 16 +++++++++++++---
+ include/linux/stmmac.h | 2 ++
+ 2 files changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+@@ -1250,10 +1250,21 @@ static int stmmac_phy_setup(struct stmma
+ priv->phylink_config.default_an_inband =
+ mdio_bus_data->default_an_inband;
+
+- /* Set the platform/firmware specified interface mode. Note, phylink
+- * deals with the PHY interface mode, not the MAC interface mode.
++ /* Get the PHY interface modes (at the PHY end of the link) that
++ * are supported by the platform.
+ */
+- __set_bit(mode, priv->phylink_config.supported_interfaces);
++ if (priv->plat->get_interfaces)
++ priv->plat->get_interfaces(priv, priv->plat->bsp_priv,
++ priv->phylink_config.supported_interfaces);
++
++ /* Set the platform/firmware specified interface mode if the
++ * supported interfaces have not already been provided using
++ * phy_interface as a last resort.
++ */
++ if (phy_interface_empty(priv->phylink_config.supported_interfaces))
++ __set_bit(priv->plat->phy_interface,
++ priv->phylink_config.supported_interfaces);
++
+
+ /* If we have an xpcs, it defines which PHY interfaces are supported. */
+ if (priv->hw->xpcs)
+--- a/include/linux/stmmac.h
++++ b/include/linux/stmmac.h
+@@ -229,6 +229,8 @@ struct plat_stmmacenet_data {
+ u8 tx_sched_algorithm;
+ struct stmmac_rxq_cfg rx_queues_cfg[MTL_MAX_RX_QUEUES];
+ struct stmmac_txq_cfg tx_queues_cfg[MTL_MAX_TX_QUEUES];
++ void (*get_interfaces)(struct stmmac_priv *priv, void *bsp_priv,
++ unsigned long *interfaces);
+ void (*fix_mac_speed)(void *priv, unsigned int speed, unsigned int mode);
+ int (*fix_soc_reset)(void *priv, void __iomem *ioaddr);
+ int (*serdes_powerup)(struct net_device *ndev, void *priv);
--- /dev/null
+--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
++++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
+@@ -78,6 +78,14 @@ config DWMAC_INGENIC
+ device driver. This driver is used on for the Ingenic SoCs
+ MAC ethernet controller.
+
++config DWMAC_IPQ5018
++ tristate "Qualcomm IPQ5018 DWMAC driver"
++ depends on OF && (ARCH_QCOM || COMPILE_TEST)
++ help
++ Support for ethernet controller on Qualcomm IPQ5018 SoCs.
++
++ This selects the IPQ5018 SoC glue layer support for the stmmac driver.
++
+ config DWMAC_IPQ806X
+ tristate "QCA IPQ806x DWMAC support"
+ default ARCH_QCOM
+--- a/drivers/net/ethernet/stmicro/stmmac/Makefile
++++ b/drivers/net/ethernet/stmicro/stmmac/Makefile
+@@ -15,6 +15,7 @@ stmmac-$(CONFIG_STMMAC_SELFTESTS) += stm
+ obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o
+ obj-$(CONFIG_DWMAC_ANARION) += dwmac-anarion.o
+ obj-$(CONFIG_DWMAC_INGENIC) += dwmac-ingenic.o
++obj-$(CONFIG_DWMAC_IPQ5018) += dwmac-ipq5018.o
+ obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o
+ obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o
+ obj-$(CONFIG_DWMAC_MEDIATEK) += dwmac-mediatek.o