--- /dev/null
+/*
+ * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/if_ether.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/timer.h>
+#include <ipxe/devtree.h>
+#include <ipxe/fdt.h>
+#include "cgem.h"
+
+/** @file
+ *
+ * Cadence Gigabit Ethernet MAC (GEM) network driver
+ *
+ * Based primarily on the Zynq 7000 SoC Technical Reference Manual,
+ * available at the time of writing from:
+ *
+ * https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM
+ *
+ */
+
+/******************************************************************************
+ *
+ * Device reset
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Reset hardware
+ *
+ * @v cgem Cadence GEM device
+ * @ret rc Return status code
+ */
+static int cgem_reset ( struct cgem_nic *cgem ) {
+
+ /* There is no software-driven reset capability in the
+ * hardware. Instead we have to write the expected reset
+ * values to the various registers.
+ */
+
+ /* Disable all interrupts */
+ writel ( CGEM_IDR_ALL, ( cgem->regs + CGEM_IDR ) );
+
+ /* Clear network control register */
+ writel ( 0, ( cgem->regs + CGEM_NWCTRL ) );
+
+ /* Clear statistics registers now that TX and RX are stopped */
+ writel ( CGEM_NWCTRL_STATCLR, ( cgem->regs + CGEM_NWCTRL ) );
+
+ /* Clear TX queue base address */
+ writel ( 0, ( cgem->regs + CGEM_TXQBASE ) );
+
+ /* Clear RX queue base address */
+ writel ( 0, ( cgem->regs + CGEM_RXQBASE ) );
+
+ /* Configure DMA */
+ writel ( ( CGEM_DMACR_RXBUF ( CGEM_RX_LEN ) | CGEM_DMACR_TXSIZE_MAX |
+ CGEM_DMACR_RXSIZE_MAX | CGEM_DMACR_BLENGTH_MAX ),
+ ( cgem->regs + CGEM_DMACR ) );
+
+ /* Enable MII interface */
+ writel ( CGEM_NWCTRL_MDEN, ( cgem->regs + CGEM_NWCTRL ) );
+
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * PHY access
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Wait for MII operation to complete
+ *
+ * @v cgem Cadence GEM device
+ * @ret rc Return status code
+ */
+static int cgem_mii_wait ( struct cgem_nic *cgem ) {
+ uint32_t nwsr;
+ unsigned int i;
+
+ /* Wait for MII interface to become idle */
+ for ( i = 0 ; i < CGEM_MII_MAX_WAIT_US ; i++ ) {
+
+ /* Check if MII interface is idle */
+ nwsr = readl ( cgem->regs + CGEM_NWSR );
+ if ( nwsr & CGEM_NWSR_MII_IDLE )
+ return 0;
+
+ /* Delay */
+ udelay ( 1 );
+ }
+
+ DBGC ( cgem, "CGEM %s timed out waiting for MII\n", cgem->name );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Read from MII register
+ *
+ * @v mdio MII interface
+ * @v phy PHY address
+ * @v reg Register address
+ * @ret data Data read, or negative error
+ */
+static int cgem_mii_read ( struct mii_interface *mdio, unsigned int phy,
+ unsigned int reg ) {
+ struct cgem_nic *cgem = container_of ( mdio, struct cgem_nic, mdio );
+ unsigned int data;
+ int rc;
+
+ /* Initiate read */
+ writel ( ( CGEM_PHYMNTNC_CLAUSE22 | CGEM_PHYMNTNC_OP_READ |
+ CGEM_PHYMNTNC_ADDR ( phy ) | CGEM_PHYMNTNC_REG ( reg ) |
+ CGEM_PHYMNTNC_FIXED ),
+ ( cgem->regs + CGEM_PHYMNTNC ) );
+
+ /* Wait for read to complete */
+ if ( ( rc = cgem_mii_wait ( cgem ) ) != 0 )
+ return rc;
+
+ /* Read data */
+ data = ( readl ( cgem->regs + CGEM_PHYMNTNC ) &
+ CGEM_PHYMNTNC_DATA_MASK );
+
+ return data;
+}
+
+/**
+ * Write to MII register
+ *
+ * @v mdio MII interface
+ * @v phy PHY address
+ * @v reg Register address
+ * @v data Data to write
+ * @ret rc Return status code
+ */
+static int cgem_mii_write ( struct mii_interface *mdio, unsigned int phy,
+ unsigned int reg, unsigned int data ) {
+ struct cgem_nic *cgem = container_of ( mdio, struct cgem_nic, mdio );
+ int rc;
+
+ /* Initiate write */
+ writel ( ( CGEM_PHYMNTNC_CLAUSE22 | CGEM_PHYMNTNC_OP_READ |
+ CGEM_PHYMNTNC_ADDR ( phy ) | CGEM_PHYMNTNC_REG ( reg ) |
+ CGEM_PHYMNTNC_FIXED | data ),
+ ( cgem->regs + CGEM_PHYMNTNC ) );
+
+ /* Wait for write to complete */
+ if ( ( rc = cgem_mii_wait ( cgem ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/** MII operations */
+static struct mii_operations cgem_mii_operations = {
+ .read = cgem_mii_read,
+ .write = cgem_mii_write,
+};
+
+/******************************************************************************
+ *
+ * Link state
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Initialise PHY
+ *
+ * @v cgem Cadence GEM device
+ * @ret rc Return status code
+ */
+static int cgem_init_phy ( struct cgem_nic *cgem ) {
+ int rc;
+
+ /* Find PHY address */
+ if ( ( rc = mii_find ( &cgem->mii ) ) != 0 ) {
+ DBGC ( cgem, "CGEM %s could not find PHY address: %s\n",
+ cgem->name, strerror ( rc ) );
+ return rc;
+ }
+
+ /* Reset PHY */
+ if ( ( rc = mii_reset ( &cgem->mii ) ) != 0 ) {
+ DBGC ( cgem, "CGEM %s could not reset PHY: %s\n",
+ cgem->name, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Check link state
+ *
+ * @v netdev Network device
+ */
+static int cgem_check_link ( struct net_device *netdev ) {
+ struct cgem_nic *cgem = netdev->priv;
+ int rc;
+
+ /* Check link state */
+ if ( ( rc = mii_check_link ( &cgem->mii, netdev ) ) != 0 ) {
+ DBGC ( cgem, "CGEM %s could not check link: %s\n",
+ cgem->name, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Check link state periodically
+ *
+ * @v retry Link state check timer
+ * @v over Failure indicator
+ */
+static void cgem_expired ( struct retry_timer *timer, int over __unused ) {
+ struct cgem_nic *cgem = container_of ( timer, struct cgem_nic, timer );
+ struct net_device *netdev = cgem->netdev;
+
+ /* Restart timer */
+ start_timer_fixed ( timer, CGEM_LINK_INTERVAL );
+
+ /* Check link state */
+ cgem_check_link ( netdev );
+}
+
+/******************************************************************************
+ *
+ * Network device interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Create descriptor ring
+ *
+ * @v cgem Cadence GEM device
+ * @v ring Descriptor ring
+ * @ret rc Return status code
+ */
+static int cgem_create_ring ( struct cgem_nic *cgem, struct cgem_ring *ring ) {
+ struct cgem_descriptor *desc;
+ unsigned int i;
+
+ /* Allocate descriptor ring (on its own size) */
+ ring->desc = dma_alloc ( cgem->dma, &ring->map, ring->len, ring->len );
+ if ( ! ring->desc )
+ return -ENOMEM;
+
+ /* Initialise descriptor ring */
+ for ( i = 0 ; i < ring->count ; i++ ) {
+ desc = &ring->desc[i];
+ desc->addr = cpu_to_le32 ( CGEM_RX_ADDR_OWNED );
+ desc->flags = cpu_to_le32 ( CGEM_TX_FL_OWNED );
+ }
+ desc = &ring->desc[ ring->count - 1 ];
+ desc->addr |= cpu_to_le32 ( CGEM_RX_ADDR_WRAP );
+ desc->flags |= cpu_to_le32 ( CGEM_TX_FL_WRAP );
+
+ /* Program ring address */
+ writel ( dma ( &ring->map, ring->desc ),
+ ( cgem->regs + ring->qbase ) );
+
+ DBGC ( cgem, "CGEM %s ring %02x is at [%08lx,%08lx)\n",
+ cgem->name, ring->qbase, virt_to_phys ( ring->desc ),
+ ( virt_to_phys ( ring->desc ) + ring->len ) );
+ return 0;
+}
+
+/**
+ * Destroy descriptor ring
+ *
+ * @v cgem Cadence GEM device
+ * @v ring Descriptor ring
+ */
+static void cgem_destroy_ring ( struct cgem_nic *cgem,
+ struct cgem_ring *ring ) {
+
+ /* Clear ring address */
+ writel ( 0, ( cgem->regs + ring->qbase ) );
+
+ /* Free descriptor ring */
+ dma_free ( &ring->map, ring->desc, ring->len );
+ ring->desc = NULL;
+ ring->prod = 0;
+ ring->cons = 0;
+}
+
+/**
+ * Refill receive descriptor ring
+ *
+ * @v cgem Cadence GEM device
+ */
+static void cgem_refill_rx ( struct cgem_nic *cgem ) {
+ struct cgem_descriptor *rx;
+ struct io_buffer *iobuf;
+ unsigned int rx_idx;
+ uint32_t addr;
+
+ /* Refill ring */
+ while ( ( cgem->rx.prod - cgem->rx.cons ) != CGEM_NUM_RX_DESC ) {
+
+ /* Allocate I/O buffer */
+ iobuf = alloc_rx_iob ( CGEM_RX_LEN, cgem->dma );
+ if ( ! iobuf ) {
+ /* Wait for next refill */
+ break;
+ }
+
+ /* Get next receive descriptor */
+ rx_idx = ( cgem->rx.prod++ % CGEM_NUM_RX_DESC );
+ rx = &cgem->rx.desc[rx_idx];
+
+ /* Populate receive descriptor */
+ rx->flags = 0;
+ addr = 0;
+ if ( ( cgem->rx.prod % CGEM_NUM_RX_DESC ) == 0 )
+ addr |= CGEM_RX_ADDR_WRAP;
+ rx->addr = cpu_to_le32 ( addr | iob_dma ( iobuf ) );
+
+ /* Record I/O buffer */
+ assert ( cgem->rx_iobuf[rx_idx] == NULL );
+ cgem->rx_iobuf[rx_idx] = iobuf;
+
+ DBGC2 ( cgem, "CGEM %s RX %d is [%08lx,%08lx)\n",
+ cgem->name, rx_idx, virt_to_phys ( iobuf->data ),
+ ( virt_to_phys ( iobuf->data ) + CGEM_RX_LEN ) );
+ }
+}
+
+/**
+ * Open network device
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int cgem_open ( struct net_device *netdev ) {
+ struct cgem_nic *cgem = netdev->priv;
+ union cgem_mac mac;
+ int rc;
+
+ /* Create transmit descriptor ring */
+ if ( ( rc = cgem_create_ring ( cgem, &cgem->tx ) ) != 0 )
+ goto err_create_tx;
+
+ /* Create receive descriptor ring */
+ if ( ( rc = cgem_create_ring ( cgem, &cgem->rx ) ) != 0 )
+ goto err_create_rx;
+
+ /* Set MAC address */
+ memcpy ( mac.raw, netdev->ll_addr, ETH_ALEN );
+ writel ( mac.reg.low, ( cgem->regs + CGEM_LADDRL ) );
+ writel ( mac.reg.high, ( cgem->regs + CGEM_LADDRH ) );
+
+ /* Enable transmit and receive */
+ writel ( CGEM_NWCTRL_NORMAL, ( cgem->regs + CGEM_NWCTRL ) );
+
+ /* Refill receive descriptor ring */
+ cgem_refill_rx ( cgem );
+
+ /* Update link state */
+ cgem_check_link ( netdev );
+
+ /* Start link state timer */
+ start_timer_fixed ( &cgem->timer, CGEM_LINK_INTERVAL );
+
+ return 0;
+
+ cgem_destroy_ring ( cgem, &cgem->rx );
+ err_create_rx:
+ cgem_destroy_ring ( cgem, &cgem->tx );
+ err_create_tx:
+ return rc;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev Network device
+ */
+static void cgem_close ( struct net_device *netdev ) {
+ struct cgem_nic *cgem = netdev->priv;
+ unsigned int i;
+
+ /* Stop link state timer */
+ stop_timer ( &cgem->timer );
+
+ /* Reset NIC */
+ cgem_reset ( cgem );
+
+ /* Discard unused receive buffers */
+ for ( i = 0 ; i < CGEM_NUM_RX_DESC ; i++ ) {
+ if ( cgem->rx_iobuf[i] )
+ free_rx_iob ( cgem->rx_iobuf[i] );
+ cgem->rx_iobuf[i] = NULL;
+ }
+
+ /* Destroy receive descriptor ring */
+ cgem_destroy_ring ( cgem, &cgem->rx );
+
+ /* Destroy transmit descriptor ring */
+ cgem_destroy_ring ( cgem, &cgem->tx );
+}
+
+/**
+ * Transmit packet
+ *
+ * @v netdev Network device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int cgem_transmit ( struct net_device *netdev,
+ struct io_buffer *iobuf ) {
+ struct cgem_nic *cgem = netdev->priv;
+ struct cgem_descriptor *tx;
+ unsigned int tx_idx;
+ uint32_t flags;
+ int rc;
+
+ /* Get next transmit descriptor */
+ if ( ( cgem->tx.prod - cgem->tx.cons ) >= CGEM_NUM_TX_DESC ) {
+ DBGC ( cgem, "CGEM %s out of transmit descriptors\n",
+ cgem->name );
+ return -ENOBUFS;
+ }
+ tx_idx = ( cgem->tx.prod % CGEM_NUM_TX_DESC );
+ tx = &cgem->tx.desc[tx_idx];
+
+ /* Pad to minimum length */
+ iob_pad ( iobuf, ETH_ZLEN );
+
+ /* Map I/O buffer */
+ if ( ( rc = iob_map_tx ( iobuf, cgem->dma ) ) != 0 )
+ return rc;
+
+ /* Update producer index */
+ cgem->tx.prod++;
+
+ /* Populate transmit descriptor */
+ flags = CGEM_TX_FL_LAST;
+ if ( ( cgem->tx.prod % CGEM_NUM_TX_DESC ) == 0 )
+ flags |= CGEM_TX_FL_WRAP;
+ tx->addr = cpu_to_le32 ( iob_dma ( iobuf ) );
+ wmb();
+ tx->flags = cpu_to_le32 ( flags | iob_len ( iobuf ) );
+ wmb();
+
+ /* Initiate transmission */
+ writel ( ( CGEM_NWCTRL_NORMAL | CGEM_NWCTRL_STARTTX ),
+ ( cgem->regs + CGEM_NWCTRL ) );
+
+ DBGC2 ( cgem, "CGEM %s TX %d is [%08lx,%08lx)\n",
+ cgem->name, tx_idx, virt_to_phys ( iobuf->data ),
+ ( virt_to_phys ( iobuf->data ) + iob_len ( iobuf ) ) );
+ return 0;
+}
+
+/**
+ * Poll for completed packets
+ *
+ * @V netdev Network device
+ */
+static void cgem_poll_tx ( struct net_device *netdev ) {
+ struct cgem_nic *cgem = netdev->priv;
+ struct cgem_descriptor *tx;
+ unsigned int tx_idx;
+
+ /* Check for completed packets */
+ while ( cgem->tx.cons != cgem->tx.prod ) {
+
+ /* Get next transmit descriptor */
+ tx_idx = ( cgem->tx.cons % CGEM_NUM_TX_DESC );
+ tx = &cgem->tx.desc[tx_idx];
+
+ /* Stop if descriptor is still owned by hardware */
+ if ( ! ( tx->flags & cpu_to_le32 ( CGEM_TX_FL_OWNED ) ) )
+ return;
+ DBGC2 ( cgem, "CGEM %s TX %d complete\n",
+ cgem->name, tx_idx );
+
+ /* Complete transmit descriptor */
+ netdev_tx_complete_next ( netdev );
+ cgem->tx.cons++;
+ }
+}
+
+/**
+ * Poll for received packets
+ *
+ * @v netdev Network device
+ */
+static void cgem_poll_rx ( struct net_device *netdev ) {
+ struct cgem_nic *cgem = netdev->priv;
+ struct cgem_descriptor *rx;
+ struct io_buffer *iobuf;
+ unsigned int rx_idx;
+ uint32_t flags;
+ size_t len;
+
+ /* Check for received packets */
+ while ( cgem->rx.cons != cgem->rx.prod ) {
+
+ /* Get next receive descriptor */
+ rx_idx = ( cgem->rx.cons % CGEM_NUM_RX_DESC );
+ rx = &cgem->rx.desc[rx_idx];
+
+ /* Stop if descriptor is still in use */
+ if ( ! ( rx->addr & cpu_to_le32 ( CGEM_RX_ADDR_OWNED ) ) )
+ return;
+
+ /* Populate I/O buffer */
+ iobuf = cgem->rx_iobuf[rx_idx];
+ cgem->rx_iobuf[rx_idx] = NULL;
+ flags = le32_to_cpu ( rx->flags );
+ len = CGEM_RX_FL_LEN ( flags );
+ iob_put ( iobuf, len );
+ DBGC2 ( cgem, "CGEM %s RX %d complete (length %zd)\n",
+ cgem->name, rx_idx, len );
+
+ /* Hand off to network stack */
+ netdev_rx ( netdev, iobuf );
+ cgem->rx.cons++;
+ }
+}
+
+/**
+ * Poll for completed and received packets
+ *
+ * @v netdev Network device
+ */
+static void cgem_poll ( struct net_device *netdev ) {
+ struct cgem_nic *cgem = netdev->priv;
+
+ /* Poll for TX competions */
+ cgem_poll_tx ( netdev );
+
+ /* Poll for RX completions */
+ cgem_poll_rx ( netdev );
+
+ /* Refill RX ring */
+ cgem_refill_rx ( cgem );
+}
+
+/** Cadence GEM network device operations */
+static struct net_device_operations cgem_operations = {
+ .open = cgem_open,
+ .close = cgem_close,
+ .transmit = cgem_transmit,
+ .poll = cgem_poll,
+};
+
+/******************************************************************************
+ *
+ * Devicetree interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Probe devicetree device
+ *
+ * @v dt Devicetree device
+ * @v offset Starting node offset
+ * @ret rc Return status code
+ */
+static int cgem_probe ( struct dt_device *dt, unsigned int offset ) {
+ struct net_device *netdev;
+ struct cgem_nic *cgem;
+ union cgem_mac mac;
+ int rc;
+
+ /* Allocate and initialise net device */
+ netdev = alloc_etherdev ( sizeof ( *cgem ) );
+ if ( ! netdev ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ netdev_init ( netdev, &cgem_operations );
+ cgem = netdev->priv;
+ dt_set_drvdata ( dt, netdev );
+ netdev->dev = &dt->dev;
+ memset ( cgem, 0, sizeof ( *cgem ) );
+ cgem->dma = &dt->dma;
+ cgem->netdev = netdev;
+ cgem->name = netdev->dev->name;
+ mdio_init ( &cgem->mdio, &cgem_mii_operations );
+ mii_init ( &cgem->mii, &cgem->mdio, 0 );
+ timer_init ( &cgem->timer, cgem_expired, &netdev->refcnt );
+ cgem_init_ring ( &cgem->tx, CGEM_NUM_TX_DESC, CGEM_TXQBASE );
+ cgem_init_ring ( &cgem->rx, CGEM_NUM_RX_DESC, CGEM_RXQBASE );
+
+ /* Map registers */
+ cgem->regs = dt_ioremap ( dt, offset, CGEM_REG_IDX, CGEM_REG_LEN );
+ if ( ! cgem->regs ) {
+ rc = -ENODEV;
+ goto err_ioremap;
+ }
+
+ /* Reset the NIC */
+ if ( ( rc = cgem_reset ( cgem ) ) != 0 )
+ goto err_reset;
+
+ /* Initialise the PHY */
+ if ( ( rc = cgem_init_phy ( cgem ) ) != 0 )
+ goto err_init_phy;
+
+ /* Fetch devicetree MAC address */
+ if ( ( rc = fdt_mac ( &sysfdt, offset, netdev ) ) != 0 ) {
+ DBGC ( cgem, "CGEM %s could not fetch MAC: %s\n",
+ cgem->name, strerror ( rc ) );
+ goto err_mac;
+ }
+
+ /* Fetch current MAC address, if set */
+ mac.reg.low = readl ( cgem->regs + CGEM_LADDRL );
+ mac.reg.high = readl ( cgem->regs + CGEM_LADDRH );
+ memcpy ( netdev->ll_addr, mac.raw, ETH_ALEN );
+
+ /* Register network device */
+ if ( ( rc = register_netdev ( netdev ) ) != 0 )
+ goto err_register_netdev;
+
+ /* Set initial link state */
+ cgem_check_link ( netdev );
+
+ return 0;
+
+ unregister_netdev ( netdev );
+ err_register_netdev:
+ err_mac:
+ err_init_phy:
+ cgem_reset ( cgem );
+ err_reset:
+ iounmap ( cgem->regs );
+ err_ioremap:
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Remove devicetree device
+ *
+ * @v dt Devicetree device
+ */
+static void cgem_remove ( struct dt_device *dt ) {
+ struct net_device *netdev = dt_get_drvdata ( dt );
+ struct cgem_nic *cgem = netdev->priv;
+
+ /* Unregister network device */
+ unregister_netdev ( netdev );
+
+ /* Reset card */
+ cgem_reset ( cgem );
+
+ /* Free network device */
+ iounmap ( cgem->regs );
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+}
+
+/** Cadence GEM compatible model identifiers */
+static const char * cgem_ids[] = {
+ "sifive,fu540-c000-gem",
+};
+
+/** Cadence GEM devicetree driver */
+struct dt_driver cgem_driver __dt_driver = {
+ .name = "cgem",
+ .ids = cgem_ids,
+ .id_count = ( sizeof ( cgem_ids ) / sizeof ( cgem_ids[0] ) ),
+ .probe = cgem_probe,
+ .remove = cgem_remove,
+};
--- /dev/null
+#ifndef _CGEM_H
+#define _CGEM_H
+
+/** @file
+ *
+ * Cadence Gigabit Ethernet MAC (GEM) network driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/if_ether.h>
+#include <ipxe/mii.h>
+#include <ipxe/dma.h>
+#include <ipxe/retry.h>
+
+/** I/O region index */
+#define CGEM_REG_IDX 0
+
+/** I/O region length */
+#define CGEM_REG_LEN 0x800
+
+/** Network control register */
+#define CGEM_NWCTRL 0x000
+#define CGEM_NWCTRL_STARTTX 0x00000200 /**< Start transmission */
+#define CGEM_NWCTRL_STATCLR 0x00000020 /**< Clear statistics */
+#define CGEM_NWCTRL_MDEN 0x00000010 /**< MII interface enable */
+#define CGEM_NWCTRL_TXEN 0x00000008 /**< Transmit enable */
+#define CGEM_NWCTRL_RXEN 0x00000004 /**< Receive enable */
+
+/** Normal value for network control register while up and running */
+#define CGEM_NWCTRL_NORMAL \
+ ( CGEM_NWCTRL_MDEN | CGEM_NWCTRL_TXEN | CGEM_NWCTRL_RXEN )
+
+/** Network configuration register */
+#define CGEM_NWCFG 0x004
+
+/** Network status register */
+#define CGEM_NWSR 0x008
+#define CGEM_NWSR_MII_IDLE 0x00000004 /**< MII interface is idle */
+
+/** DMA configuration register */
+#define CGEM_DMACR 0x010
+#define CGEM_DMACR_RXBUF( x ) ( ( (x) / 64 ) << 16 ) /**< RX buffer size */
+#define CGEM_DMACR_TXSIZE( x ) ( (x) << 10 ) /**< TX memory size */
+#define CGEM_DMACR_TXSIZE_MAX \
+ CGEM_DMACR_TXSIZE ( 0x1 ) /**< Max TX memory size */
+#define CGEM_DMACR_RXSIZE( x ) ( (x) << 8 ) /**< RX memory size */
+#define CGEM_DMACR_RXSIZE_MAX \
+ CGEM_DMACR_RXSIZE ( 0x3 ) /**< Max RX memory size */
+#define CGEM_DMACR_BLENGTH( x ) ( (x) << 0 ) /**< DMA burst length */
+#define CGEM_DMACR_BLENGTH_MAX \
+ CGEM_DMACR_BLENGTH ( 0x10 ) /**< Max DMA burst length */
+
+/** RX queue base address register */
+#define CGEM_RXQBASE 0x018
+
+/** TX queue base address register */
+#define CGEM_TXQBASE 0x01c
+
+/** Interrupt disable register */
+#define CGEM_IDR 0x02c
+#define CGEM_IDR_ALL 0xffffffff /**< Disable all interrupts */
+
+/** PHY maintenance register */
+#define CGEM_PHYMNTNC 0x034
+#define CGEM_PHYMNTNC_CLAUSE22 0x40000000 /**< Clause 22 operation */
+#define CGEM_PHYMNTNC_OP_WRITE 0x10000000 /**< Write to PHY register */
+#define CGEM_PHYMNTNC_OP_READ 0x20000000 /**< Read from PHY register */
+#define CGEM_PHYMNTNC_ADDR( x ) ( (x) << 23 ) /**< PHY address */
+#define CGEM_PHYMNTNC_REG( x ) ( (x) << 18 ) /**< Register address */
+#define CGEM_PHYMNTNC_FIXED 0x00020000 /**< Fixed value to write */
+#define CGEM_PHYMNTNC_DATA_MASK 0x0000ffff /**< Data mask */
+
+/** Maximum time to wait for PHY access, in microseconds */
+#define CGEM_MII_MAX_WAIT_US 500
+
+/** Link state check interval */
+#define CGEM_LINK_INTERVAL ( 2 * TICKS_PER_SEC )
+
+/** Local MAC address (low half) register */
+#define CGEM_LADDRL 0x088
+
+/** Local MAC address (high half) register */
+#define CGEM_LADDRH 0x08c
+
+/** A Cadence GEM descriptor */
+struct cgem_descriptor {
+ /** Buffer address */
+ uint32_t addr;
+ /** Flags */
+ uint32_t flags;
+} __attribute__ (( packed ));
+
+/** Transmit flags */
+#define CGEM_TX_FL_OWNED 0x80000000 /**< Owned by software */
+#define CGEM_TX_FL_WRAP 0x40000000 /**< End of descriptor ring */
+#define CGEM_TX_FL_LAST 0x00008000 /**< Last buffer in frame */
+
+/** Transmit ring length */
+#define CGEM_NUM_TX_DESC 8
+
+/** Receive flags (in buffer address) */
+#define CGEM_RX_ADDR_OWNED 0x00000001 /**< Owned by software */
+#define CGEM_RX_ADDR_WRAP 0x00000002 /**< End of descriptor ring */
+
+/** Receive flags */
+#define CGEM_RX_FL_LEN( x ) ( (x) & 0x1fff ) /**< RX packet length */
+
+/** Receive ring length */
+#define CGEM_NUM_RX_DESC 8
+
+/** Length of receive buffers
+ *
+ * Must be a multiple of 64.
+ */
+#define CGEM_RX_LEN 1536
+
+/** A Cadence GEM MAC address */
+union cgem_mac {
+ struct {
+ uint32_t low;
+ uint32_t high;
+ } __attribute__ (( packed )) reg;
+ uint8_t raw[ETH_ALEN];
+};
+
+/** A Cadence GEM descriptor ring */
+struct cgem_ring {
+ /** Descriptors */
+ struct cgem_descriptor *desc;
+ /** Descriptor ring DMA mapping */
+ struct dma_mapping map;
+ /** Producer index */
+ unsigned int prod;
+ /** Consumer index */
+ unsigned int cons;
+
+ /** Queue base address register */
+ uint8_t qbase;
+ /** Number of descriptors */
+ uint8_t count;
+ /** Length of descriptors */
+ uint16_t len;
+};
+
+/**
+ * Initialise descriptor ring
+ *
+ * @v ring Descriptor ring
+ * @v count Number of descriptors
+ * @v qbase Queue base address register
+ */
+static inline __attribute__ (( always_inline )) void
+cgem_init_ring ( struct cgem_ring *ring, unsigned int count,
+ unsigned int qbase ) {
+
+ ring->qbase = qbase;
+ ring->count = count;
+ ring->len = ( count * sizeof ( ring->desc[0] ) );
+}
+
+/** A Cadence GEM network card */
+struct cgem_nic {
+ /** Registers */
+ void *regs;
+ /** DMA device */
+ struct dma_device *dma;
+ /** Network device */
+ struct net_device *netdev;
+ /** Device name (for debugging) */
+ const char *name;
+
+ /** PHY interface */
+ struct mii_interface mdio;
+ /** PHY device */
+ struct mii_device mii;
+ /** Link state timer */
+ struct retry_timer timer;
+
+ /** Transmit ring */
+ struct cgem_ring tx;
+ /** Receive ring */
+ struct cgem_ring rx;
+ /** Receive I/O buffers */
+ struct io_buffer *rx_iobuf[CGEM_NUM_RX_DESC];
+};
+
+#endif /* _CGEM_H */