nuttx/arch/arm/src/lpc54xx/lpc54_ethernet.c
2021-03-09 23:18:28 +08:00

3164 lines
90 KiB
C

/****************************************************************************
* arch/arm/src/lpc54xx/lpc54_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_timer(&priv->eth_dev, 0, 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 */