mirror of
https://github.com/ipxe/ipxe
synced 2025-12-07 18:00:28 +03:00
[cgem] Add a driver for the Cadence GEM NIC
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>
This commit is contained in:
711
src/drivers/net/cgem.c
Normal file
711
src/drivers/net/cgem.c
Normal file
@@ -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,
|
||||
};
|
||||
189
src/drivers/net/cgem.h
Normal file
189
src/drivers/net/cgem.h
Normal file
@@ -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 */
|
||||
@@ -10,11 +10,14 @@
|
||||
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 */
|
||||
|
||||
@@ -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 )
|
||||
|
||||
Reference in New Issue
Block a user