]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[cgem] Add a driver for the Cadence GEM NIC
authorMichael Brown <mcb30@ipxe.org>
Sat, 19 Apr 2025 10:54:08 +0000 (11:54 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sat, 19 Apr 2025 10:54:08 +0000 (11:54 +0100)
Add a basic driver for the Cadence GEM network interface as emulated
by QEMU when using the RISC-V "sifive_u" machine type.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/drivers/net/cgem.c [new file with mode: 0644]
src/drivers/net/cgem.h [new file with mode: 0644]
src/include/ipxe/devtree.h
src/include/ipxe/errfile.h

diff --git a/src/drivers/net/cgem.c b/src/drivers/net/cgem.c
new file mode 100644 (file)
index 0000000..c935c80
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ * 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,
+};
diff --git a/src/drivers/net/cgem.h b/src/drivers/net/cgem.h
new file mode 100644 (file)
index 0000000..c91e367
--- /dev/null
@@ -0,0 +1,189 @@
+#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 */
index 911e7ad1515bcdf111c341dfaa1b33e444b1eb05..04414f370280181e3494bf77bd8b02015b34e256 100644 (file)
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <ipxe/device.h>
+#include <ipxe/dma.h>
 
 /** A devicetree device */
 struct dt_device {
        /** Generic device */
        struct device dev;
+       /** DMA device */
+       struct dma_device dma;
        /** Device path */
        const char *path;
        /** Driver for this device */
index 2b88067064a72dd6bb2ec1914837274f0c550933..37e36c720d9485cdd5b304b4bb65f18474f9f9d9 100644 (file)
@@ -232,6 +232,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_atl_hw              ( ERRFILE_DRIVER | 0x00d80000 )
 #define ERRFILE_atl2_hw                     ( ERRFILE_DRIVER | 0x00d90000 )
 #define ERRFILE_devtree                     ( ERRFILE_DRIVER | 0x00da0000 )
+#define ERRFILE_cgem                ( ERRFILE_DRIVER | 0x00db0000 )
 
 #define ERRFILE_aoe                    ( ERRFILE_NET | 0x00000000 )
 #define ERRFILE_arp                    ( ERRFILE_NET | 0x00010000 )