qualcommax: ipq50xx: add Qualcomm IPQ5018 DWMAC driver

Add IPQ5018 DWMAC driver. IP version of this Synopsys DWMAC is 3.7.

This Qualcomm IPQ5018 specific MAC implementation supports link speeds
of 10HD/FD, 100HD/FD, 1,000HD/FD, and 2500FD and SGMII and 2500BASEX
interface modes.

The driver supports the MAC be attached directly to a PHY or via an
optional PCS to a switch or PHY.

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
Link: https://github.com/openwrt/openwrt/pull/22381
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
George Moussalem
2026-07-11 22:26:43 +02:00
committed by Robert Marko
parent bdb0b722b7
commit b910cbfc0c
4 changed files with 413 additions and 0 deletions
@@ -0,0 +1,199 @@
// 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");
@@ -0,0 +1,131 @@
# 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>;
};
@@ -0,0 +1,56 @@
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);
@@ -0,0 +1,27 @@
--- 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