text: /****************************************************************************
   * arch/arm/src/lpc54xx/lpx54_ethernet.c
   *
   *   Copyright (C) 2017 Gregory Nutt. All rights reserved.
   *   Author: Gregory Nutt <gnutt@nuttx.org>
   *
   * Some of the logic in this file was developed using sample code provided by
   * NXP that has a compatible BSD license:
   *
   *   Copyright (c) 2016, Freescale Semiconductor, Inc.
   *   Copyright 2016-2017 NXP
   *   All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   * 3. Neither the name NuttX nor the names of its contributors may be
   *    used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
   * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
   * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   *
   ****************************************************************************/
  
  /* TODO:
   *
   * 1. Timestamps not supported
   *
   * 2. Multi-queuing not fully; supported.  The second queue is intended to
   *    support QVLAN, AVB type packets which have an 18-byte IEEE 802.1q
   *    Ethernet header.  I propose handling this case with a new network
   *    interface qvlan_input().
   *
   * 3. Multicast address filtering.  Unlike other hardware, this Ethernet
   *    does not seem to support explicit Multicast address filtering as
   *    needed for ICMPv6 and for IGMP.  In these cases, I am simply accepting
   *    all multicast packets.  I am not sure if that is the right thing to
   *    do.
   */
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  
  #include <sys/ioctl.h>
  #include <stdint.h>
  #include <stdbool.h>
  #include <time.h>
  #include <string.h>
  #include <queue.h>
  #include <errno.h>
  #include <assert.h>
  #include <debug.h>
  
  #include <arpa/inet.h>
  
  #include <nuttx/arch.h>
  #include <nuttx/irq.h>
  #include <nuttx/wdog.h>
  #include <nuttx/wqueue.h>
  #include <nuttx/clock.h>
  #include <nuttx/net/mii.h>
  #include <nuttx/net/arp.h>
  #include <nuttx/net/netdev.h>
  
  #ifdef CONFIG_NET_PKT
  #  include <nuttx/net/pkt.h>
  #endif
  
  #include "arm_arch.h"
  #include "hardware/lpc54_syscon.h"
  #include "hardware/lpc54_pinmux.h"
  #include "hardware/lpc54_ethernet.h"
  #include "lpc54_enableclk.h"
  #include "lpc54_reset.h"
  #include "lpc54_gpio.h"
  
  #include <arch/board/board.h>
  
  #ifdef CONFIG_LPC54_ETHERNET
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  /* Work queue support is required. */
  
  #if !defined(CONFIG_SCHED_WORKQUEUE)
  #  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
  #endif
  
  /* Multicast address filtering.  Unlike other hardware, this Ethernet does
   * not seem to support explicit Multicast address filtering as needed for
   * ICMPv6 and for IGMP.  In these cases, I am simply accepting all multicast
   * packets.
   */
  
  #undef LPC54_ACCEPT_ALLMULTICAST
  #if  defined(CONFIG_LPC54_ETH_RX_ALLMULTICAST) || \
       defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
  #  define LPC54_ACCEPT_ALLMULTICAST 1
  #endif
  
  /* The low priority work queue is preferred.  If it is not enabled, LPWORK
   * will be the same as HPWORK.
   *
   * NOTE:  However, the network should NEVER run on the high priority work
   * queue!  That queue is intended only to service short back end interrupt
   * processing that never suspends.  Suspending the high priority work queue
   * may bring the system to its knees!
   */
  
  #define ETHWORK LPWORK
  
  /* TX poll delay = 1 seconds.
   * CLK_TCK is the number of clock ticks per second
   */
  
  #define LPC54_WDDELAY   (1*CLK_TCK)
  
  /* TX timeout = 1 minute */
  
  #define LPC54_TXTIMEOUT (60*CLK_TCK)
  
  /* PHY-related definitions */
  
  #define LPC54_PHY_TIMEOUT 0x00ffffff  /* Timeout for PHY register accesses */
  
  #ifdef CONFIG_ETH0_PHY_LAN8720
  #  define LPC54_PHYID1_VAL MII_PHYID1_LAN8720
  #else
  #  error Unrecognized PHY selection
  #endif
  
  /* MTL-related definitions */
  
  #define LPC54_MTL_QUEUE_UNIT      256
  #define LPC54_MTL_RXQUEUE_UNITS   8     /* Rx queue size = 2048 bytes */
  #define LPC54_MTL_TXQUEUE_UNITS   8     /* Tx queue size = 2048 bytes */
  
  #ifdef CONFIG_LPC54_ETH_TXRR
  #  define LPC54_MTL_OPMODE_SCHALG ETH_MTL_OP_MODE_SHALG_WSP
  #else
  #  define LPC54_MTL_OPMODE_SCHALG ETH_MTL_OP_MODE_SHALG_SP
  #endif
  
  #ifdef CONFIG_LPC54_ETH_RXRR
  #  define LPC54_MTL_OPMODE_RAA    ETH_MTL_OP_MODE_RAA_WSP
  #else
  #  define LPC54_MTL_OPMODE_RAA    ETH_MTL_OP_MODE_RAA_SP
  #endif
  
  /* MAC-related definitions */
  
  #define LPC54_MAC_HALFDUPLEX_IPG ETH_MAC_CONFIG_IPG_64 /* Default half-duplex IPG */
  
  /* Packet-buffer definitions */
  
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
  #  define LPC54_NBUFFERS        (CONFIG_LPC54_ETH_NRXDESC0 + \
                                   CONFIG_LPC54_ETH_NRXDESC1 + \
                                   CONFIG_LPC54_ETH_NTXDESC0 + \
                                   CONFIG_LPC54_ETH_NTXDESC1)
  #else
  #  define LPC54_NBUFFERS        (CONFIG_LPC54_ETH_NRXDESC0 + \
                                   CONFIG_LPC54_ETH_NTXDESC0)
  #endif
  
  #define LPC54_BUFFER_SIZE       MAX_NETDEV_PKTSIZE
  #define LPC54_BUFFER_ALLOC      ((MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE + 3) & ~3)
  #define LPC54_BUFFER_WORDS      ((MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE + 3) >> 2)
  #define LPC54_BUFFER_MAX        16384
  
  /* DMA and DMA descriptor definitions */
  
  #define LPC54_MIN_RINGLEN       4     /* Min length of a ring */
  #define LPC54_MAX_RINGLEN       1023  /* Max length of a ring */
  #define LPC54_MAX_RINGS         2     /* Max number of tx/rx descriptor rings */
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
  #  define LPC54_NRINGS          2     /* Use 2 Rx and Tx rings */
  #else
  #  define LPC54_NRINGS          1     /* Use 1 Rx and 1 Tx ring */
  #endif
  
  #ifndef CONFIG_LPC54_ETH_BURSTLEN
  #  define CONFIG_LPC54_ETH_BURSTLEN 1
  #endif
  
  #if CONFIG_LPC54_ETH_BURSTLEN < 2
  #  define LPC54_BURSTLEN        1
  #  define LPC54_PBLX8           0
  #elif CONFIG_LPC54_ETH_BURSTLEN < 4
  #  define LPC54_BURSTLEN        2
  #  define LPC54_PBLX8           0
  #elif CONFIG_LPC54_ETH_BURSTLEN < 8
  #  define LPC54_BURSTLEN        4
  #  define LPC54_PBLX8           0
  #elif CONFIG_LPC54_ETH_BURSTLEN < 16
  #  define LPC54_BURSTLEN        8
  #  define LPC54_PBLX8           0
  #elif CONFIG_LPC54_ETH_BURSTLEN < 32
  #  define LPC54_BURSTLEN        16
  #  define LPC54_PBLX8           0
  #elif CONFIG_LPC54_ETH_BURSTLEN < 64
  #  define LPC54_BURSTLEN        32
  #  define LPC54_PBLX8           0
  #elif CONFIG_LPC54_ETH_BURSTLEN < 128
  #  define LPC54_BURSTLEN        8
  #  define LPC54_PBLX8           ETH_DMACH_CTRL_PBLx8
  #elif CONFIG_LPC54_ETH_BURSTLEN < 256
  #  define LPC54_BURSTLEN        16
  #  define LPC54_PBLX8           ETH_DMACH_CTRL_PBLx8
  #else
  #  define LPC54_BURSTLEN        32
  #  define LPC54_PBLX8           ETH_DMACH_CTRL_PBLx8
  #endif
  
  #ifdef CONFIG_LPC54_ETH_DYNAMICMAP
  #  define LPC54_QUEUEMAP        (ETH_MTL_RXQ_DMA_MAP_Q0DDMACH | \
                                   ETH_MTL_RXQ_DMA_MAP_Q1DDMACH)
  #else
  #  define LPC54_QUEUEMAP        ETH_MTL_RXQ_DMA_MAP_Q1MDMACH
  #endif
  
  /* Interrupt masks */
  
  #define LPC54_ABNORM_INTMASK    (ETH_DMACH_INT_TS  | ETH_DMACH_INT_RBU | \
                                   ETH_DMACH_INT_RS  | ETH_DMACH_INT_RWT | \
                                   ETH_DMACH_INT_FBE | ETH_DMACH_INT_AI)
  #define LPC54_TXERR_INTMASK     (ETH_DMACH_INT_TS)
  #define LPC54_RXERR_INTMASK     (ETH_DMACH_INT_RBU | ETH_DMACH_INT_RS  | \
                                   ETH_DMACH_INT_RWT)
  #define LPC54_NORM_INTMASK      (ETH_DMACH_INT_TI  | ETH_DMACH_INT_RI  | \
                                   ETH_DMACH_INT_NI)
  
  /* This is a helper pointer for accessing the contents of the Ethernet
   * header.
   */
  
  #define ETHBUF       ((struct eth_hdr_s *)priv->eth_dev.d_buf)
  #define ETH8021QWBUF ((struct eth_8021qhdr_s *)priv->eth_dev.d_buf)
  
  /****************************************************************************
   * Private Types
   ****************************************************************************/
  
  /* Describes the state of one Tx descriptor ring */
  
  struct lpc54_txring_s
  {
    struct enet_txdesc_s *tr_desc; /* Tx descriptor base address */
    uint16_t tr_supply;            /* Tx supplier ring index */
    uint16_t tr_consume;           /* Tx consumer ring index */
    uint16_t tr_ndesc;             /* Number or descriptors in the Tx ring */
    uint16_t tr_inuse;             /* Number of Tx descriptors in-use */
    uint32_t **tr_buffers;         /* Packet buffers assigned to the Rx ring */
  };
  
  /* Describes the state of one Rx descriptor ring */
  
  struct lpc54_rxring_s
  {
    struct enet_rxdesc_s *rr_desc; /* Rx descriptor base address */
    uint16_t rr_supply;            /* Available Rx descriptor ring index */
    uint16_t rr_ndesc;             /* Number or descriptors in the Rx ring */
    uint32_t **rr_buffers;         /* Packet buffers assigned to the Rx ring */
  };
  
  /* The lpc54_ethdriver_s encapsulates all state information for a single
   * Ethernet interface
   */
  
  struct lpc54_ethdriver_s
  {
    uint8_t eth_bifup : 1;         /* 1:ifup 0:ifdown */
    uint8_t eth_fullduplex : 1;    /* 1:Full duplex 0:Half duplex mode */
    uint8_t eth_100mbps : 1;       /* 1:100mbps 0:10mbps */
    uint8_t eth_rxdiscard : 1;     /* 1:Discarding Rx data */
    struct wdog_s eth_txpoll;      /* TX poll timer */
    struct wdog_s eth_txtimeout;   /* TX timeout timer */
    struct work_s eth_irqwork;     /* For deferring interrupt work to the work queue */
    struct work_s eth_pollwork;    /* For deferring poll work to the work queue */
    struct work_s eth_timeoutwork; /* For deferring timeout work to the work queue */
    struct sq_queue_s eth_freebuf; /* Free packet buffers */
  
    /* Ring state */
  
    struct lpc54_txring_s eth_txring[LPC54_NRINGS];
    struct lpc54_rxring_s eth_rxring[LPC54_NRINGS];
  
    /* This holds the information visible to the NuttX network */
  
    struct net_driver_s eth_dev;   /* Interface understood by the network */
  };
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  /* These statically allocated structures are possible because only a single
   * instance of the Ethernet device could be supported.  In order to support
   * multiple devices instances, this data would have to be allocated
   * dynamically.
   */
  
  /* Driver state structure */
  
  static struct lpc54_ethdriver_s g_ethdriver;
  
  /* Rx DMA descriptors */
  
  static struct enet_rxdesc_s g_ch0_rxdesc[CONFIG_LPC54_ETH_NRXDESC0];
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
  static struct enet_rxdesc_s g_ch1_rxdesc[CONFIG_LPC54_ETH_NRXDESC1];
  #endif
  
  /* Tx DMA descriptors */
  
  static struct enet_txdesc_s g_ch0_txdesc[CONFIG_LPC54_ETH_NTXDESC0];
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
  static struct enet_txdesc_s g_ch1_txdesc[CONFIG_LPC54_ETH_NTXDESC1];
  #endif
  
  /* Preallocated packet buffers */
  
  static uint32_t g_prealloc_buffers[LPC54_NBUFFERS * LPC54_BUFFER_WORDS];
  
  /* Packet buffers assigned to Rx and Tx descriptors.  The packet buffer
   * addresses are lost in the DMA due to write-back from the DMA hardware.
   * So we have to remember the buffer assignments explicitly.
   *
   * REVISIT:  According to the User manual, buffer1 and buffer2 addresses
   * will be overwritten by DMA write-back data.  However, I see that the
   * Rx buffer1 and buffer2 addresses are, indeed, used by the NXP example
   * code after completion of DMA so the user manual must be wrong.  We
   * could eliminate this extra array of saved allocation addresses.
   */
  
  static uint32_t *g_rxbuffers0[CONFIG_LPC54_ETH_NRXDESC0];
  static uint32_t *g_txbuffers0[CONFIG_LPC54_ETH_NTXDESC0];
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
  static uint32_t *g_rxbuffers1[CONFIG_LPC54_ETH_NRXDESC1];
  static uint32_t *g_txbuffers1[CONFIG_LPC54_ETH_NTXDESC1];
  #endif
  
  /****************************************************************************
   * Private Function Prototypes
   ****************************************************************************/
  
  /* Register level debug hooks */
  
  #ifdef CONFIG_LPC54_ETH_REGDEBUG
  static uint32_t lpc54_getreg(uintptr_t addr);
  static void lpc54_putreg(uint32_t val, uintptr_t addr);
  #else
  # define lpc54_getreg(addr)      getreg32(addr)
  # define lpc54_putreg(val,addr)  putreg32(val,addr)
  #endif
  
  /* Common TX logic */
  
  static int  lpc54_eth_transmit(struct lpc54_ethdriver_s *priv,
                unsigned int chan);
  static unsigned int lpc54_eth_getring(struct lpc54_ethdriver_s *priv);
  static int  lpc54_eth_txpoll(struct net_driver_s *dev);
  
  /* Interrupt handling */
  
  static void lpc54_eth_reply(struct lpc54_ethdriver_s *priv);
  static void lpc54_eth_rxdispatch(struct lpc54_ethdriver_s *priv);
  static int  lpc54_eth_receive(struct lpc54_ethdriver_s *priv,
                unsigned int chan);
  static void lpc54_eth_txdone(struct lpc54_ethdriver_s *priv,
                unsigned int chan);
  
  static void lpc54_eth_channel_work(struct lpc54_ethdriver_s *priv,
                unsigned int chan);
  static void lpc54_eth_interrupt_work(void *arg);
  static int  lpc54_eth_interrupt(int irq, void *context, void *arg);
  #if 0 /* Not used */
  static int  lpc54_pmt_interrupt(int irq, void *context, void *arg);
  static int  lpc54_mac_interrupt(int irq, void *context, void *arg);
  #endif
  
  /* Watchdog timer expirations */
  
  static void lpc54_eth_dotimer(struct lpc54_ethdriver_s *priv);
  static void lpc54_eth_dopoll(struct lpc54_ethdriver_s *priv);
  
  static void lpc54_eth_txtimeout_work(void *arg);
  static void lpc54_eth_txtimeout_expiry(wdparm_t arg);
  
  static void lpc54_eth_poll_work(void *arg);
  static void lpc54_eth_poll_expiry(wdparm_t arg);
  
  /* NuttX callback functions */
  
  static int  lpc54_eth_ifup(struct net_driver_s *dev);
  static int  lpc54_eth_ifdown(struct net_driver_s *dev);
  
  static void lpc54_eth_txavail_work(void *arg);
  static int  lpc54_eth_txavail(struct net_driver_s *dev);
  
  #ifdef CONFIG_NET_MCASTGROUP
  static int  lpc54_eth_addmac(struct net_driver_s *dev,
                const uint8_t *mac);
  static int  lpc54_eth_rmmac(struct net_driver_s *dev,
                const uint8_t *mac);
  #endif
  #ifdef CONFIG_NETDEV_IOCTL
  static int  lpc54_eth_ioctl(struct net_driver_s *dev, int cmd,
                unsigned long arg);
  #endif
  
  /* Packet buffers */
  
  static void lpc54_pktbuf_initialize(struct lpc54_ethdriver_s *priv);
  static inline uint32_t *lpc54_pktbuf_alloc(struct lpc54_ethdriver_s *priv);
  static inline void lpc54_pktbuf_free(struct lpc54_ethdriver_s *priv,
                uint32_t *pktbuf);
  
  /* DMA descriptor rings */
  
  static void lpc54_txring_initialize(struct lpc54_ethdriver_s *priv,
                unsigned int chan);
  static void lpc54_rxring_initialize(struct lpc54_ethdriver_s *priv,
                unsigned int chan);
  static void lpc54_ring_initialize(struct lpc54_ethdriver_s *priv);
  
  /* Initialization/PHY control */
  
  static void lpc54_set_csrdiv(void);
  static uint16_t lpc54_phy_read(struct lpc54_ethdriver_s *priv,
                uint8_t phyreg);
  static void lpc54_phy_write(struct lpc54_ethdriver_s *priv,
                uint8_t phyreg, uint16_t phyval);
  static inline bool lpc54_phy_linkstatus(struct lpc54_ethdriver_s *priv);
  static int  lpc54_phy_autonegotiate(struct lpc54_ethdriver_s *priv);
  static int  lpc54_phy_reset(struct lpc54_ethdriver_s *priv);
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: lpc54_getreg
   *
   * Description:
   *   This function may to used to intercept an monitor all register accesses.
   *   Clearly this is nothing you would want to do unless you are debugging
   *   this driver.
   *
   * Input Parameters:
   *   addr - The register address to read
   *
   * Returned Value:
   *   The value read from the register
   *
   ****************************************************************************/
  
  #ifdef CONFIG_LPC54_ETH_REGDEBUG
  static uint32_t lpc54_getreg(uintptr_t addr)
  {
    static uintptr_t prevaddr = 0;
    static uint32_t preval    = 0;
    static uint32_t count     = 0;
  
    /* Read the value from the register */
  
    uint32_t val = getreg32(addr);
  
    /* Is this the same value that we read from the same register last time?
     * Are we polling the register?  If so, suppress some of the output.
     */
  
    if (addr == prevaddr && val == preval)
      {
        if (count == 0xffffffff || ++count > 3)
          {
            if (count == 4)
              {
                ninfo("...\n");
              }
  
            return val;
          }
      }
  
    /* No this is a new address or value */
  
    else
      {
        /* Did we print "..." for the previous value? */
  
        if (count > 3)
          {
            /* Yes.. then show how many times the value repeated */
  
            ninfo("[repeats %d more times]\n", count - 3);
          }
  
        /* Save the new address, value, and count */
  
        prevaddr = addr;
        preval   = val;
        count    = 1;
      }
  
    /* Show the register value read */
  
    ninfo("%08x->%08x\n", addr, val);
    return val;
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_putreg
   *
   * Description:
   *   This function may to used to intercept an monitor all register accesses.
   *   Clearly this is nothing you would want to do unless you are debugging
   *   this driver.
   *
   * Input Parameters:
   *   val - The value to write to the register
   *   addr - The register address to read
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  #ifdef CONFIG_LPC54_ETH_REGDEBUG
  static void lpc54_putreg(uint32_t val, uintptr_t addr)
  {
    /* Show the register value being written */
  
    ninfo("%08x<-%08x\n", addr, val);
  
    /* Write the value */
  
    putreg32(val, addr);
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_eth_transmit
   *
   * Description:
   *   Start hardware transmission.  Called either from the txdone interrupt
   *   handling or from watchdog based polling.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *   chan - The channel to send the packet on
   *
   * Returned Value:
   *   OK on success; a negated errno on failure
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static int lpc54_eth_transmit(struct lpc54_ethdriver_s *priv,
                                unsigned int chan)
  {
    struct lpc54_txring_s *txring;
    struct enet_txdesc_s *txdesc;
    uint8_t *buffer;
    uint32_t regval;
    unsigned int buflen;
  
    /* Verify that the hardware is ready to send another packet.  If we get
     * here, then we are committed to sending a packet; Higher level logic
     * must have assured that we have the resources available to perform the
     * send.
     */
  
    txring = &priv->eth_txring[chan];
  
    DEBUGASSERT(priv->eth_dev.d_buf != 0 && priv->eth_dev.d_len > 0 &&
                priv->eth_dev.d_len <= LPC54_BUFFER_SIZE &&
                txring->tr_inuse < txring->tr_ndesc);
  
    /* Fill the descriptor. */
  
    txdesc = txring->tr_desc + txring->tr_supply;
    buffer = priv->eth_dev.d_buf;
    buflen = priv->eth_dev.d_len;
  
    priv->eth_dev.d_buf = NULL;
    priv->eth_dev.d_len = 0;
  
    if (buflen <= LPC54_BUFFER_MAX)
      {
        /* Prepare the Tx descriptor for transmission */
  
        txdesc->buffer1 = (uint32_t)buffer;
        txdesc->buffer2 = (uint32_t)NULL;
  
        /* One buffer, no timestamp, interrupt on completion */
  
        regval = ETH_TXDES2_B1L(buflen) | ETH_TXDES2_B2L(0) | ETH_TXDES2_IOC;
        txdesc->buflen = regval;
  
        /* Full packet length, last descriptor, first descriptor, owned by
         * DMA.
         */
  
        regval = ETH_TXDES3_FL(buflen) | ETH_TXDES3_LD | ETH_TXDES3_FD |
                 ETH_TXDES3_OWN;
        txdesc->ctrlstat = regval;
      }
  #if LPC54_BUFFER_SIZE > LPC54_BUFFER_MAX
    else
      {
        unsigned int buf2len = buflen - LPC54_BUFFER_MAX;
        uint8_t *buffer2     = buffer + LPC54_BUFFER_MAX;
  
        DEBUGASSERT(buf2len <= LPC54_BUFFER_MAX);
  
        /* Prepare the Tx descriptor for transmission */
  
        txdesc->buffer1 = (uint32_t)buffer;
        txdesc->buffer2 = (uint32_t)buffer2;
  
        /* Two buffers, no timestamp, interrupt on completion */
  
        regval = ETH_TXDES2_B1L(LPC54_BUFFER_MAX) |
                 ETH_TXDES2_B2L(buf2len) | ETH_TXDES2_IOC;
        txdesc->buflen = regval;
  
        /* Full packet length, last descriptor, first descriptor, owned by
         * DMA.
         */
  
        regval = ETH_TXDES3_FL(buflen) | ETH_TXDES3_LD | ETH_TXDES3_FD |
                 ETH_TXDES3_OWN;
        txdesc->ctrlstat = regval;
      }
  #endif
  
    /* Increase the index */
  
    if (++(txring->tr_supply) >= txring->tr_ndesc)
      {
        txring->tr_supply = 0;
      }
  
    /* Increment the number of descriptors in-use. */
  
    txring->tr_inuse++;
  
    /* Update the transmit tail address. */
  
    txdesc = txring->tr_desc + txring->tr_supply;
    if (txring->tr_supply == 0)
      {
        txdesc = txring->tr_desc + txring->tr_ndesc;
      }
  
    /* Update the DMA tail pointer */
  
    lpc54_putreg((uint32_t)txdesc, LPC54_ETH_DMACH_TXDESC_TAIL_PTR(chan));
  
    /* Setup the TX timeout watchdog (perhaps restarting the timer) */
  
    wd_start(&priv->eth_txtimeout, LPC54_TXTIMEOUT,
             lpc54_eth_txtimeout_expiry, (wdparm_t)priv);
    return OK;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_getring
   *
   * Description:
   *   An output message is ready to send, but which queue should we send it
   *   on?  The rule is this:
   *
   *   "Normal" packets (or CONFIG_LPC54_ETH_MULTIQUEUE not selected):
   *     Always send on ring 0
   *   8021QVLAN AVB packets (and CONFIG_LPC54_ETH_MULTIQUEUE not selected):
   *     Always send on ring 1
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   The ring to use when sending the packet.
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static unsigned int lpc54_eth_getring(struct lpc54_ethdriver_s *priv)
  {
    unsigned int ring = 0;
  
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    /* Choose the ring ID for different types of frames.  For 802.1q VLAN AVB
     * frames, uses ring 1.  Everything else goes on ring 0.
     */
  
    if (ETH8021QWBUF->tpid == HTONS(TPID_8021QVLAN) &&
        ETH8021QWBUF->type == HTONS(ETHTYPE_AVBTP))
      {
        ring = 1;
      }
  #endif
  
    return ring;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_txpoll
   *
   * Description:
   *   The transmitter is available, check if the network has any outgoing
   *   packets ready to send.  This is a callback from devif_poll().
   *   devif_poll() may be called:
   *
   *   1. When the preceding TX packet send is complete,
   *   2. When the preceding TX packet send timesout and the interface is reset
   *   3. During normal TX polling
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   OK on success; a negated errno on failure
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static int lpc54_eth_txpoll(struct net_driver_s *dev)
  {
    struct lpc54_ethdriver_s *priv;
    struct lpc54_txring_s *txring;
    struct lpc54_txring_s *txring0;
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    struct lpc54_txring_s *txring1;
  #endif
    unsigned int chan;
  
    DEBUGASSERT(dev->d_private != NULL && dev->d_buf != NULL);
    priv = (struct lpc54_ethdriver_s *)dev->d_private;
  
    /* If the polling resulted in data that should be sent out on the network,
     * the field d_len is set to a value > 0.
     */
  
    if (priv->eth_dev.d_len > 0)
      {
        /* Look up the destination MAC address and add it to the Ethernet
         * header.
         */
  
  #ifdef CONFIG_NET_IPv4
  #ifdef CONFIG_NET_IPv6
        if (IFF_IS_IPv4(priv->eth_dev.d_flags))
  #endif
          {
            arp_out(&priv->eth_dev);
          }
  #endif /* CONFIG_NET_IPv4 */
  
  #ifdef CONFIG_NET_IPv6
  #ifdef CONFIG_NET_IPv4
        else
  #endif
          {
            neighbor_out(&priv->eth_dev);
          }
  #endif /* CONFIG_NET_IPv6 */
  
        if (!devif_loopback(&priv->eth_dev))
          {
            /* Send the packet */
  
            chan   = lpc54_eth_getring(priv);
            txring = &priv->eth_txring[chan];
  
            (txring->tr_buffers)[txring->tr_supply] =
              (uint32_t *)priv->eth_dev.d_buf;
  
            lpc54_eth_transmit(priv, chan);
  
            txring0 = &priv->eth_txring[0];
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
            txring1 = &priv->eth_txring[1];
  
            /* We cannot perform the Tx poll now if all of the Tx descriptors
             * for both channels are in-use.
             */
  
            if (txring0->tr_inuse >= txring0->tr_ndesc ||
                txring1->tr_inuse >= txring1->tr_ndesc)
  #else
            /* We cannot continue the Tx poll now if all of the Tx descriptors
             * for this channel 0 are in-use.
             */
  
            if (txring0->tr_inuse >= txring0->tr_ndesc)
  #endif
              {
                /* Stop the poll.. no more Tx descriptors */
  
                return 1;
              }
  
            /* There is a free descriptor in the ring, allocate a new Tx buffer
             * to perform the poll.
             */
  
            priv->eth_dev.d_buf = (uint8_t *)lpc54_pktbuf_alloc(priv);
            if (priv->eth_dev.d_buf == NULL)
              {
                /* Stop the poll.. no more packet buffers */
  
                return 1;
              }
          }
      }
  
    /* If zero is returned, the polling will continue until all connections
     * have been examined.  If there is nothing to be sent, we will return to
     * the caller of devif_poll() with an allocated packet buffer.
     */
  
    return 0;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_reply
   *
   * Description:
   *   After a packet has been received and dispatched to the network, it
   *   may return return with an outgoing packet.  This function checks for
   *   that case and performs the transmission if necessary.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static void lpc54_eth_reply(struct lpc54_ethdriver_s *priv)
  {
    struct lpc54_txring_s *txring;
    unsigned int chan;
  
    /* If the packet dispatch resulted in data that should be sent out on the
     * network, the field d_len will set to a value > 0.
     */
  
    if (priv->eth_dev.d_len > 0)
      {
        /* Update the Ethernet header with the correct MAC address */
  
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
        /* Check for an outgoing 802.1q VLAN packet */
  #warning Missing Logic
  #endif
  
  #ifdef CONFIG_NET_IPv4
  #ifdef CONFIG_NET_IPv6
        /* Check for an outgoing IPv4 packet */
  
        if (IFF_IS_IPv4(priv->eth_dev.d_flags))
  #endif
          {
            arp_out(&priv->eth_dev);
          }
  #endif
  
  #ifdef CONFIG_NET_IPv6
  #ifdef CONFIG_NET_IPv4
        /* Otherwise, it must be an outgoing IPv6 packet */
  
        else
  #endif
          {
            neighbor_out(&priv->eth_dev);
          }
  #endif
  
        /* And send the packet */
  
        chan   = lpc54_eth_getring(priv);
        txring = &priv->eth_txring[chan];
  
        (txring->tr_buffers)[txring->tr_supply] =
          (uint32_t *)priv->eth_dev.d_buf;
  
        lpc54_eth_transmit(priv, chan);
      }
  }
  
  /****************************************************************************
   * Name: lpc54_eth_rxdispatch
   *
   * Description:
   *   A new packet has been received and will be forwarded to the network
   *   stack.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static void lpc54_eth_rxdispatch(struct lpc54_ethdriver_s *priv)
  {
  #ifdef CONFIG_NET_PKT
    /* When packet sockets are enabled, feed the frame into the tap */
  
    pkt_input(&priv->eth_dev);
  #endif
  
    /* We only accept IP packets of the configured type and ARP packets */
  
  #ifdef CONFIG_NET_IPv4
    if (ETHBUF->type == HTONS(ETHTYPE_IP))
      {
        ninfo("IPv4 packet\n");
        NETDEV_RXIPV4(&priv->eth_dev);
  
        /* Handle ARP on input,
         * then dispatch IPv4 packet to the network layer
         */
  
        arp_ipin(&priv->eth_dev);
        ipv4_input(&priv->eth_dev);
  
        /* Check for a reply to the IPv4 packet */
  
        lpc54_eth_reply(priv);
      }
    else
  #endif
  #ifdef CONFIG_NET_IPv6
    if (ETHBUF->type == HTONS(ETHTYPE_IP6))
      {
        ninfo("IPv6 packet\n");
        NETDEV_RXIPV6(&priv->eth_dev);
  
        /* Dispatch IPv6 packet to the network layer */
  
        ipv6_input(&priv->eth_dev);
  
        /* Check for a reply to the IPv6 packet */
  
        lpc54_eth_reply(priv);
      }
    else
  #endif
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    if (ETH8021QWBUF->tpid == HTONS(TPID_8021QVLAN))
      {
        ninfo("IEEE 802.1q packet\n");
        NETDEV_RXQVLAN(&priv->eth_dev);
  
        /* Dispatch the 802.1q VLAN packet to the network layer */
  
        qvlan_input(&priv->eth_dev);
  
        /* Check for a reply to the 802.1q VLAN packet */
  
        lpc54_eth_reply(priv);
      }
    else
  #endif
  #ifdef CONFIG_NET_ARP
    if (ETHBUF->type == htons(ETHTYPE_ARP))
      {
        struct lpc54_txring_s *txring;
        unsigned int chan;
  
        /* Dispatch the ARP packet to the network layer */
  
        arp_arpin(&priv->eth_dev);
        NETDEV_RXARP(&priv->eth_dev);
  
        /* If the above function invocation resulted in data that should be
         * sent out on the network, d_len field will set to a value > 0.
         */
  
        if (priv->eth_dev.d_len > 0)
          {
            chan   = lpc54_eth_getring(priv);
            txring = &priv->eth_txring[chan];
  
            (txring->tr_buffers)[txring->tr_supply] =
              (uint32_t *)priv->eth_dev.d_buf;
  
            lpc54_eth_transmit(priv, chan);
          }
      }
    else
  #endif
      {
        NETDEV_RXDROPPED(&priv->eth_dev);
      }
  
    /* On entry, d_buf refers to the receive buffer as set by logic in
     * lpc54_eth_receive().  If lpc54_eth_transmit() was called to reply
     * with an outgoing packet, then that packet was removed for transmission
     * and d_buf was nullified.  Otherwise, d_buf still holds the stale
     * receive buffer and we will need to dispose of it here.
     */
  
    if (priv->eth_dev.d_buf != NULL)
      {
        lpc54_pktbuf_free(priv, (uint32_t *)priv->eth_dev.d_buf);
      }
  
    priv->eth_dev.d_buf = NULL;
    priv->eth_dev.d_len = 0;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_receive
   *
   * Description:
   *   An interrupt was received indicating the availability of a new RX packet
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *   chan - The channel with the completed Rx transfer
   *
   * Returned Value:
   *   The number of Rx descriptors processed
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static int lpc54_eth_receive(struct lpc54_ethdriver_s *priv,
                                unsigned int chan)
  {
    struct lpc54_rxring_s *rxring;
    struct enet_rxdesc_s *rxdesc;
    unsigned int framelen;
    unsigned int pktlen;
    unsigned int supply;
    uint32_t regval;
    bool suspend;
    int ndesc;
  
    /* Get the Rx ring associated with this channel */
  
    rxring  = &priv->eth_rxring[chan];
  
    /* If no Rx descriptor is available, then suspend for now */
  
    regval  = lpc54_getreg(LPC54_ETH_DMACH_STAT(chan));
    suspend = ((regval & ETH_DMACH_INT_RBU) != 0);
  
    /* Loop until the next full frame is encountered or until we encounter a
     * descriptor still owned by the DMA.
     */
  
    pktlen = 0;
    ndesc  = 0;
  
    for (; ; )
      {
        /* Get the last Rx descriptor in the ring */
  
        supply = rxring->rr_supply;
        rxdesc = rxring->rr_desc + supply;
  
        /* Is this frame still owned by the DMA? */
  
        if ((rxdesc->ctrl & ETH_RXDES3_OWN) != 0)
          {
            /* Yes.. then bail */
  
            return ndesc;
          }
  
        ndesc++;
  
        /* Set the supplier index to the next descriptor */
  
        if (++(rxring->rr_supply) > rxring->rr_ndesc)
          {
            rxring->rr_supply = 0;
          }
  
        /* Is this the last descriptor of the frame? */
  
        if ((rxdesc->ctrl & ETH_RXDES3_LD) != 0)
          {
            /* Have we been discarding Rx data?  If so, that was the last
             * packet to be discarded.
             */
  
            if (priv->eth_rxdiscard)
              {
                priv->eth_rxdiscard = 0;
              }
            else
              {
                /* Last frame encountered.  This is a valid packet */
  
                framelen  = (rxdesc->ctrl & ETH_RXDES3_PL_MASK);
                pktlen   += framelen;
  
                if (pktlen > 0)
                  {
                    /* Recover the buffer.
                     *
                     * REVISIT:  According to the User manual, buffer1 and
                     * buffer2 addresses were overwritten by the write-back
                     * data.  However, I see that the buffer1 and buffer2
                     * addresses are, indeed, used by the NXP example code
                     * so the user manual must be wrong.  We could eliminate
                     * this extra array of saved allocation addresses.
                     */
  
                    priv->eth_dev.d_buf = (uint8_t *)
                      (rxring->rr_buffers)[supply];
                    (rxring->rr_buffers)[supply] = NULL;
                    DEBUGASSERT(priv->eth_dev.d_buf != NULL);
  
                    priv->eth_dev.d_len = pktlen;
  
                    /* REVISIT: What should we do if there is no Tx buffer
                     * available.  In receiving the packet, we could also
                     * generate a new outgoing packet that could only be
                     * handled if there is an available Tx descriptor.
                     */
  
                    lpc54_eth_rxdispatch(priv);
  
                    /* Allocate a new Rx buffer and update the Rx buffer
                     * descriptor.
                     */
  
                    rxdesc->buffer1 = (uint32_t)lpc54_pktbuf_alloc(priv);
                    DEBUGASSERT(rxdesc->buffer1 != 0);
                    (rxring->rr_buffers)[supply] = (uint32_t *)rxdesc->buffer1;
  
  #if LPC54_BUFFER_SIZE > LPC54_BUFFER_MAX
                    rxdesc->buffer2 = rxdesc->buffer1 + LPC54_BUFFER_MAX;
  #else
                    /* The second buffer is not used */
  
                    rxdesc->buffer2  = 0;
  #endif
                    rxdesc->reserved = 0;
  
                    /* Buffer1 (and maybe 2) valid, interrupt on completion,
                     * owned by DMA.
                     */
  
                    regval  = ETH_RXDES3_BUF1V | ETH_RXDES3_IOC |
                              ETH_RXDES3_OWN;
  #if LPC54_BUFFER_SIZE > LPC54_BUFFER_MAX
                    regval |= ETH_RXDES3_BUF2V;
  #endif
                    rxdesc->ctrl = regval;
                  }
  
                return ndesc;
              }
          }
        else if (!priv->eth_rxdiscard)
          {
            /* Not the last Rx descriptor of the packet.
             *
             * We are attempting to receive a large packet spanning multiple
             * Rx descriptors.  We cannot support that in this design.  We
             * would like to:
             *
             *   1. Accumulate the data in yet another Rx buffer,
             *   2. Accumulate the size in the 'pktlen' local variable, then
             *   3. Dispatch that extra Rx buffer when the last frame is
             *      encountered.
             *
             * The assumption here is that this will never happen if our MTU
             * properly advertised.
             */
  
            NETDEV_RXDROPPED(&priv->eth_dev);
            priv->eth_rxdiscard = 1;
          }
      }
  
    /* Restart the receiver and clear the RBU status if it was suspended. */
  
    if (suspend)
      {
        uintptr_t regaddr = LPC54_ETH_DMACH_RXDESC_TAIL_PTR(chan);
  
        /* Clear the RBU status */
  
        lpc54_putreg(ETH_DMACH_INT_RBU, LPC54_ETH_DMACH_STAT(chan));
  
        /* Writing to the tail pointer register
         * will restart the Rx processing
         */
  
        regval = lpc54_getreg(regaddr);
        lpc54_putreg(regval, regaddr);
      }
  
    return ndesc;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_txdone
   *
   * Description:
   *   An interrupt was received indicating that the last TX packet(s) is done
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *   chan - The channel with the completed Tx transfer
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static void lpc54_eth_txdone(struct lpc54_ethdriver_s *priv,
                               unsigned int chan)
  {
    struct lpc54_txring_s *txring;
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    struct lpc54_txring_s *txring0;
    struct lpc54_txring_s *txring1;
  #endif
    struct enet_txdesc_s *txdesc;
    uint32_t *pktbuf;
  
    /* Reclaim the compled Tx descriptor */
  
    txring = &priv->eth_txring[chan];
    txdesc = txring->tr_desc + txring->tr_consume;
  
    /* Update the first index for transmit buffer free. */
  
    while (txring->tr_inuse > 0 && (txdesc->ctrlstat & ETH_TXDES3_OWN) == 0)
      {
        /* Update statistics */
  
        NETDEV_TXDONE(priv->eth_dev);
  
        /* Free the Tx buffer assigned to the descriptor */
  
        pktbuf = txring->tr_buffers[txring->tr_consume];
        DEBUGASSERT(pktbuf != NULL);
        if (pktbuf != NULL)
          {
            lpc54_pktbuf_free(priv, pktbuf);
            txring->tr_buffers[txring->tr_consume] = NULL;
          }
  
        /* One less Tx descriptor in use */
  
        txring->tr_inuse--;
  
        /* Update the consume index and the descriptor pointer. */
  
        if (++(txring->tr_consume) >= txring->tr_ndesc)
          {
            txring->tr_consume = 0;
          }
  
        txdesc = txring->tr_desc + txring->tr_consume;
      }
  
    /* If no further transmissions are pending, then cancel the TX timeout. */
  
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    txring0 = &priv->eth_txring[0];
    txring1 = &priv->eth_txring[1];
  
    if (txring0->tr_inuse == 0 && txring1->tr_inuse == 0)
  
  #else
    if (txring->tr_inuse == 0)
  #endif
      {
        wd_cancel(&priv->eth_txtimeout);
        work_cancel(ETHWORK, &priv->eth_timeoutwork);
      }
  
    /* Poll the network for new TX data. */
  
    lpc54_eth_dopoll(priv);
  }
  
  /****************************************************************************
   * Name: lpc54_eth_channel_work
   *
   * Description:
   *   Perform interrupt related work for a channel DMA interrupt
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *   chan - The channel that received the interrupt event.
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static void lpc54_eth_channel_work(struct lpc54_ethdriver_s *priv,
                                     unsigned int chan)
  {
    uintptr_t regaddr;
    uint32_t status;
    uint32_t pending;
  
    /* Read the DMA status register for this channel */
  
    regaddr = LPC54_ETH_DMACH_STAT(chan);
    status  = lpc54_getreg(regaddr);
    pending = status & lpc54_getreg(LPC54_ETH_DMACH_INT_EN(chan));
  
    /* Check for abnormal interrupts */
  
    if ((pending & LPC54_ABNORM_INTMASK) != 0)
      {
        /* Acknowledge the abnormal interrupt interrupts except for RBU...
         * that is a special case where the status will be cleared in
         * lpc54_eth_receive().  See comments below.
         */
  
        lpc54_putreg((LPC54_ABNORM_INTMASK & ~ETH_DMACH_INT_RBU), regaddr);
  
        /* Handle the incoming packet */
  
        nerr("ERROR: Abnormal interrupt received: %08lx (%08lx)\n",
             (unsigned long)pending, (unsigned long)status);
  
        /* Check for Tx/Rx related errors and update statistics */
  
        if ((pending & LPC54_RXERR_INTMASK) != 0)
          {
            NETDEV_RXERRORS(priv->eth_dev);
          }
  
        if ((pending & LPC54_TXERR_INTMASK) != 0)
          {
            NETDEV_TXERRORS(priv->eth_dev);
          }
  
        /* The Receive Buffer Unavailable (RBU) error is a special case.  It
         * means that we have an Rx overrun condition:  All of the Rx buffers
         * have been filled with packet data and there are no Rx descriptors
         * available to receive the next packet.
         *
         * Often RBU is accompanied by RI but we need to force that condition
         * in all cases.  In the case of RBU, we need to perform receive
         * processing in order to recover from the situation and to resume.
         *
         * This is really a configuration problem:  It really means that we
         * have not assigned enough Rx buffers for the environment and
         * addressing filtering options that we have selected.
         */
  
        if ((pending & ETH_DMACH_INT_RBU) != 0)
          {
            pending |= ETH_DMACH_INT_RI;
          }
  
        pending &= ~LPC54_ABNORM_INTMASK;
      }
  
    /* Check for a receive interrupt */
  
    if ((pending & ETH_DMACH_INT_RI) != 0)
      {
        int ndesc;
  
        /* Acknowledge the normal receive interrupt */
  
        lpc54_putreg(ETH_DMACH_INT_RI | ETH_DMACH_INT_NI, regaddr);
        pending &= ~(ETH_DMACH_INT_RI | ETH_DMACH_INT_NI);
  
        /* Loop until all available Rx packets
         * in the ring have been processed
         */
  
        for (; ; )
          {
            /* Dispatch the next packet from the Rx ring */
  
            ndesc = lpc54_eth_receive(priv, chan);
            if (ndesc > 0)
              {
                /* Update statistics if a packet was dispatched */
  
                NETDEV_RXPACKETS(priv->eth_dev);
              }
            else
              {
                break;
              }
          }
      }
  
    /* Check for a transmit interrupt */
  
    if ((pending & ETH_DMACH_INT_TI) != 0)
      {
        /* Acknowledge the normal receive interrupt */
  
        lpc54_putreg(ETH_DMACH_INT_TI | ETH_DMACH_INT_NI, regaddr);
        pending &= ~(ETH_DMACH_INT_TI | ETH_DMACH_INT_NI);
  
        /* Handle the Tx completion event.  Reclaim the completed Tx
         * descriptors, free packet buffers, and check if we can start a new
         * transmission.
         */
  
        lpc54_eth_txdone(priv, chan);
      }
  
    /* Check for unhandled interrupts (shouldn't be any) */
  
    if (pending != 0)
      {
        nwarn("WARNING: Unhandled interrupts: %08lx (%08lx)\n",
             (unsigned long)pending, (unsigned long)status);
        lpc54_putreg(pending, regaddr);
      }
  }
  
  /****************************************************************************
   * Name: lpc54_eth_interrupt_work
   *
   * Description:
   *   Perform interrupt related work from the worker thread
   *
   * Input Parameters:
   *   arg - The argument passed when work_queue() was called.
   *
   * Returned Value:
   *   OK on success
   *
   * Assumptions:
   *   Runs on a worker thread.
   *
   ****************************************************************************/
  
  static void lpc54_eth_interrupt_work(void *arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
    uint32_t intrstat;
  
    /* Lock the network to serialize driver operations. */
  
    net_lock();
  
    /* Check if interrupt is from DMA channel 0. */
  
    intrstat = lpc54_getreg(LPC54_ETH_DMA_INTR_STAT);
    if ((intrstat & ETH_DMA_INTR_STAT_DC0IS) != 0)
      {
        lpc54_eth_channel_work(priv, 0);
      }
  
    /* Check if interrupt is from DMA channel 1. */
  
    intrstat = lpc54_getreg(LPC54_ETH_DMA_INTR_STAT);
    if ((intrstat & ETH_DMA_INTR_STAT_DC1IS) != 0)
      {
        lpc54_eth_channel_work(priv, 1);
      }
  
    /* Un-lock the network and re-enable Ethernet interrupts */
  
    net_unlock();
    up_enable_irq(LPC54_IRQ_ETHERNET);
  }
  
  /****************************************************************************
   * Name: lpc54_eth_interrupt
   *
   * Description:
   *   Ethernet interrupt handler
   *
   * Input Parameters:
   *   irq     - Number of the IRQ that generated the interrupt
   *   context - Interrupt register state save info (architecture-specific)
   *
   * Returned Value:
   *   Runs in the context of a the Ethernet interrupt handler.  Local
   *   interrupts are disabled by the interrupt logic.
   *
   ****************************************************************************/
  
  static int lpc54_eth_interrupt(int irq, void *context, void *arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
  
    DEBUGASSERT(priv != NULL);
  
    /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
     * also disabled if the TX timeout event occurs, there can be no race
     * condition here.
     */
  
    up_disable_irq(LPC54_IRQ_ETHERNET);
  
    /* Note:  We have a race condition which, I believe is handled OK.  If
     * there is a Tx timeout in place, then that timeout could expire
     * anytime and queue additional work to handle the timeout.
     */
  
    /* Schedule to perform the interrupt processing on the worker thread. */
  
    work_queue(ETHWORK, &priv->eth_irqwork, lpc54_eth_interrupt_work, priv, 0);
    return OK;
  }
  
  /****************************************************************************
   * Name: lpc54_pmt_interrupt
   *
   * Description:
   *   Ethernet power management interrupt handler
   *
   * Input Parameters:
   *   irq     - Number of the IRQ that generated the interrupt
   *   context - Interrupt register state save info (architecture-specific)
   *
   * Returned Value:
   *   Runs in the context of a the Ethernet PMT interrupt handler.  Local
   *   interrupts are disabled by the interrupt logic.
   *
   ****************************************************************************/
  
  #if 0 /* Not used */
  static int  lpc54_pmt_interrupt(int irq, void *context, void *arg)
  {
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_mac_interrupt
   *
   * Description:
   *   Ethernet MAC interrupt handler
   *
   * Input Parameters:
   *   irq     - Number of the IRQ that generated the interrupt
   *   context - Interrupt register state save info (architecture-specific)
   *
   * Returned Value:
   *   Runs in the context of a the Ethernet MAC handler.  Local
   *   interrupts are disabled by the interrupt logic.
   *
   ****************************************************************************/
  
  #if 0 /* Not used */
  static int lpc54_mac_interrupt(int irq, void *context, void *arg)
  {
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_eth_txtimeout_work
   *
   * Description:
   *   Perform TX timeout related work from the worker thread
   *
   * Input Parameters:
   *   arg - The argument passed when work_queue() as called.
   *
   * Returned Value:
   *   OK on success
   *
   * Assumptions:
   *   Runs on a worker thread.
   *
   ****************************************************************************/
  
  static void lpc54_eth_txtimeout_work(void *arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
  
    /* Lock the network and serialize driver operations if necessary.
     * NOTE: Serialization is only required in the case where the driver work
     * is performed on an LP worker thread and where more than one LP worker
     * thread has been configured.
     */
  
    net_lock();
  
    /* Increment statistics and dump debug info */
  
    NETDEV_TXTIMEOUTS(priv->eth_dev);
  
    /* Then reset the hardware by bringing it down and taking it back up
     * again.
     */
  
    lpc54_eth_ifdown(&priv->eth_dev);
    lpc54_eth_ifup(&priv->eth_dev);
  
    /* Then poll the network for new XMIT data */
  
    lpc54_eth_dopoll(priv);
    net_unlock();
  }
  
  /****************************************************************************
   * Name: lpc54_eth_txtimeout_expiry
   *
   * Description:
   *   Our TX watchdog timed out.  Called from the timer interrupt handler.
   *   The last TX never completed.  Reset the hardware and start again.
   *
   * Input Parameters:
   *   arg  - The argument
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Runs in the context of a the timer interrupt handler.  Local
   *   interrupts are disabled by the interrupt logic.
   *
   ****************************************************************************/
  
  static void lpc54_eth_txtimeout_expiry(wdparm_t arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
  
    /* Disable further Ethernet interrupts.  This will prevent some race
     * conditions with interrupt work.  There is still a potential race
     * condition with interrupt work that is already queued and in progress.
     */
  
    up_disable_irq(LPC54_IRQ_ETHERNET);
  
    /* Schedule to perform the TX timeout processing on the worker thread. */
  
    work_queue(ETHWORK, &priv->eth_timeoutwork, lpc54_eth_txtimeout_work,
               priv, 0);
  }
  
  /****************************************************************************
   * Name: lpc54_eth_dotimer
   *
   * Description:
   *   Check if there are Tx descriptors available and, if so, allocate a Tx
   *   then perform the normal Tx poll
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static void lpc54_eth_dotimer(struct lpc54_ethdriver_s *priv)
  {
    struct lpc54_txring_s *txring0;
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    struct lpc54_txring_s *txring1;
  #endif
  
    DEBUGASSERT(priv->eth_dev.d_buf == NULL);
  
    txring0 = &priv->eth_txring[0];
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    txring1 = &priv->eth_txring[1];
  
    /* We cannot perform the Tx poll now if all of the Tx descriptors for both
     * channels are in-use.
     */
  
    if (txring0->tr_inuse < txring0->tr_ndesc &&
        txring1->tr_inuse < txring1->tr_ndesc)
  #else
    /* We cannot perform the Tx poll now if all of the Tx descriptors for this
     * channel 0 are in-use.
     */
  
    if (txring0->tr_inuse < txring0->tr_ndesc)
  #endif
      {
        /* There is a free descriptor in the ring, allocate a new Tx buffer
         * to perform the poll.
         */
  
        priv->eth_dev.d_buf = (uint8_t *)lpc54_pktbuf_alloc(priv);
        if (priv->eth_dev.d_buf != NULL)
          {
            devif_timer(&priv->eth_dev, LPC54_WDDELAY, lpc54_eth_txpoll);
  
            /* Make sure that the Tx buffer remaining after the poll is
             * freed.
             */
  
            if (priv->eth_dev.d_buf != NULL)
              {
                lpc54_pktbuf_free(priv, (uint32_t *)priv->eth_dev.d_buf);
                priv->eth_dev.d_buf = NULL;
              }
          }
      }
  }
  
  /****************************************************************************
   * Name: lpc54_eth_dopoll
   *
   * Description:
   *   Check if there are Tx descriptors available and, if so, allocate a Tx
   *   then perform the normal Tx poll
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static void lpc54_eth_dopoll(struct lpc54_ethdriver_s *priv)
  {
    struct lpc54_txring_s *txring0;
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    struct lpc54_txring_s *txring1;
  #endif
  
    DEBUGASSERT(priv->eth_dev.d_buf == NULL);
  
    txring0 = &priv->eth_txring[0];
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    txring1 = &priv->eth_txring[1];
  
    /* We cannot perform the Tx poll now if all of the Tx descriptors for both
     * channels are in-use.
     */
  
    if (txring0->tr_inuse < txring0->tr_ndesc &&
        txring1->tr_inuse < txring1->tr_ndesc)
  #else
    /* We cannot perform the Tx poll now if all of the Tx descriptors for this
     * channel 0 are in-use.
     */
  
    if (txring0->tr_inuse < txring0->tr_ndesc)
  #endif
      {
        /* There is a free descriptor in the ring, allocate a new Tx buffer
         * to perform the poll.
         */
  
        priv->eth_dev.d_buf = (uint8_t *)lpc54_pktbuf_alloc(priv);
        if (priv->eth_dev.d_buf != NULL)
          {
            devif_poll(&priv->eth_dev, lpc54_eth_txpoll);
  
            /* Make sure that the Tx buffer remaining after the poll is
             * freed.
             */
  
            if (priv->eth_dev.d_buf != NULL)
              {
                lpc54_pktbuf_free(priv, (uint32_t *)priv->eth_dev.d_buf);
                priv->eth_dev.d_buf = NULL;
              }
          }
      }
  }
  
  /****************************************************************************
   * Name: lpc54_eth_poll_work
   *
   * Description:
   *   Perform periodic polling from the worker thread
   *
   * Input Parameters:
   *   arg - The argument passed when work_queue() as called.
   *
   * Returned Value:
   *   OK on success
   *
   * Assumptions:
   *   Run on a work queue thread.
   *
   ****************************************************************************/
  
  static void lpc54_eth_poll_work(void *arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
  
    /* Lock the network and serialize driver operations if necessary.
     * NOTE: Serialization is only required in the case where the driver work
     * is performed on an LP worker thread and where more than one LP worker
     * thread has been configured.
     */
  
    net_lock();
  
    /* Perform the timer poll */
  
    lpc54_eth_dotimer(priv);
  
    /* Setup the watchdog poll timer again */
  
    wd_start(&priv->eth_txpoll, LPC54_WDDELAY,
             lpc54_eth_poll_expiry, (wdparm_t)priv);
    net_unlock();
  }
  
  /****************************************************************************
   * Name: lpc54_eth_poll_expiry
   *
   * Description:
   *   Periodic timer handler.  Called from the timer interrupt handler.
   *
   * Input Parameters:
   *   arg  - The argument
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Runs in the context of a the timer interrupt handler.  Local
   *   interrupts are disabled by the interrupt logic.
   *
   ****************************************************************************/
  
  static void lpc54_eth_poll_expiry(wdparm_t arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
  
    /* Schedule to perform the interrupt processing on the worker thread. */
  
    work_queue(ETHWORK, &priv->eth_pollwork, lpc54_eth_poll_work, priv, 0);
  }
  
  /****************************************************************************
   * Name: lpc54_eth_ifup
   *
   * Description:
   *   NuttX Callback: Bring up the Ethernet interface when an IP address is
   *   provided
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static int lpc54_eth_ifup(struct net_driver_s *dev)
  {
    struct lpc54_ethdriver_s *priv =
      (struct lpc54_ethdriver_s *)dev->d_private;
    uint8_t *mptr;
    uintptr_t base;
    uint32_t regval;
    int ret;
    int i;
  
  #ifdef CONFIG_NET_IPv4
    ninfo("Bringing up: %d.%d.%d.%d\n",
          (int)(dev->d_ipaddr & 0xff),
          (int)((dev->d_ipaddr >> 8) & 0xff),
          (int)((dev->d_ipaddr >> 16) & 0xff),
          (int)(dev->d_ipaddr >> 24));
  #endif
  #ifdef CONFIG_NET_IPv6
    ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
          dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
          dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
          dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
  #endif
  
    /* Initialize the PHY *****************************************************/
  
    ret = lpc54_phy_autonegotiate(priv);
    if (ret < 0)
      {
        nerr("ERROR: lpc54_phy_autonegotiate failed: %d\n", ret);
        return ret;
      }
  
    /* Initialize Ethernet DMA ************************************************/
  
    /* Reset DMA.  Resets the logic and all internal registers of the OMA, MTL,
     * and MAC.  This bit is automatically cleared after the reset operation
     * is complete in all Ethernet Block clock domains.
     */
  
    regval  = lpc54_getreg(LPC54_ETH_DMA_MODE);
    regval |= ETH_DMA_MODE_SWR;
    lpc54_putreg(regval, LPC54_ETH_DMA_MODE);
  
    /* Wait for the reset bit to be cleared at the completion of the reset */
  
    while ((lpc54_getreg(LPC54_ETH_DMA_MODE) & ETH_DMA_MODE_SWR) != 0)
      {
      }
  
    /* Set the burst length for each DMA descriptor ring */
  
    for (i = 0; i < LPC54_NRINGS; i++)
      {
        base = LPC54_ETH_DMACH_BASE(i);
        lpc54_putreg(LPC54_PBLX8, base + LPC54_ETH_DMACH_CTRL_OFFSET);
  
        regval  = lpc54_getreg(base + LPC54_ETH_DMACH_TX_CTRL_OFFSET);
        regval &= ~ETH_DMACH_TX_CTRL_TXPBL_MASK;
        regval |= ETH_DMACH_TX_CTRL_TXPBL(LPC54_BURSTLEN);
        lpc54_putreg(regval, base + LPC54_ETH_DMACH_TX_CTRL_OFFSET);
  
        regval  = lpc54_getreg(base + LPC54_ETH_DMACH_RX_CTRL_OFFSET);
        regval &= ~ETH_DMACH_RX_CTRL_RXPBL_MASK;
        regval |= ETH_DMACH_RX_CTRL_RXPBL(LPC54_BURSTLEN);
        lpc54_putreg(regval, base + LPC54_ETH_DMACH_RX_CTRL_OFFSET);
      }
  
    /* Initializes the Ethernet MTL *******************************************/
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    /* Set the schedule/arbitration for multiple queues */
  
    lpc54_putreg(LPC54_MTL_OPMODE_SCHALG | LPC54_MTL_OPMODE_RAA,
                 LPC54_ETH_MTL_OP_MODE);
  
    /* Set the Rx queue mapping to DMA channel. */
  
    lpc54_putreg(LPC54_QUEUEMAP, LPC54_ETH_MTL_RXQ_DMA_MAP);
  #endif
  
    /* Set transmit queue operation mode
     *
     * FTQ   - Set to flush the queue
     * TSF   - Depends on configuration
     * TXQEN - Queue 0 enabled; queue 1 may be disabled
     * TTC   - Set to 32 bytes (ignored if TSF set)
     * TQS   - Set to 2048 bytes
     */
  
  #ifdef CONFIG_LPC54_ETH_TX_STRFWD
    regval = ETH_MTL_TXQ_OP_MODE_TSF;
  #else
    regval = 0;
  #endif
  
    regval |= ETH_MTL_TXQ_OP_MODE_FTQ | ETH_MTL_TXQ_OP_MODE_TTC_32 |
              ETH_MTL_TXQ_OP_MODE_TQS(LPC54_MTL_TXQUEUE_UNITS);
    lpc54_putreg(regval | ETH_MTL_TXQ_OP_MODE_TXQEN_ENABLE,
                 LPC54_ETH_MTL_TXQ_OP_MODE(0));
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    lpc54_putreg(regval | ETH_MTL_TXQ_OP_MODE_TXQEN_ENABLE,
                 LPC54_ETH_MTL_TXQ_OP_MODE(1));
  #else
    lpc54_putreg(regval | ETH_MTL_TXQ_OP_MODE_TXQEN_DISABLE,
                 LPC54_ETH_MTL_TXQ_OP_MODE(1));
  #endif
  
    /* Set receive receive operation mode
     *
     * RTC        - Set to 64 bytes (ignored if RSF selected)
     * FUP        - enabled
     * FEP        - disabled
     * RSF        - Depends on configuration
     * DIS_TCP_EF - Not disabled
     * RQS        - Set to 2048 bytes
     */
  
  #ifdef CONFIG_LPC54_ETH_RX_STRFWD
    regval = ETH_MTL_RXQ_OP_MODE_RSF;
  #else
    regval = 0;
  #endif
  
    regval |= ETH_MTL_RXQ_OP_MODE_RTC_64 | ETH_MTL_RXQ_OP_MODE_FUP |
              ETH_MTL_RXQ_OP_MODE_RQS(LPC54_MTL_RXQUEUE_UNITS);
    lpc54_putreg(regval, LPC54_ETH_MTL_RXQ_OP_MODE(0));
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    lpc54_putreg(regval, LPC54_ETH_MTL_RXQ_OP_MODE(1));
  
    /* Set the Tx/Rx queue weights. */
  
    lpc54_putreg(CONFIG_LPC54_ETH_TXQ0WEIGHT, LPC54_ETH_MTL_TXQ_QNTM_WGHT(0));
    lpc54_putreg(CONFIG_LPC54_ETH_TXQ1WEIGHT, LPC54_ETH_MTL_TXQ_QNTM_WGHT(1));
  
    lpc54_putreg(CONFIG_LPC54_ETH_RXQ0WEIGHT, LPC54_ETH_MTL_RXQ_CTRL(0));
    lpc54_putreg(CONFIG_LPC54_ETH_RXQ1WEIGHT, LPC54_ETH_MTL_RXQ_CTRL(1));
  #endif
  
    /* Initialize the Ethernet MAC ********************************************/
  
    /* Instantiate the MAC address that application logic should have set in
     * the device structure.
     *
     * "Note that the first DA byte that is received on the MII interface
     *  corresponds to the LS Byte (bits 7:0) of the MAC address low register.
     *  For example, if 0x1122 3344 5566 is received (0x11 is the first byte)
     *  on the MII as the destination address, then the MAC address
     *  register[47:0] is compared with 0x6655 4433 2211."
     */
  
    mptr   = (uint8_t *)priv->eth_dev.d_mac.ether.ether_addr_octet;
    regval = ((uint32_t)mptr[3] << 24) | ((uint32_t)mptr[2] << 16) |
             ((uint32_t)mptr[1] << 8)  | ((uint32_t)mptr[0]);
    lpc54_putreg(regval, LPC54_ETH_MAC_ADDR_LOW);
  
    regval = ((uint32_t)mptr[5] << 8)  | ((uint32_t)mptr[4]);
    lpc54_putreg(regval, LPC54_ETH_MAC_ADDR_HIGH);
  
    /* Set the receive address filter */
  
    regval  = ETH_MAC_FRAME_FILTER_PCF_NONE;
  #ifdef CONFIG_LPC54_ETH_RX_PROMISCUOUS
    regval |= ETH_MAC_FRAME_FILTER_PR;
  #endif
  #ifndef CONFIG_LPC54_ETH_RX_BROADCAST
    regval |= ETH_MAC_FRAME_FILTER_DBF;
  #endif
  #ifdef LPC54_ACCEPT_ALLMULTICAST
    regval |= ETH_MAC_FRAME_FILTER_PM;
  #endif
    lpc54_putreg(regval, LPC54_ETH_MAC_FRAME_FILTER);
  
  #ifdef CONFIG_LPC54_ETH_FLOWCONTROL
    /* Configure flow control */
  
    regval = ETH_MAC_RX_FLOW_CTRL_RFE | ETH_MAC_RX_FLOW_CTRL_UP;
    lpc54_putreg(regval, LPC54_ETH_MAC_RX_FLOW_CTRL);
  
    regval = ETH_MAC_TX_FLOW_CTRL_Q_PT(CONFIG_LPC54_ETH_TX_PAUSETIME);
    lpc54_putreg(regval, LPC54_ETH_MAC_TX_FLOW_CTRL_Q0);
    lpc54_putreg(regval, LPC54_ETH_MAC_TX_FLOW_CTRL_Q1);
  #endif
  
    /* Set the 1uS tick counter */
  
    regval = ETH_MAC_1US_TIC_COUNTR(BOARD_MAIN_CLK / USEC_PER_SEC);
    lpc54_putreg(regval, LPC54_ETH_MAC_1US_TIC_COUNTR);
  
    /* Set the speed and duplex using the values previously determined through
     * autonegotiaion.
     */
  
    regval  = ETH_MAC_CONFIG_ECRSFD | ETH_MAC_CONFIG_PS;
  
  #ifdef CONFIG_LPC54_ETH_8023AS2K
    regval |= ENET_MAC_CONFIG_S2KP;
  #endif
  
    if (priv->eth_fullduplex)
      {
        regval |= ETH_MAC_CONFIG_DM;
      }
    else
      {
        regval |= LPC54_MAC_HALFDUPLEX_IPG;
      }
  
    if (priv->eth_100mbps)
      {
        regval |= ETH_MAC_CONFIG_FES;
      }
  
    lpc54_putreg(regval, LPC54_ETH_MAC_CONFIG);
  
    /* REVISIT:  The User Manual says we need to set the SYSCON sideband flow
     * control for each channel.  But it is not clear to me what setting that
     * refers to nor do I see any such settings in the NXP sample code.
     */
  
    /* Enable Rx queues  */
  
    regval = ETH_MAC_RXQ_CTRL0_RXQ0EN_ENABLE |
             ETH_MAC_RXQ_CTRL0_RXQ1EN_ENABLE;
    lpc54_putreg(regval, LPC54_ETH_MAC_RXQ_CTRL0);
  
    /* Setup up Ethernet interrupts */
  
    regval = LPC54_NORM_INTMASK | LPC54_ABNORM_INTMASK;
    lpc54_putreg(regval, LPC54_ETH_DMACH_INT_EN(0));
    lpc54_putreg(regval, LPC54_ETH_DMACH_INT_EN(1));
  
    lpc54_putreg(0, LPC54_ETH_MAC_INTR_EN);
  
    /* Initialize packet buffers */
  
    lpc54_pktbuf_initialize(priv);
  
    /* Initialize descriptors */
  
    lpc54_ring_initialize(priv);
  
    /* Activate DMA on channel 0 */
  
    regval  = lpc54_getreg(LPC54_ETH_DMACH_RX_CTRL(0));
    regval |= ETH_DMACH_RX_CTRL_SR;
    lpc54_putreg(regval, LPC54_ETH_DMACH_RX_CTRL(0));
  
    regval  = lpc54_getreg(LPC54_ETH_DMACH_TX_CTRL(0));
    regval |= ETH_DMACH_TX_CTRL_ST;
    lpc54_putreg(regval, LPC54_ETH_DMACH_TX_CTRL(0));
  
    /* Then enable the Rx/Tx */
  
    regval  = lpc54_getreg(LPC54_ETH_MAC_CONFIG);
    regval |= ETH_MAC_CONFIG_RE;
    lpc54_putreg(regval, LPC54_ETH_MAC_CONFIG);
  
    regval |= ETH_MAC_CONFIG_TE;
    lpc54_putreg(regval, LPC54_ETH_MAC_CONFIG);
  
    /* Set and activate a timer process */
  
    wd_start(&priv->eth_txpoll, LPC54_WDDELAY,
             lpc54_eth_poll_expiry, (wdparm_t)priv);
  
    /* Enable the Ethernet interrupt */
  
    priv->eth_bifup = 1;
    up_enable_irq(LPC54_IRQ_ETHERNET);
    return OK;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_ifdown
   *
   * Description:
   *   NuttX Callback: Stop the interface.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static int lpc54_eth_ifdown(struct net_driver_s *dev)
  {
    struct lpc54_ethdriver_s *priv =
      (struct lpc54_ethdriver_s *)dev->d_private;
    irqstate_t flags;
    uint32_t regval;
    int ret;
  
    /* Disable the Ethernet interrupt */
  
    flags = enter_critical_section();
    up_disable_irq(LPC54_IRQ_ETHERNET);
  
    /* Cancel the TX poll timer and TX timeout timers */
  
    wd_cancel(&priv->eth_txpoll);
    wd_cancel(&priv->eth_txtimeout);
  
    /* Put the EMAC in its post-reset, non-operational state.  This should be
     * a known configuration that will guarantee the lpc54_eth_ifup() always
     * successfully brings the interface back up.
     *
     * Reset the Ethernet interface.
     */
  
    lpc54_reset_eth();
  
    /* Set the CSR clock divider */
  
    lpc54_set_csrdiv();
  
    /* Select MII or RMII mode */
  
    regval  = lpc54_getreg(LPC54_SYSCON_ETHPHYSEL);
    regval &= ~SYSCON_ETHPHYSEL_MASK;
  #ifdef CONFIG_LPC54_ETH_MII
    regval |= SYSCON_ETHPHYSEL_MII;
  #else
    regval |= SYSCON_ETHPHYSEL_RMII;
  #endif
    lpc54_putreg(regval, LPC54_SYSCON_ETHPHYSEL);
  
    /* Reset the PHY and bring it to an operational state.  We must be capable
     * of handling PHY ioctl commands while the network is down.
     */
  
    ret = lpc54_phy_reset(priv);
    if (ret < 0)
      {
        nerr("ERROR: lpc54_phy_reset failed: %d\n", ret);
        return ret;
      }
  
    /* Mark the device "down" */
  
    priv->eth_bifup = 0;
    leave_critical_section(flags);
    return OK;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_txavail_work
   *
   * Description:
   *   Perform an out-of-cycle poll on the worker thread.
   *
   * Input Parameters:
   *   arg - Reference to the NuttX driver state structure (cast to void*)
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Runs on a work queue thread.
   *
   ****************************************************************************/
  
  static void lpc54_eth_txavail_work(void *arg)
  {
    struct lpc54_ethdriver_s *priv = (struct lpc54_ethdriver_s *)arg;
  
    /* Lock the network and serialize driver operations if necessary.
     * NOTE: Serialization is only required in the case where the driver work
     * is performed on an LP worker thread and where more than one LP worker
     * thread has been configured.
     */
  
    net_lock();
  
    /* Ignore the notification if the interface is not yet up */
  
    if (priv->eth_bifup)
      {
        /* Poll the network for new XMIT data. */
  
        lpc54_eth_dopoll(priv);
      }
  
    net_unlock();
  }
  
  /****************************************************************************
   * Name: lpc54_eth_txavail
   *
   * Description:
   *   Driver callback invoked when new TX data is available.  This is a
   *   stimulus perform an out-of-cycle poll and, thereby, reduce the TX
   *   latency.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  static int lpc54_eth_txavail(struct net_driver_s *dev)
  {
    struct lpc54_ethdriver_s *priv =
      (struct lpc54_ethdriver_s *)dev->d_private;
  
    /* Is our single work structure available?  It may not be if there are
     * pending interrupt actions and we will have to ignore the Tx
     * availability action.
     */
  
    if (work_available(&priv->eth_pollwork))
      {
        /* Schedule to serialize the poll on the worker thread. */
  
        work_queue(ETHWORK, &priv->eth_pollwork,
                   lpc54_eth_txavail_work, priv, 0);
      }
  
    return OK;
  }
  
  /****************************************************************************
   * Name: lpc54_eth_addmac
   *
   * Description:
   *   NuttX Callback: Add the specified MAC address to the hardware multicast
   *   address filtering
   *
   * Input Parameters:
   *   dev  - Reference to the NuttX driver state structure
   *   mac  - The MAC address to be added
   *
   * Returned Value:
   *   Zero (OK) on success; a negated errno value on failure.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_NET_MCASTGROUP
  static int lpc54_eth_addmac(struct net_driver_s *dev, const uint8_t *mac)
  {
    /* Unlike other Ethernet hardware, the LPC54xx does not seem to support
     * explicit Multicast address filtering as needed for ICMPv6 and for IGMP.
     * In these cases, I am simply accepting all multicast packets.
     */
  
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_eth_rmmac
   *
   * Description:
   *   NuttX Callback: Remove the specified MAC address from the hardware
   *   multicast address filtering
   *
   * Input Parameters:
   *   dev  - Reference to the NuttX driver state structure
   *   mac  - The MAC address to be removed
   *
   * Returned Value:
   *   Zero (OK) on success; a negated errno value on failure.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_NET_MCASTGROUP
  static int lpc54_eth_rmmac(struct net_driver_s *dev, const uint8_t *mac)
  {
    /* Unlike other Ethernet hardware, the LPC54xx does not seem to support
     * explicit Multicast address filtering as needed for ICMPv6 and for IGMP.
     * In these cases, I am simply accepting all multicast packets.
     */
  
    return -ENOSYS;
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_eth_ioctl
   *
   * Description:
   *   Handle network IOCTL commands directed to this device.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *   cmd - The IOCTL command
   *   arg - The argument for the IOCTL command
   *
   * Returned Value:
   *   OK on success; Negated errno on failure.
   *
   * Assumptions:
   *   The network is locked.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_NETDEV_IOCTL
  static int lpc54_eth_ioctl(struct net_driver_s *dev, int cmd,
                             unsigned long arg)
  {
  #ifdef CONFIG_NETDEV_PHY_IOCTL
    struct lpc54_ethdriver_s *priv =
      (struct lpc54_ethdriver_s *)dev->d_private;
  #endif
    int ret;
  
    /* Decode and dispatch the driver-specific IOCTL command */
  
    switch (cmd)
      {
  #ifdef CONFIG_NETDEV_PHY_IOCTL
       case SIOCGMIIPHY: /* Get MII PHY address */
          {
            struct mii_ioctl_data_s *req =
          (struct mii_ioctl_data_s *)((uintptr_t)arg);
            req->phy_id = CONFIG_LPC54_ETH_PHYADDR;
            ret = OK;
          }
          break;
  
        case SIOCGMIIREG: /* Get register from MII PHY */
          {
            struct mii_ioctl_data_s *req =
          (struct mii_ioctl_data_s *)((uintptr_t)arg);
            req->val_out = lpc54_phy_read(priv, req->reg_num);
            ret = OK
          }
          break;
  
        case SIOCSMIIREG: /* Set register in MII PHY */
          {
            struct mii_ioctl_data_s *req =
          (struct mii_ioctl_data_s *)((uintptr_t)arg);
            lpc54_phy_write(priv, req->reg_num, req->val_in);
            ret = OK
          }
          break;
  #endif /* ifdef CONFIG_NETDEV_PHY_IOCTL */
  
        default:
          nerr("ERROR: Unrecognized IOCTL command: %d\n", command);
          return -ENOTTY;  /* Special return value for this case */
      }
  
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: lpc54_pktbuf_initialize
   *
   * Description:
   *   Initialize packet buffers my placing all of the pre-allocated packet
   *   buffers into a free list.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void lpc54_pktbuf_initialize(struct lpc54_ethdriver_s *priv)
  {
    uint32_t *pktbuf;
    int i;
  
    for (i = 0, pktbuf = g_prealloc_buffers;
         i < LPC54_NBUFFERS;
         i++, pktbuf += LPC54_BUFFER_WORDS)
      {
        sq_addlast((sq_entry_t *)pktbuf, &priv->eth_freebuf);
      }
  }
  
  /****************************************************************************
   * Name: lpc54_pktbuf_alloc
   *
   * Description:
   *   Allocate one packet buffer by removing it from the free list.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   A pointer to the allocated packet buffer on success; NULL is returned if
   *   there are no available packet buffers.
   *
   * Assumptions:
   *   The network must be locked.  Mutually exclusive access to the free list
   *   is maintained by locking the network.
   *
   ****************************************************************************/
  
  static inline uint32_t *lpc54_pktbuf_alloc(struct lpc54_ethdriver_s *priv)
  {
    return (uint32_t *)sq_remfirst(&priv->eth_freebuf);
  }
  
  /****************************************************************************
   * Name: lpc54_pktbuf_free
   *
   * Description:
   *   Allocate one packet buffer by removing it from the free list.
   *
   * Input Parameters:
   *   priv   - Reference to the driver state structure
   *   pktbuf - The packet buffer to be freed
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   The network must be locked.  Mutually exclusive access to the free list
   *   is maintained by locking the network.
   *
   ****************************************************************************/
  
  static inline void lpc54_pktbuf_free(struct lpc54_ethdriver_s *priv,
                                       uint32_t *pktbuf)
  {
    sq_addlast((sq_entry_t *)pktbuf, &priv->eth_freebuf);
  }
  
  /****************************************************************************
   * Name: lpc54_txring_initialize
   *
   * Description:
   *   Initialize one Tx descriptor ring.
   *
   * Input Parameters:
   *   priv   - Reference to the driver state structure
   *   chan   - Channel being initialized
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void lpc54_txring_initialize(struct lpc54_ethdriver_s *priv,
                                      unsigned int chan)
  {
    struct lpc54_txring_s *txring;
    struct enet_txdesc_s *txdesc;
    uint32_t regval;
    int i;
  
    txring  = &priv->eth_txring[chan];
    txdesc  = txring->tr_desc;
  
    /* Set the word-aligned Tx descriptor start/tail pointers. */
  
    regval  = (uint32_t)txdesc;
    lpc54_putreg(regval, LPC54_ETH_DMACH_TXDESC_LIST_ADDR(chan));
  
    regval += txring->tr_ndesc * sizeof(struct enet_txdesc_s);
    lpc54_putreg(regval, LPC54_ETH_DMACH_TXDESC_TAIL_PTR(chan));
  
    /* Set the Tx ring length */
  
    regval = ETH_DMACH_TXDESC_RING_LENGTH(txring->tr_ndesc);
    lpc54_putreg(regval, LPC54_ETH_DMACH_TXDESC_RING_LENGTH(chan));
  
    /* Inituialize the Tx descriptors . */
  
    for (i = 0; i < txring->tr_ndesc; i++, txdesc++)
      {
        txdesc->buffer1  = 0;
        txdesc->buffer2  = 0;
        txdesc->buflen   = ETH_TXDES2_IOC;
        txdesc->ctrlstat = 0;
      }
  }
  
  /****************************************************************************
   * Name: lpc54_rxring_initialize
   *
   * Description:
   *   Initialize one Rx descriptor ring.
   *
   * Input Parameters:
   *   priv   - Reference to the driver state structure
   *   chan   - Channel being initialized
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void lpc54_rxring_initialize(struct lpc54_ethdriver_s *priv,
                                      unsigned int chan)
  {
    struct lpc54_rxring_s *rxring;
    struct enet_rxdesc_s *rxdesc;
    uint32_t regval;
    int i;
  
    rxring  = &priv->eth_rxring[chan];
    rxdesc  = rxring->rr_desc;
  
    /* Set the word-aligned Rx descriptor start/tail pointers. */
  
    regval  = (uint32_t)rxdesc;
    lpc54_putreg(regval, LPC54_ETH_DMACH_RXDESC_LIST_ADDR(chan));
  
    regval += rxring->rr_ndesc * sizeof(struct enet_rxdesc_s);
    lpc54_putreg(regval, LPC54_ETH_DMACH_RXDESC_TAIL_PTR(chan));
  
    /* Set the Rx ring length */
  
    regval = ETH_DMACH_RXDESC_RING_LENGTH(rxring->rr_ndesc);
    lpc54_putreg(regval, LPC54_ETH_DMACH_RXDESC_RING_LENGTH(chan));
  
    /* Set the receive buffer size (in words) in the Rx control register */
  
    regval  = lpc54_getreg(LPC54_ETH_DMACH_RX_CTRL(chan));
    regval &= ~ETH_DMACH_RX_CTRL_RBSZ_MASK;
    regval |= ETH_DMACH_RX_CTRL_RBSZ(LPC54_BUFFER_SIZE >> 2);
    lpc54_putreg(regval, LPC54_ETH_DMACH_RX_CTRL(chan));
  
    /* Initialize the Rx descriptor ring. */
  
    regval  = ETH_RXDES3_BUF1V | ETH_RXDES3_IOC | ETH_RXDES3_OWN;
  #if LPC54_BUFFER_SIZE > LPC54_BUFFER_MAX
    regval |= ETH_RXDES3_BUF2V;
  #endif
  
    for (i = 0; i < rxring->rr_ndesc; i++, rxdesc++)
      {
        /* Allocate the first Rx packet buffer */
  
        rxdesc->buffer1 = (uint32_t)lpc54_pktbuf_alloc(priv);
        DEBUGASSERT(rxdesc->buffer1 != 0);
        (rxring->rr_buffers)[i] = (uint32_t *)rxdesc->buffer1;
  
  #if LPC54_BUFFER_SIZE > LPC54_BUFFER_MAX
        /* Configure the second part of a large packet buffer as buffer2 */
  
        rxdesc->buffer2 = rxdesc->buffer1 + LPC54_BUFFER_MAX;
  #else
        /* The second buffer is not used */
  
        rxdesc->buffer2 = 0;
  #endif
  
        /* Buffer1 and maybe 2 valid, interrupt on completion, owned by DMA. */
  
        rxdesc->ctrl = regval;
      }
  }
  
  /****************************************************************************
   * Name: lpc54_ring_initialize
   *
   * Description:
   *   Initialize the Rx and Tx rings for every channel.
   *
   * Input Parameters:
   *   priv   - Reference to the driver state structure
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void lpc54_ring_initialize(struct lpc54_ethdriver_s *priv)
  {
    /* Initialize ring descriptions */
  
    memset(priv->eth_txring, 0, LPC54_NRINGS * sizeof(struct lpc54_txring_s));
    memset(priv->eth_rxring, 0, LPC54_NRINGS * sizeof(struct lpc54_rxring_s));
  
    /* Initialize channel 0 rings */
  
    memset(g_txbuffers0, 0, CONFIG_LPC54_ETH_NTXDESC0 * sizeof(uint32_t *));
    memset(g_rxbuffers0, 0, CONFIG_LPC54_ETH_NRXDESC0 * sizeof(uint32_t *));
  
    priv->eth_txring[0].tr_desc     = g_ch0_txdesc;
    priv->eth_txring[0].tr_ndesc    = CONFIG_LPC54_ETH_NTXDESC0;
    priv->eth_txring[0].tr_buffers  = g_txbuffers0;
    lpc54_txring_initialize(priv, 0);
  
    priv->eth_rxring[0].rr_desc     = g_ch0_rxdesc;
    priv->eth_rxring[0].rr_ndesc    = CONFIG_LPC54_ETH_NRXDESC0;
    priv->eth_rxring[0].rr_buffers  = g_rxbuffers0;
    lpc54_rxring_initialize(priv, 0);
  
  #ifdef CONFIG_LPC54_ETH_MULTIQUEUE
    /* Initialize channel 1 rings */
  
    memset(g_txbuffers1, 0, CONFIG_LPC54_ETH_NTXDESC1 * sizeof(uint32_t *));
    memset(g_rxbuffers1, 0, CONFIG_LPC54_ETH_NRXDESC1 * sizeof(uint32_t *));
  
    priv->eth_txring[1].tr_desc     = g_ch1_txdesc;
    priv->eth_txring[1].tr_ndesc    = CONFIG_LPC54_ETH_NTXDESC1;
    priv->eth_txring[1].tr_buffers  = g_txbuffers1;
    lpc54_txring_initialize(priv, 1);
  
    priv->eth_rxring[1].rr_desc     = g_ch1_rxdesc;
    priv->eth_rxring[1].rr_ndesc    = CONFIG_LPC54_ETH_NRXDESC1;
    priv->eth_rxring[1].rr_buffers  = g_rxbuffers1;
    lpc54_rxring_initialize(priv, 1);
  #endif
  }
  
  /****************************************************************************
   * Name: lpc54_set_csrdiv
   *
   * Description:
   *   Set the CSR clock divider.  The MDC clock derives from the divided down
   *   CSR clock (aka core clock or main clock).
   *
   * Input Parameters:
   *   None
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void lpc54_set_csrdiv(void)
  {
    uint32_t srcclk = BOARD_MAIN_CLK / 1000000;
    uint32_t regval;
  
    regval = lpc54_getreg(LPC54_ETH_MAC_MDIO_ADDR);
    regval &= ~ETH_MAC_MDIO_ADDR_CR_MASK;
  
    if (srcclk < 35)
      {
        regval |= ETH_MAC_MDIO_ADDR_CR_DIV16;    /* CSR=20-35 MHz; MDC=CSR/16 */
      }
    else if (srcclk < 60)
      {
        regval |= ETH_MAC_MDIO_ADDR_CR_DIV26;    /* CSR=35-60 MHz; MDC=CSR/26 */
      }
    else if (srcclk < 100)
      {
        regval |= ETH_MAC_MDIO_ADDR_CR_DIV42;    /* CSR=60-100 MHz; MDC=CSR/42 */
      }
    else /* if (srcclk < 150) */
      {
        regval |= ETH_MAC_MDIO_ADDR_CR_DIV62;    /* CSR=100-150 MHz; MDC=CSR/62 */
      }
  
    lpc54_putreg(regval, LPC54_ETH_MAC_MDIO_ADDR);
  }
  
  /****************************************************************************
   * Name: lpc54_phy_read
   *
   * Description:
   *   Read the content from one PHY register.
   *
   * Input Parameters:
   *   priv   - Reference to the driver state structure
   *   phyreg - The 5-bit PHY address to read
   *
   * Returned Value:
   *   The 16-bit value read from the specified PHY register
   *
   ****************************************************************************/
  
  static uint16_t lpc54_phy_read(struct lpc54_ethdriver_s *priv,
                                 uint8_t phyreg)
  {
    uint32_t regval;
  
    /* Set the MII read command. */
  
    regval  = lpc54_getreg(LPC54_ETH_MAC_MDIO_ADDR);
    regval &= ETH_MAC_MDIO_ADDR_CR_MASK;
    regval |= ETH_MAC_MDIO_ADDR_MOC_READ | ETH_MAC_MDIO_ADDR_RDA(phyreg) |
              ETH_MAC_MDIO_ADDR_PA(CONFIG_LPC54_ETH_PHYADDR);
    lpc54_putreg(regval, LPC54_ETH_MAC_MDIO_ADDR);
  
    /* Initiate the read */
  
    regval |= ETH_MAC_MDIO_ADDR_MB;
    lpc54_putreg(regval, LPC54_ETH_MAC_MDIO_ADDR);
  
    /* Wait until the SMI is no longer busy with the read */
  
    while ((lpc54_getreg(LPC54_ETH_MAC_MDIO_ADDR) & ETH_MAC_MDIO_ADDR_MB) != 0)
      {
      }
  
    return (uint16_t)lpc54_getreg(LPC54_ETH_MAC_MDIO_DATA);
  }
  
  /****************************************************************************
   * Name: lpc54_phy_write
   *
   * Description:
   *   Write a new value to of one PHY register.
   *
   * Input Parameters:
   *   priv   - Reference to the driver state structure
   *   phyreg - The 5-bit PHY address to write
   *   phyval - The 16-bit value to write to the PHY register
   *
   * Returned Value:
   *   The 16-bit value read from the specified PHY register
   *
   ****************************************************************************/
  
  static void lpc54_phy_write(struct lpc54_ethdriver_s *priv,
                              uint8_t phyreg, uint16_t phyval)
  {
    uint32_t regval;
  
    /* Set the MII write command. */
  
    regval  = lpc54_getreg(LPC54_ETH_MAC_MDIO_ADDR);
    regval &= ETH_MAC_MDIO_ADDR_CR_MASK;
    regval |= ETH_MAC_MDIO_ADDR_MOC_WRITE | ETH_MAC_MDIO_ADDR_RDA(phyreg) |
              ETH_MAC_MDIO_ADDR_PA(CONFIG_LPC54_ETH_PHYADDR);
    lpc54_putreg(regval, LPC54_ETH_MAC_MDIO_ADDR);
  
    /* Set the write data */
  
    lpc54_putreg((uint32_t)phyval, LPC54_ETH_MAC_MDIO_DATA);
  
    /* Initiate the write */
  
    regval |= ETH_MAC_MDIO_ADDR_MB;
    lpc54_putreg(regval, LPC54_ETH_MAC_MDIO_ADDR);
  
    /* Wait until the SMI is no longer busy with the write */
  
    while ((lpc54_getreg(LPC54_ETH_MAC_MDIO_ADDR) & ETH_MAC_MDIO_ADDR_MB) != 0)
      {
      }
  }
  
  /****************************************************************************
   * Name: lpc54_phy_linkstatus
   *
   * Description:
   *   Read the MII status register and return tru if the link is up.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   true if the link is up
   *
   ****************************************************************************/
  
  static inline bool lpc54_phy_linkstatus(struct lpc54_ethdriver_s *priv)
  {
    /* Read the status register and return tru of the linkstatus bit is set. */
  
    return ((lpc54_phy_read(priv, MII_MSR) & MII_MSR_LINKSTATUS) != 0);
  }
  
  /****************************************************************************
   * Name: lpc54_phy_autonegotiate
   *
   * Description:
   *   Initialize the PHY.
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   OK on success; a negated errno on failure
   *
   ****************************************************************************/
  
  static int lpc54_phy_autonegotiate(struct lpc54_ethdriver_s *priv)
  {
    volatile int32_t timeout;
    uint16_t phyval;
  
    /* Advertise our capabilities. */
  
    phyval = (MII_ADVERTISE_CSMA | MII_ADVERTISE_10BASETXHALF |
              MII_ADVERTISE_10BASETXFULL | MII_ADVERTISE_100BASETXHALF |
              MII_ADVERTISE_100BASETXFULL);
    lpc54_phy_write(priv, MII_ADVERTISE, phyval);
  
    /* Start Auto negotiation and wait until auto negotiation completion */
  
    phyval = (MII_MCR_ANENABLE | MII_MCR_ANRESTART);
    lpc54_phy_write(priv, MII_MCR, phyval);
  
    /* Wait for the completion of autonegotiation. */
  
  #ifdef CONFIG_ETH0_PHY_LAN8720
    timeout = LPC54_PHY_TIMEOUT;
    do
      {
        if (timeout-- <= 0)
          {
            nerr("ERROR: Autonegotiation timed out\n");
            return -ETIMEDOUT;
          }
  
        phyval = lpc54_phy_read(priv, MII_LAN8720_SCSR);
      }
    while ((phyval & MII_LAN8720_SPSCR_ANEGDONE) == 0);
  #else
  #  error Unrecognized PHY
  #endif
  
    /* Wait for the link to be in the UP state */
  
    timeout = LPC54_PHY_TIMEOUT;
    do
      {
        if (timeout-- <= 0)
          {
            nerr("ERROR: Link status UP timed out\n");
            return -ETIMEDOUT;
          }
      }
    while (!lpc54_phy_linkstatus(priv));
  
    /* Get the negotiate PHY link mode. */
  
  #ifdef CONFIG_ETH0_PHY_LAN8720
    /* Read the LAN8720 SPCR register. */
  
    phyval               = lpc54_phy_read(priv, MII_LAN8720_SCSR);
    priv->eth_fullduplex = ((phyval & MII_LAN8720_SPSCR_DUPLEX) != 0);
    priv->eth_100mbps    = ((phyval & MII_LAN8720_SPSCR_100MBPS) != 0);
  #else
  #  error Unrecognized PHY
  #endif
  
    return OK;
  }
  
  /****************************************************************************
   * Name: lpc54_phy_reset
   *
   * Description:
   *   Reset the PHY and bring it to the operational status
   *
   * Input Parameters:
   *   priv - Reference to the driver state structure
   *
   * Returned Value:
   *   OK on success; a negated errno on failure
   *
   ****************************************************************************/
  
  static int lpc54_phy_reset(struct lpc54_ethdriver_s *priv)
  {
    volatile int32_t timeout;
    uint16_t phyid1;
    uint16_t phyval;
  
    /* Read and verify the PHY ID1 register */
  
    timeout = LPC54_PHY_TIMEOUT;
    do
      {
        if (timeout-- <= 0)
          {
            nerr("ERROR: PHY start up timed out\n");
            return -ETIMEDOUT;
          }
  
        phyid1 = lpc54_phy_read(priv, MII_PHYID1);
      }
    while (phyid1 != LPC54_PHYID1_VAL);
  
    /* Reset PHY and wait until completion. */
  
    lpc54_phy_write(priv, MII_MCR, MII_MCR_RESET);
  
    timeout = LPC54_PHY_TIMEOUT;
    do
      {
        if (timeout-- <= 0)
          {
            nerr("ERROR: PHY reset timed out\n");
            return -ETIMEDOUT;
          }
  
        phyval = lpc54_phy_read(priv, MII_MCR);
      }
    while ((phyval & MII_MCR_RESET) != 0);
  
    return OK;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: arm_netinitialize
   *
   * Description:
   *   Initialize the Ethernet controller and driver.
   *
   *   This is the "standard" network initialization logic called from the
   *   low-level initialization logic in arm_initialize.c.
   *
   * Input Parameters:
   *   intf - In the case where there are multiple EMACs, this value
   *          identifies which EMAC is to be initialized.
   *
   * Returned Value:
   *   OK on success; Negated errno on failure.
   *
   * Assumptions:
   *   Called early in initialization before multi-tasking is initiated.
   *
   ****************************************************************************/
  
  int arm_netinitialize(int intf)
  {
    struct lpc54_ethdriver_s *priv;
    int ret;
  
    /* Get the interface structure associated with this interface number. */
  
    DEBUGASSERT(intf == 0);
    priv = &g_ethdriver;
  
    /* Attach the three Ethernet-related IRQs to the handlers */
  
    ret = irq_attach(LPC54_IRQ_ETHERNET, lpc54_eth_interrupt, priv);
    if (ret < 0)
      {
        /* We could not attach the ISR to the interrupt */
  
        nerr("ERROR:  irq_attach failed: %d\n", ret);
        return -EAGAIN;
      }
  
  #if 0 /* Not used */
    ret = irq_attach(LPC54_IRQ_ETHERNETPMT, lpc54_pmt_interrupt, priv);
    if (ret < 0)
      {
        /* We could not attach the ISR to the interrupt */
  
        nerr("ERROR:  irq_attach for PMT failed: %d\n", ret);
        return -EAGAIN;
      }
  
    ret = irq_attach(LPC54_IRQ_ETHERNETMACLP, lpc54_mac_interrupt, priv);
    if (ret < 0)
      {
        /* We could not attach the ISR to the interrupt */
  
        nerr("ERROR:  irq_attach for MAC failed: %d\n", ret);
        return -EAGAIN;
      }
  #endif
  
    /* Initialize the driver structure */
  
    memset(priv, 0, sizeof(struct lpc54_ethdriver_s));
    priv->eth_dev.d_ifup    = lpc54_eth_ifup;     /* I/F up (new IP address) callback */
    priv->eth_dev.d_ifdown  = lpc54_eth_ifdown;   /* I/F down callback */
    priv->eth_dev.d_txavail = lpc54_eth_txavail;  /* New TX data callback */
  #ifdef CONFIG_NET_MCASTGROUP
    priv->eth_dev.d_addmac  = lpc54_eth_addmac;   /* Add multicast MAC address */
    priv->eth_dev.d_rmmac   = lpc54_eth_rmmac;    /* Remove multicast MAC address */
  #endif
  #ifdef CONFIG_NETDEV_IOCTL
    priv->eth_dev.d_ioctl   = lpc54_eth_ioctl;    /* Handle network IOCTL commands */
  #endif
    priv->eth_dev.d_private = &g_ethdriver;       /* Used to recover private state from dev */
  
    /* Configure GPIO pins to support Ethernet */
  
    /* Common MIIM interface */
  
    lpc54_gpio_config(GPIO_ENET_MDIO);    /* Ethernet MIIM data input and output */
    lpc54_gpio_config(GPIO_ENET_MDC);     /* Ethernet MIIM clock */
  
  #ifdef CONFIG_LPC54_ETH_MII
    /* MII interface */
  
    lpc54_gpio_config(GPIO_ENET_RXD0);    /* Ethernet receive data 0-3 */
    lpc54_gpio_config(GPIO_ENET_RXD1);
    lpc54_gpio_config(GPIO_ENET_RXD2);
    lpc54_gpio_config(GPIO_ENET_RXD3);
    lpc54_gpio_config(GPIO_ENET_TXD0);    /* Ethernet transmit data 0-3 */
    lpc54_gpio_config(GPIO_ENET_TXD1);
    lpc54_gpio_config(GPIO_ENET_TXD2);
    lpc54_gpio_config(GPIO_ENET_TXD3);
    lpc54_gpio_config(GPIO_ENET_COL);     /* Ethernet collision detect */
    lpc54_gpio_config(GPIO_ENET_CRS);     /* Ethernet carrier sense */
    lpc54_gpio_config(GPIO_ENET_RX_ER);   /* Ethernet transmit error */
    lpc54_gpio_config(GPIO_ENET_TX_CLK);  /* Ethernet transmit clock */
    lpc54_gpio_config(GPIO_ENET_RX_CLK);  /* Ethernet receive clock */
    lpc54_gpio_config(GPIO_ENET_TX_ER);   /* Ethernet receive error */
    lpc54_gpio_config(GPIO_ENET_TX_EN);   /* Ethernet transmit enable */
  #else
    /* RMII interface.
     *
     *   REF_CLK may be available in some implementations.  Clocking from
     *     PHY appears to be necessary for DMA reset operations.
     *   RX_ER is optional on switches.
     */
  
    lpc54_gpio_config(GPIO_ENET_RXD0);    /* Ethernet receive data 0-1 */
    lpc54_gpio_config(GPIO_ENET_RXD1);
    lpc54_gpio_config(GPIO_ENET_TXD0);    /* Ethernet transmit data 0-1 */
    lpc54_gpio_config(GPIO_ENET_TXD1);
    lpc54_gpio_config(GPIO_ENET_RX_DV);   /* Ethernet receive data valid */
    lpc54_gpio_config(GPIO_ENET_TX_EN);   /* Ethernet transmit data enable */
    lpc54_gpio_config(GPIO_ENET_REF_CLK); /* PHY reference clock */
  #endif
  
    /* Enable clocking to the Ethernet peripheral */
  
    lpc54_eth_enableclk();
  
    /* Put the interface in the down state.  This amounts to resetting the
     * device by calling lpc54_eth_ifdown().
     */
  
    ret = lpc54_eth_ifdown(&priv->eth_dev);
    if (ret < 0)
      {
        nerr("ERROR:  lpc54_eth_ifdown failed: %d\n", ret);
        goto errout_with_clock;
      }
  
    /* Register the device with the OS so that socket IOCTLs can be performed */
  
    ret = netdev_register(&priv->eth_dev, NET_LL_ETHERNET);
    if (ret < 0)
      {
        nerr("ERROR:  netdev_register failed: %d\n", ret);
        goto errout_with_clock;
      }
  
    return OK;
  
  errout_with_clock:
    lpc54_eth_disableclk();
    return ret;
  }
  
  #endif /* CONFIG_LPC54_ETHERNET */
  