From: Marcello Sylvester Bauer <[email protected]>

Add ethernet driver for the UMAC controller found on HPE GXP SoCs.
This controller is connected to an external Marvell 88E1415 PHY via an
SGMII interface.

Co-developed-by: Jean-Marie Verdun <[email protected]>
Signed-off-by: Jean-Marie Verdun <[email protected]>
Signed-off-by: Marcello Sylvester Bauer <[email protected]>
Signed-off-by: Tan Siewert <[email protected]>
---
 MAINTAINERS                      |   2 +
 arch/arm/dts/hpe-gxp-u-boot.dtsi |  24 ++
 arch/arm/dts/hpe-gxp.dtsi        |  42 +++
 configs/gxp_defconfig            |   7 +
 drivers/net/Kconfig              |   9 +
 drivers/net/Makefile             |   1 +
 drivers/net/gxp_umac.c           | 572 +++++++++++++++++++++++++++++++++++++++
 drivers/net/gxp_umac.h           | 136 ++++++++++
 8 files changed, 793 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 420a3bbf730..64354cf0c62 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -366,6 +366,8 @@ F:  arch/arm/mach-hpe/
 F:     board/hpe/
 F:     configs/gxp_defconfig
 F:     doc/device-tree-bindings/spi/hpe,gxp-spi.yaml
+F:     drivers/net/gxp_umac.c
+F:     drivers/net/gxp_umac.h
 F:     drivers/misc/gxp_eeprom.c
 F:     drivers/sysinfo/gxp.c
 F:     drivers/timer/gxp-timer.c
diff --git a/arch/arm/dts/hpe-gxp-u-boot.dtsi b/arch/arm/dts/hpe-gxp-u-boot.dtsi
index 63a1a11fed2..31f898f09f4 100644
--- a/arch/arm/dts/hpe-gxp-u-boot.dtsi
+++ b/arch/arm/dts/hpe-gxp-u-boot.dtsi
@@ -23,3 +23,27 @@
                };
        };
 };
+
+&umac0 {
+       bootph-all;
+};
+
+&mdio0 {
+       bootph-all;
+};
+
+&phy0 {
+       bootph-all;
+};
+
+&umac1 {
+       bootph-all;
+};
+
+&mdio1 {
+       bootph-all;
+};
+
+&phy1 {
+       bootph-all;
+};
diff --git a/arch/arm/dts/hpe-gxp.dtsi b/arch/arm/dts/hpe-gxp.dtsi
index 66ddbd924f7..9e13e941ff0 100644
--- a/arch/arm/dts/hpe-gxp.dtsi
+++ b/arch/arm/dts/hpe-gxp.dtsi
@@ -163,6 +163,48 @@
                                interrupts = <6>;
                                interrupt-parent = <&vic0>;
                        };
+
+                       umac0: ethernet@4000 {
+                               compatible = "hpe,gxp-umac";
+                               reg = <0x4000 0x100>;
+                               phy-mode = "sgmii";
+                               phy-handle = <&phy0>;
+                               nvmem-cells = <&mac_address0>;
+                               nvmem-cell-names = "mac-address";
+                               status = "okay";
+
+                               mdio0: mdio {
+                                       compatible = "hpe,gxp-umac-mdio";
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       phy0: ethernet-phy@0 {
+                                               compatible = 
"ethernet-phy-ieee802.3-c22";
+                                               reg = <0>;
+                                       };
+                               };
+                       };
+
+                       umac1: ethernet@5000 {
+                               compatible = "hpe,gxp-umac";
+                               reg = <0x5000 0x100>;
+                               phy-mode = "gmii";
+                               phy-handle = <&phy1>;
+                               nvmem-cells = <&mac_address1>;
+                               nvmem-cell-names = "mac-address";
+                               status = "okay";
+
+                               mdio1: mdio {
+                                       compatible = "hpe,gxp-umac-mdio";
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       phy1: ethernet-phy@0 {
+                                               compatible = 
"ethernet-phy-ieee802.3-c22";
+                                               reg = <0>;
+                                       };
+                               };
+                       };
                };
        };
 };
diff --git a/configs/gxp_defconfig b/configs/gxp_defconfig
index d63c675d746..200ccc8d7a5 100644
--- a/configs/gxp_defconfig
+++ b/configs/gxp_defconfig
@@ -39,6 +39,13 @@ CONFIG_CMD_FAT=y
 CONFIG_ENV_IS_IN_SPI_FLASH=y
 CONFIG_ENV_REDUNDANT=y
 CONFIG_NETCONSOLE=y
+CONFIG_NETDEVICES=y
+CONFIG_PHYLIB=y
+CONFIG_PHY_MARVELL=y
+CONFIG_MII=y
+CONFIG_GXP_UMAC=y
+CONFIG_NET_RANDOM_ETHADDR=y
+CONFIG_CMD_MII=y
 CONFIG_MISC=y
 CONFIG_GXP_EEPROM=y
 # CONFIG_MMC is not set
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index c057504cf66..5bc504d00f1 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -861,6 +861,15 @@ config GMAC_ROCKCHIP
          This driver provides Rockchip SoCs network support based on the
          Synopsys Designware driver.
 
+config GXP_UMAC
+       bool "HPE GXP UMAC ethernet controller"
+       depends on ARCH_GXP
+       select PHYLIB
+       select MII
+       help
+         This driver supports the UMAC ethernet controller found on
+         HPE GXP BMC SoCs.
+
 config RENESAS_ETHER_SWITCH
        bool "Renesas Ethernet Switch support"
        depends on DM_ETH && (R8A779F0 || R8A78000)
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 96ec6fb187a..5330bfa00cd 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_FSL_PFE) += pfe_eth/
 obj-$(CONFIG_FTGMAC100) += ftgmac100.o
 obj-$(CONFIG_FTMAC100) += ftmac100.o
 obj-$(CONFIG_GMAC_ROCKCHIP) += gmac_rockchip.o
+obj-$(CONFIG_GXP_UMAC) += gxp_umac.o
 obj-$(CONFIG_HIFEMAC_ETH) += hifemac.o
 obj-$(CONFIG_HIFEMAC_MDIO) += hifemac_mdio.o
 obj-$(CONFIG_HIGMACV300_ETH) += higmacv300.o
diff --git a/drivers/net/gxp_umac.c b/drivers/net/gxp_umac.c
new file mode 100644
index 00000000000..78f5d6b6669
--- /dev/null
+++ b/drivers/net/gxp_umac.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HPE GXP UMAC ethernet driver
+ *
+ * Copyright (C) 2020 Hewlett Packard Enterprise Development LP.
+ * Copyright (C) 2026 9elements GmbH
+ */
+
+#include <cpu_func.h>
+#include <dm.h>
+#include <log.h>
+#include <malloc.h>
+#include <miiphy.h>
+#include <net.h>
+#include <asm/cache.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#include "gxp_umac.h"
+
+#define GXP_UMAC_TX_TIMEOUT_MS         1000
+
+static int gxp_umac_mdio_read(struct mii_dev *bus, int phy_addr, int dev_addr,
+                             int reg_addr)
+{
+       struct gxp_umac_priv *priv = bus->priv;
+       struct gxp_mdio_regs *mdio = priv->mdio_regs;
+       u32 val;
+       int ret;
+
+       val = readl(&mdio->mmi);
+       val &= ~(UMAC_MMI_PHY_ADDR_MASK | UMAC_MMI_REG_ADDR);
+       val |= (phy_addr << UMAC_MMI_PHY_ADDR_SHIFT) & UMAC_MMI_PHY_ADDR_MASK;
+       val |= reg_addr & UMAC_MMI_REG_ADDR;
+       val |= UMAC_MMI_MRNW;  /* Read operation */
+       writel(val, &mdio->mmi);
+
+       /* Activate transfer */
+       val |= UMAC_MMI_MOWNER;
+       writel(val, &mdio->mmi);
+
+       /* Wait for transfer to complete */
+       ret = readl_poll_timeout(&mdio->mmi, val, !(val & UMAC_MMI_MOWNER),
+                                GXP_MDIO_TIMEOUT_USEC);
+       if (ret) {
+               pr_err("%s: mdio read timeout (phy:%d reg:%x)\n",
+                      bus->name, phy_addr, reg_addr);
+               return ret;
+       }
+
+       return readl(&mdio->mmi_data) & UMAC_MMI_DATA_MASK;
+}
+
+static int gxp_umac_mdio_write(struct mii_dev *bus, int phy_addr, int dev_addr,
+                              int reg_addr, u16 value)
+{
+       struct gxp_umac_priv *priv = bus->priv;
+       struct gxp_mdio_regs *mdio = priv->mdio_regs;
+       u32 val;
+       int ret;
+
+       /* Write data first */
+       writel(value & UMAC_MMI_DATA_MASK, &mdio->mmi_data);
+
+       val = readl(&mdio->mmi);
+       val &= ~(UMAC_MMI_PHY_ADDR_MASK | UMAC_MMI_REG_ADDR | UMAC_MMI_MRNW);
+       val |= (phy_addr << UMAC_MMI_PHY_ADDR_SHIFT) & UMAC_MMI_PHY_ADDR_MASK;
+       val |= reg_addr & UMAC_MMI_REG_ADDR;
+       /* MRNW cleared = write operation */
+       writel(val, &mdio->mmi);
+
+       /* Activate transfer */
+       val |= UMAC_MMI_MOWNER;
+       writel(val, &mdio->mmi);
+
+       /* Wait for transfer to complete */
+       ret = readl_poll_timeout(&mdio->mmi, val, !(val & UMAC_MMI_MOWNER),
+                                GXP_MDIO_TIMEOUT_USEC);
+       if (ret) {
+               pr_err("%s: mdio write timeout (phy:%d reg:%x)\n",
+                      bus->name, phy_addr, reg_addr);
+       }
+
+       return ret;
+}
+
+static int gxp_umac_mdio_init(struct udevice *dev)
+{
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+       struct mii_dev *bus;
+       int ret;
+
+       bus = mdio_alloc();
+       if (!bus)
+               return -ENOMEM;
+
+       bus->read = gxp_umac_mdio_read;
+       bus->write = gxp_umac_mdio_write;
+       bus->priv = priv;
+
+       ret = mdio_register_seq(bus, dev_seq(dev));
+       if (ret) {
+               free(bus);
+               return ret;
+       }
+
+       priv->bus = bus;
+       return 0;
+}
+
+static u32 gxp_umac_get_ring_size_val(u32 num_entries)
+{
+       switch (num_entries) {
+       case 4:
+               return 0;
+       case 8:
+               return 1;
+       case 16:
+               return 3;
+       case 32:
+               return 7;
+       case 64:
+               return 0xf;
+       case 128:
+               return 0x1f;
+       case 256:
+               return 0x3f;
+       default:
+               return 1;  /* Default to 8 entries */
+       }
+}
+
+static void gxp_umac_init_rings(struct gxp_umac_priv *priv)
+{
+       int i;
+
+       memset(priv->txdes, 0, UMAC_MAX_RING_ENTRIES * sizeof(*priv->txdes));
+       memset(priv->rxdes, 0, UMAC_MAX_RING_ENTRIES * sizeof(*priv->rxdes));
+
+       priv->tx_index = 0;
+       priv->rx_index = 0;
+
+       /* Initialize RX descriptors with buffer addresses */
+       for (i = 0; i < UMAC_MAX_RING_ENTRIES; i++) {
+               priv->rxdes[i].dma_address = (u32)&priv->rx_packets[i];
+               priv->rxdes[i].count = UMAC_MAX_FRAME_SIZE;
+               priv->rxdes[i].status = UMAC_RING_ENTRY_HW_OWN;
+       }
+
+       /* Flush descriptors to memory */
+       flush_dcache_range((ulong)priv->txdes,
+                          (ulong)priv->txdes + UMAC_MAX_RING_ENTRIES * 
sizeof(*priv->txdes));
+       flush_dcache_range((ulong)priv->rxdes,
+                          (ulong)priv->rxdes + UMAC_MAX_RING_ENTRIES * 
sizeof(*priv->rxdes));
+       flush_dcache_range((ulong)priv->rx_packets,
+                          (ulong)priv->rx_packets +
+                          UMAC_MAX_RING_ENTRIES * sizeof(*priv->rx_packets));
+}
+
+static void gxp_umac_set_mac_addr(struct gxp_umac_priv *priv, const u8 *addr)
+{
+       struct gxp_umac_regs *regs = priv->regs;
+
+       writel((addr[0] << 8) | addr[1], &regs->mac_addr_hi);
+       writel((addr[2] << 8) | addr[3], &regs->mac_addr_mid);
+       writel((addr[4] << 8) | addr[5], &regs->mac_addr_lo);
+}
+
+static void gxp_umac_set_channel_enable(struct gxp_umac_priv *priv, int enable)
+{
+       struct gxp_umac_regs *regs = priv->regs;
+       u32 val;
+
+       if (enable) {
+               val = readl(&regs->config_status);
+               val |= UMAC_CFG_TXEN | UMAC_CFG_RXEN;
+               writel(val, &regs->config_status);
+               writel(0, &regs->ring_prompt);
+       } else {
+               writel(0, &regs->config_status);
+       }
+}
+
+static void gxp_umac_adjust_link(struct gxp_umac_priv *priv)
+{
+       struct gxp_umac_regs *regs = priv->regs;
+       struct phy_device *phydev = priv->phydev;
+       u32 val;
+
+       /* Disable MAC during configuration */
+       gxp_umac_set_channel_enable(priv, 0);
+
+       /* Disable both clocks first */
+       val = readl(&regs->config_status);
+       val &= ~(UMAC_CFG_GIGE_EN | UMAC_CFG_100M_EN);
+       writel(val, &regs->config_status);
+       udelay(2);
+
+       /* Configure speed and duplex */
+       val &= ~(UMAC_CFG_GIGE_EN | UMAC_CFG_100M_EN | UMAC_CFG_GIGE_MODE |
+                UMAC_CFG_FULL_DUPLEX);
+
+       if (phydev->speed == 1000)
+               val |= UMAC_CFG_GIGE_EN | UMAC_CFG_GIGE_MODE;
+       else
+               val |= UMAC_CFG_100M_EN;
+
+       if (phydev->duplex)
+               val |= UMAC_CFG_FULL_DUPLEX;
+
+       writel(val, &regs->config_status);
+       udelay(2);
+
+       /* Re-enable MAC */
+       gxp_umac_set_channel_enable(priv, 1);
+}
+
+static void gxp_umac_init_hw(struct gxp_umac_priv *priv)
+{
+       struct gxp_umac_regs *regs = priv->regs;
+       u32 ring_size;
+
+       /* Reset ring pointers */
+       writel(0, &regs->ring_ptr);
+
+       /* Clear missed status */
+       writel(0, &regs->clear_status);
+
+       /* Disable checksum generation */
+       writel(0, &regs->cksum_config);
+
+       /* Set ring size */
+       ring_size = gxp_umac_get_ring_size_val(UMAC_MAX_RING_ENTRIES);
+       writel((ring_size << UMAC_RING_SIZE_TX_SHIFT) |
+              (ring_size << UMAC_RING_SIZE_RX_SHIFT), &regs->ring_size);
+
+       /* Set ring base addresses */
+       writel((u32)priv->rxdes, &regs->rx_ring_addr);
+       writel((u32)priv->txdes, &regs->tx_ring_addr);
+
+       /* Set DMA burst size */
+       writel(0x22, &regs->dma_config);
+
+       /* Disable clocks initially */
+       writel(0, &regs->config_status);
+}
+
+static int gxp_umac_start(struct udevice *dev)
+{
+       struct eth_pdata *pdata = dev_get_plat(dev);
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+       struct phy_device *phydev = priv->phydev;
+       int ret;
+
+       /* Initialize ring buffers */
+       gxp_umac_init_rings(priv);
+
+       /* Initialize hardware */
+       gxp_umac_init_hw(priv);
+
+       /* Set MAC address */
+       gxp_umac_set_mac_addr(priv, pdata->enetaddr);
+
+       /* Start PHY */
+       ret = phy_startup(phydev);
+       if (ret) {
+               dev_err(dev, "Could not start PHY\n");
+               return ret;
+       }
+
+       if (!phydev->link) {
+               dev_err(dev, "No link\n");
+               return -ENOLINK;
+       }
+
+       /* Configure MAC for link speed/duplex */
+       gxp_umac_adjust_link(priv);
+
+       printf("%s: link up, %d Mbps %s-duplex\n", dev->name,
+              phydev->speed, phydev->duplex ? "full" : "half");
+
+       return 0;
+}
+
+static void gxp_umac_stop(struct udevice *dev)
+{
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+
+       gxp_umac_set_channel_enable(priv, 0);
+
+       if (priv->phydev)
+               phy_shutdown(priv->phydev);
+}
+
+static int gxp_umac_send(struct udevice *dev, void *packet, int length)
+{
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+       struct gxp_umac_regs *regs = priv->regs;
+       struct gxp_umac_tx_desc *txdes = &priv->txdes[priv->tx_index];
+       ulong des_start, des_end, data_start, data_end;
+       u32 val;
+       int timeout;
+
+       /* Wait for descriptor to be available */
+       des_start = (ulong)txdes & ~(ARCH_DMA_MINALIGN - 1);
+       des_end = des_start + roundup(sizeof(*txdes), ARCH_DMA_MINALIGN);
+       invalidate_dcache_range(des_start, des_end);
+
+       if (txdes->status & UMAC_RING_ENTRY_HW_OWN) {
+               dev_err(dev, "TX ring full\n");
+               return -EBUSY;
+       }
+
+       /* Pad short frames */
+       if (length < UMAC_MIN_FRAME_SIZE)
+               length = UMAC_MIN_FRAME_SIZE;
+
+       /* Set up descriptor */
+       txdes->dma_address = (u32)packet;
+       txdes->count = length;
+       txdes->cksum_offset = 0;
+       txdes->status = UMAC_RING_ENTRY_HW_OWN;
+
+       /* Flush packet data and descriptor */
+       data_start = (ulong)packet & ~(ARCH_DMA_MINALIGN - 1);
+       data_end = data_start + roundup(length, ARCH_DMA_MINALIGN);
+       flush_dcache_range(data_start, data_end);
+       flush_dcache_range(des_start, des_end);
+
+       /* Trigger transmission */
+       writel(0, &regs->ring_prompt);
+
+       /* Wait for TX interrupt */
+       timeout = GXP_UMAC_TX_TIMEOUT_MS * 1000;
+       do {
+               val = readl(&regs->interrupt);
+               if (val & UMAC_INT_TX)
+                       break;
+               udelay(1);
+       } while (--timeout > 0);
+
+       /* Clear TX interrupt */
+       writel(UMAC_INT_TX, &regs->interrupt);
+
+       /* Wait for descriptor to be released */
+       timeout = GXP_UMAC_TX_TIMEOUT_MS * 1000;
+       do {
+               invalidate_dcache_range(des_start, des_end);
+               if (!(txdes->status & UMAC_RING_ENTRY_HW_OWN))
+                       break;
+               udelay(1);
+       } while (--timeout > 0);
+
+       if (timeout <= 0) {
+               dev_err(dev, "TX timeout\n");
+               return -ETIMEDOUT;
+       }
+
+       /* Move to next descriptor */
+       priv->tx_index = (priv->tx_index + 1) % UMAC_MAX_RING_ENTRIES;
+
+       return 0;
+}
+
+static int gxp_umac_recv(struct udevice *dev, int flags, uchar **packetp)
+{
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+       struct gxp_umac_regs *regs = priv->regs;
+       struct gxp_umac_rx_desc *rxdes = &priv->rxdes[priv->rx_index];
+       ulong des_start, des_end, data_start, data_end;
+       u32 val;
+       int length;
+
+       /* Check and clear interrupts */
+       val = readl(&regs->interrupt);
+       if (val & UMAC_INT_OVERRUN)
+               dev_warn(dev, "RX overrun\n");
+       if (val)
+               writel(val, &regs->interrupt);
+
+       /* Check for missed frames */
+       val = readl(&regs->config_status);
+       if (val & UMAC_CFG_MISSED) {
+               dev_warn(dev, "RX missed frame\n");
+               writel(0, &regs->clear_status);
+       }
+
+       /* Invalidate descriptor */
+       des_start = (ulong)rxdes & ~(ARCH_DMA_MINALIGN - 1);
+       des_end = des_start + roundup(sizeof(*rxdes), ARCH_DMA_MINALIGN);
+       invalidate_dcache_range(des_start, des_end);
+
+       /* Check if descriptor is owned by hardware */
+       if (rxdes->status & UMAC_RING_ENTRY_HW_OWN) {
+               /* Check for errors even if HW owned */
+               if (rxdes->status & UMAC_RING_RX_ERR_MASK) {
+                       rxdes->status &= ~UMAC_RING_RX_ERR_MASK;
+                       flush_dcache_range(des_start, des_end);
+               }
+               return -EAGAIN;
+       }
+
+       length = rxdes->count;
+
+       /* Invalidate received data */
+       data_start = (ulong)rxdes->dma_address & ~(ARCH_DMA_MINALIGN - 1);
+       data_end = data_start + roundup(length, ARCH_DMA_MINALIGN);
+       invalidate_dcache_range(data_start, data_end);
+
+       *packetp = (uchar *)rxdes->dma_address;
+
+       return length;
+}
+
+static int gxp_umac_free_pkt(struct udevice *dev, uchar *packet, int length)
+{
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+       struct gxp_umac_regs *regs = priv->regs;
+       struct gxp_umac_rx_desc *rxdes = &priv->rxdes[priv->rx_index];
+       ulong des_start, des_end;
+
+       /* Return descriptor to hardware */
+       rxdes->count = UMAC_MAX_FRAME_SIZE;
+       rxdes->status = UMAC_RING_ENTRY_HW_OWN;
+
+       /* Flush descriptor */
+       des_start = (ulong)rxdes & ~(ARCH_DMA_MINALIGN - 1);
+       des_end = des_start + roundup(sizeof(*rxdes), ARCH_DMA_MINALIGN);
+       flush_dcache_range(des_start, des_end);
+
+       /* Prompt hardware to check for new buffers */
+       writel(0, &regs->ring_prompt);
+
+       /* Move to next descriptor */
+       priv->rx_index = (priv->rx_index + 1) % UMAC_MAX_RING_ENTRIES;
+
+       return 0;
+}
+
+static int gxp_umac_write_hwaddr(struct udevice *dev)
+{
+       struct eth_pdata *pdata = dev_get_plat(dev);
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+
+       gxp_umac_set_mac_addr(priv, pdata->enetaddr);
+       return 0;
+}
+
+static int gxp_umac_of_to_plat(struct udevice *dev)
+{
+       struct eth_pdata *pdata = dev_get_plat(dev);
+
+       pdata->iobase = dev_read_addr(dev);
+       if (pdata->iobase == FDT_ADDR_T_NONE) {
+               dev_err(dev, "Could not read register address\n");
+               return -EINVAL;
+       }
+
+       pdata->phy_interface = dev_read_phy_mode(dev);
+       if (pdata->phy_interface == PHY_INTERFACE_MODE_NA)
+               pdata->phy_interface = PHY_INTERFACE_MODE_SGMII;
+
+       return 0;
+}
+
+static int gxp_umac_probe(struct udevice *dev)
+{
+       struct eth_pdata *pdata = dev_get_plat(dev);
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+       int ret;
+
+       priv->regs = (struct gxp_umac_regs *)pdata->iobase;
+       priv->mdio_regs = (struct gxp_mdio_regs *)(pdata->iobase + 0x80);
+       priv->phy_mode = pdata->phy_interface;
+       priv->phy_addr = 0;
+
+       /* Allocate DMA-aligned ring buffers */
+       priv->txdes = memalign(ARCH_DMA_MINALIGN,
+                              UMAC_MAX_RING_ENTRIES * sizeof(*priv->txdes));
+       if (!priv->txdes)
+               return -ENOMEM;
+
+       priv->rxdes = memalign(ARCH_DMA_MINALIGN,
+                              UMAC_MAX_RING_ENTRIES * sizeof(*priv->rxdes));
+       if (!priv->rxdes) {
+               ret = -ENOMEM;
+               goto err_free_txdes;
+       }
+
+       priv->rx_packets = memalign(ARCH_DMA_MINALIGN,
+                                   UMAC_MAX_RING_ENTRIES * 
sizeof(*priv->rx_packets));
+       if (!priv->rx_packets) {
+               ret = -ENOMEM;
+               goto err_free_rxdes;
+       }
+
+       /* Initialize MDIO bus */
+       ret = gxp_umac_mdio_init(dev);
+       if (ret) {
+               dev_err(dev, "Failed to initialize MDIO bus\n");
+               goto err_free_rx_packets;
+       }
+
+       priv->phydev = phy_connect(priv->bus, priv->phy_addr, dev, 
priv->phy_mode);
+       if (!priv->phydev) {
+               dev_err(dev, "Failed to connect to PHY\n");
+               ret = -ENODEV;
+               goto err_free_mdio;
+       }
+
+       /* Configure PHY */
+       priv->phydev->supported &= PHY_GBIT_FEATURES;
+       priv->phydev->advertising = priv->phydev->supported;
+       phy_config(priv->phydev);
+
+       return 0;
+
+err_free_mdio:
+       mdio_unregister(priv->bus);
+       mdio_free(priv->bus);
+err_free_rx_packets:
+       free(priv->rx_packets);
+err_free_rxdes:
+       free(priv->rxdes);
+err_free_txdes:
+       free(priv->txdes);
+       return ret;
+}
+
+static int gxp_umac_remove(struct udevice *dev)
+{
+       struct gxp_umac_priv *priv = dev_get_priv(dev);
+
+       free(priv->phydev);
+       mdio_unregister(priv->bus);
+       mdio_free(priv->bus);
+       free(priv->rx_packets);
+       free(priv->rxdes);
+       free(priv->txdes);
+
+       return 0;
+}
+
+static const struct eth_ops gxp_umac_ops = {
+       .start          = gxp_umac_start,
+       .send           = gxp_umac_send,
+       .recv           = gxp_umac_recv,
+       .stop           = gxp_umac_stop,
+       .free_pkt       = gxp_umac_free_pkt,
+       .write_hwaddr   = gxp_umac_write_hwaddr,
+};
+
+static const struct udevice_id gxp_umac_ids[] = {
+       { .compatible = "hpe,gxp-umac" },
+       { }
+};
+
+U_BOOT_DRIVER(gxp_umac) = {
+       .name           = "gxp_umac",
+       .id             = UCLASS_ETH,
+       .of_match       = gxp_umac_ids,
+       .of_to_plat     = gxp_umac_of_to_plat,
+       .probe          = gxp_umac_probe,
+       .remove         = gxp_umac_remove,
+       .ops            = &gxp_umac_ops,
+       .priv_auto      = sizeof(struct gxp_umac_priv),
+       .plat_auto      = sizeof(struct eth_pdata),
+       .flags          = DM_FLAG_ALLOC_PRIV_DMA,
+};
diff --git a/drivers/net/gxp_umac.h b/drivers/net/gxp_umac.h
new file mode 100644
index 00000000000..42930e39864
--- /dev/null
+++ b/drivers/net/gxp_umac.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * HPE GXP UMAC ethernet driver
+ *
+ * Copyright (C) 2020 Hewlett Packard Enterprise Development LP.
+ * Copyright (C) 2026 9elements GmbH
+ */
+
+#ifndef _GXP_UMAC_H_
+#define _GXP_UMAC_H_
+
+struct gxp_umac_regs {
+       u32 config_status;              /* 0x00: Configuration and Status 
Register I */
+       u32 ring_ptr;                   /* 0x04: Ring Pointer Register */
+       u32 ring_prompt;                /* 0x08: Ring Prompt Register (W) */
+       u32 clear_status;               /* 0x0c: Clear Status Register (W) */
+       u32 cksum_config;               /* 0x10: Checksum Config Register */
+       u32 ring_size;                  /* 0x14: Ring Size Register */
+       u32 mac_addr_hi;                /* 0x18: MAC Address[47:32] */
+       u32 mac_addr_mid;               /* 0x1c: MAC Address[31:16] */
+       u32 mac_addr_lo;                /* 0x20: MAC Address[15:0] */
+       u32 mc_addr_filt_hi;            /* 0x24: LAF[63:32] */
+       u32 mc_addr_filt_lo;            /* 0x28: LAF[31:0] */
+       u32 config_status2;             /* 0x2c: Configuration and Status 
Register II */
+       u32 interrupt;                  /* 0x30: MAC Interrupt Config and 
Status */
+       u32 overrun_count;              /* 0x34: Overrun Counter Register */
+       u32 rx_int_config;              /* 0x38: Rx Interrupt Config Register */
+       u32 tx_int_config;              /* 0x3c: Tx Interrupt Config Register */
+       u32 packet_length;              /* 0x40: Packet Length Register */
+       u32 bcast_filter;               /* 0x44: Broadcast Filter Config 
Register */
+       u32 bcast_prompt;               /* 0x48: Broadcast Prompt Register (W) 
*/
+       u32 rx_ring_addr;               /* 0x4c: Rx Ring Base Address Register 
*/
+       u32 tx_ring_addr;               /* 0x50: Tx Ring Base Address Register 
*/
+       u32 dma_config;                 /* 0x54: DMA Config Register */
+       u32 burst_config;               /* 0x58: Bursting Config Register */
+       u32 pause_config;               /* 0x5c: PAUSE Frame Config Register */
+       u32 pause_control;              /* 0x60: PAUSE Frame Control and Status 
*/
+       u32 congestion_config;          /* 0x64: Channel Congestion Config */
+       u32 frame_filter_config;        /* 0x68: Frame Type Filter Config */
+       u32 rx_fifo_config_status;      /* 0x6c: RX FIFO Config and Status */
+       u32 rx_ring1_base_addr;         /* 0x70: RX Ring 1 Base Address */
+       u32 config_status3;             /* 0x74: Configuration and Status 
Register III */
+       u32 reserved[2];                /* 0x78-0x7c: Reserved */
+};
+
+/* MDIO registers (at offset 0x80 from UMAC base) */
+struct gxp_mdio_regs {
+       u32 mmi;                        /* 0x00: MMI Register */
+       u32 mmi_data;                   /* 0x04: MMI Data Register */
+};
+
+/* Config/Status Register I bits */
+#define UMAC_CFG_TXEN                  BIT(12)
+#define UMAC_CFG_RXEN                  BIT(11)
+#define UMAC_CFG_GIGE_EN               BIT(10)
+#define UMAC_CFG_100M_EN               BIT(9)
+#define UMAC_CFG_GIGE_MODE             BIT(2)
+#define UMAC_CFG_FULL_DUPLEX           BIT(0)
+#define UMAC_CFG_MISSED                        BIT(7)
+
+/* Interrupt register bits */
+#define UMAC_INT_TX                    BIT(0)
+#define UMAC_INT_RX                    BIT(2)
+#define UMAC_INT_OVERRUN               BIT(4)
+
+/* MMI register bits */
+#define UMAC_MMI_PHY_ADDR_MASK         0x001f0000
+#define UMAC_MMI_PHY_ADDR_SHIFT                16
+#define UMAC_MMI_MOWNER                        BIT(9)
+#define UMAC_MMI_MRNW                  BIT(8)
+#define UMAC_MMI_REG_ADDR              0x0000001f
+#define UMAC_MMI_DATA_MASK             0x0000ffff
+
+/* Ring size register shifts */
+#define UMAC_RING_SIZE_TX_SHIFT                24
+#define UMAC_RING_SIZE_RX_SHIFT                16
+
+/* Ring entry status bits */
+#define UMAC_RING_ENTRY_HW_OWN         BIT(15)
+#define UMAC_RING_RX_ERR_MASK          0x38e0
+
+/* Frame sizes */
+#define UMAC_MIN_FRAME_SIZE            60
+#define UMAC_MAX_FRAME_SIZE            1518
+#define UMAC_MAX_PACKET_ROUNDED                0x600   /* 1536, aligned */
+
+/* Ring configuration */
+#define UMAC_MAX_RING_ENTRIES          8
+#define UMAC_RING_ENTRY_SIZE           16
+
+/* Ring entry structures */
+struct gxp_umac_rx_desc {
+       u32 dma_address;
+       u16 status;
+       u16 count;
+       u16 checksum;
+       u16 control;
+       u32 reserved;
+} __packed;
+
+struct gxp_umac_tx_desc {
+       u32 dma_address;
+       u16 status;
+       u16 count;
+       u32 cksum_offset;
+       u32 reserved;
+} __packed;
+
+/* Packet buffer structure */
+struct gxp_umac_packet {
+       u8 data[UMAC_MAX_FRAME_SIZE];
+       u8 pad[UMAC_MAX_PACKET_ROUNDED - UMAC_MAX_FRAME_SIZE];
+};
+
+/* Private data structure */
+struct gxp_umac_priv {
+       struct gxp_umac_regs __iomem *regs;
+       struct gxp_mdio_regs __iomem *mdio_regs;
+
+       struct gxp_umac_tx_desc *txdes;
+       struct gxp_umac_rx_desc *rxdes;
+       struct gxp_umac_packet *rx_packets;
+
+       int tx_index;
+       int rx_index;
+
+       struct mii_dev *bus;
+       struct phy_device *phydev;
+       u32 phy_addr;
+       u32 phy_mode;
+};
+
+/* MDIO timeout */
+#define GXP_MDIO_TIMEOUT_USEC          10000
+
+#endif /* _GXP_UMAC_H_ */

---
base-commit: 8d7bc8add17bbc69a58dfe0002eea039dd2dc1bc
change-id: 20260818-gxp-net-9caa4fdff62d

Best regards,
--  
Tan Siewert <[email protected]>

Reply via email to