/****************************************************************************
 * arch/arm/src/c5471/c5471_ethernet.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#if defined(CONFIG_NET)

#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <debug.h>

#include <arpa/inet.h>
#include <net/ethernet.h>

#include <nuttx/wdog.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/wqueue.h>
#include <nuttx/net/arp.h>
#include <nuttx/net/netdev.h>

#ifdef CONFIG_NET_PKT
#  include <nuttx/net/pkt.h>
#endif

#include "chip.h"
#include "arm_internal.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

/* If processing is not done at the interrupt level, then work queue support
 * is required.
 */

#if !defined(CONFIG_SCHED_WORKQUEUE)
#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
#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

/* CONFIG_C5471_NET_NINTERFACES determines the number of physical interfaces
 * that will be supported.
 */

#ifndef CONFIG_C5471_NET_NINTERFACES
# define CONFIG_C5471_NET_NINTERFACES 1
#endif

/* CONFIG_C5471_NET_STATS will enabled collection of driver statistics.
 * Default is disabled.
 */

/* Mode of operation defaults to AUTONEGOTIATION */

#if defined(CONFIG_C5471_AUTONEGOTIATION)
# undef CONFIG_C5471_BASET100
# undef CONFIG_C5471_BASET10
#elif defined(CONFIG_C5471_BASET100)
# undef CONFIG_C5471_AUTONEGOTIATION
# undef CONFIG_C5471_BASET10
#elif defined(CONFIG_C5471_BASET10)
# undef CONFIG_C5471_AUTONEGOTIATION
# undef CONFIG_C5471_BASET100
#else
# define CONFIG_C5471_AUTONEGOTIATION 1
# undef CONFIG_C5471_BASET100
# undef CONFIG_C5471_BASET10
#endif

/* This should be disabled unless you are performing very low level debug */

#undef CONFIG_C5471_NET_DUMPBUFFER

/* Timing values ************************************************************/

/* TX timeout = 1 minute */

#define C5471_TXTIMEOUT       (60*CLK_TCK)

/* Ethernet GPIO bit settings ***********************************************/

#define GPIO_CIO_MDIO         0x00004000
#define GPIO_IO_MDCLK         0x00008000

/* Ethernet interface bit settings ******************************************/

/* TX descriptor, word #0 */

#define EIM_TXDESC_OWN_HOST   0x80000000 /* Bit 15: Ownership bit */
#define EIM_TXDESC_OWN_ENET   0x00000000
#define EIM_TXDESC_WRAP_NEXT  0x40000000 /* Bit 14: Descriptor chain wrap */
#define EIM_TXDESC_WRAP_FIRST 0x00000000
#define EIM_TXDESC_FIF        0x20000000 /* Bit 13: First in frame */
#define EIM_TXDESC_LIF        0x10000000 /* Bit 12: Last in frame */
                                         /* Bits 8-11: Retry count status */
#define EIM_TXDESC_INTRE      0x00800000 /* Bit 7: TX_IRQ int enable */
#define EIM_TXDESC_STATUSMASK 0x007f0000 /* Bits 0-6: Status */
#define EIM_TXDESC_RETRYERROR 0x00400000 /*   Exceed retry error */
#define EIM_TXDESC_HEARTBEAT  0x00200000 /*   Heartbeat (SQE) */
#define EIM_TXDESC_LCOLLISON  0x00100000 /*   Late collision error */
#define EIM_TXDESC_COLLISION  0x00080000 /*   Collision */
#define EIM_TXDESC_CRCERROR   0x00040000 /*   CRC error */
#define EIM_TXDESC_UNDERRUN   0x00020000 /*   Underrun error */
#define EIM_TXDESC_LOC        0x00010000 /*   Loss of carrier */

/* Packet bytes value used for both TX and RX descriptors */

#define EIM_PACKET_BYTES      0x00000040

/* Count of descriptors */

#define NUM_DESC_TX           32
#define NUM_DESC_RX           64

/* TX descriptor, word #1 */

                                         /* Bit 15: reserved */
#define EIM_TXDESC_PADCRC     0x00004000 /* Bit 14: Enable padding small frames */
                                         /* Bits 11-13: reserved */
#define EIM_TXDESC_BYTEMASK   0x000007ff /* Bits 0-10: Descriptor byte count */

/* RX descriptor, word #0 */

#define EIM_RXDESC_OWN_HOST   0x80000000 /* Bit 15: Ownership bit */
#define EIM_RXDESC_OWN_ENET   0x00000000
#define EIM_RXDESC_WRAP_NEXT  0x40000000 /* Bit 14: Descriptor chain wrap */
#define EIM_RXDESC_WRAP_FIRST 0x00000000
#define EIM_RXDESC_FIF        0x20000000 /* Bit 13: First in frame */
#define EIM_RXDESC_LIF        0x10000000 /* Bit 12: Last in frame */
                                         /* Bits 8-11: reserved */
#define EIM_RXDESC_INTRE      0x00800000 /* Bit 7: RX_IRQ int enable */
#define EIM_RXDESC_STATUSMASK 0x007f0000 /* Bits 0-6: Status */
#define EIM_RXDESC_MISS       0x00400000 /*   Miss */
#define EIM_RXDESC_VLAN       0x00200000 /*   VLAN */
#define EIM_RXDESC_LFRAME     0x00100000 /*   Long frame error */
#define EIM_RXDESC_SFRAME     0x00080000 /*   Short frame error */
#define EIM_RXDESC_CRCERROR   0x00040000 /*   CRC error */
#define EIM_RXDESC_OVERRUN    0x00020000 /*   Overrun error */
#define EIM_RXDESC_ALIGN      0x00010000 /*   Non-octect align error */

#define EIM_RXDESC_PADCRC     0x00004000 /* Enable padding for small frames */

/* RX descriptor, word #1 */

                                         /* Bits 11-15: reserved */
#define EIM_RXDESC_BYTEMASK   0x000007ff /* Bits 0-10: Descriptor byte count */

/* EIM_CPU_FILTER bit settings */

                                         /* Bits 5-31: reserved */
#define EIM_FILTER_MACLA      0x00000010 /* Bit 4: Enable logical address+multicast filtering */
#define EIM_FILTER_LOGICAL    0x00000008 /* Bit 3: Enable ENET logical filtering */
#define EIM_FILTER_MULTICAST  0x00000004 /* Bit 2: Enable multicast filtering */
#define EIM_FILTER_BROADCAST  0x00000002 /* Bit 1: Enable broadcast matching */
#define EIM_FILTER_UNICAST    0x00000001 /* Bit 0: Enable dest CPU address matching */

/* EIM_CTRL bit settings */

                                         /* Bits 16-31: Reserved */
#define EIM_CTRL_ESM_EN       0x00008000 /* Bit 15: Ethernet state machine enable */
                                         /* Bits 9-14: reserved */
#define EIM_CTRL_ENET0_EN     0x00000100 /* Bit 8: Enable routing of RX packets CPU->ENET0 */
                                         /* Bit 7: reserved */
#define EIM_CTRL_ENET0_FLW    0x00000040 /* Bit 6: Enable ENET0 flow control RX threshold */
#define EIM_CTRL_RXENET0_EN   0x00000020 /* Bit 5: Enable processing of ENET0 RX queue */
#define EIM_CTRL_TXENET0_EN   0x00000010 /* Bit 4: Enable processing of ENET0 TX queue */
                                         /* Bits 2-3: reserved */
#define EIM_CTRL_RXCPU_EN     0x00000002 /* Bit 1: Enable processing of CPU RX queue */
#define EIM_CTRL_TXCPU_EN     0x00000001 /* Bit 0: Enable processing of CPU TX queue */

/* EIM_STATUS bit settings */

                                         /* Bits 10-31: reserved */
#define EIM_STATUS_CPU_TXLIF  0x00000200 /* Bit 9: Last descriptor of TX packet filled */
#define EIM_STATUS_CPU_RXLIF  0x00000100 /* Bit 8: Last descriptor of RX queue processed */
#define EIM_STATUS_CPU_TX     0x00000080 /* Bit 7: Descriptor filled in TX queue */
#define EIM_STATUS_CPU_RX     0x00000040 /* Bit 6: Descriptor filled in RX queue */
                                         /* Bits 3-5: reserved */
#define EIM_STATUS_ENET0_ERR  0x00000004 /* Bit 2: ENET0 error interrupt */
#define EIM_STATUS_ENET0_TX   0x00000002 /* Bit 1: ENET0 TX interrupt */
#define EIM_STATUS_ENET0_RX   0x00000001 /* Bit 0" ENET0 RX interrupt */

/* EIM_INTEN bit settings */

#define EIM_INTEN_CPU_TXLIF  0x00000200 /* Bit 9: Last descriptor of TX packet filled */
#define EIM_INTEN_CPU_RXLIF  0x00000100 /* Bit 8: Last descriptor of RX queue processed */
#define EIM_INTEN_CPU_TX     0x00000080 /* Bit 7: Descriptor filled in TX queue */
#define EIM_INTEN_CPU_RX     0x00000040 /* Bit 6: Descriptor filled in RX queue */
                                        /* Bits 3-5: reserved */
#define EIM_INTEN_ENET0_ERR  0x00000004 /* Bit 2: ENET0 error interrupt */
#define EIM_INTEN_ENET0_TX   0x00000002 /* Bit 1: ENET0 TX interrupt */
#define EIM_INTEN_ENET0_RX   0x00000001 /* Bit 0: ENET0 RX interrupt */

/* ENET0_ADRMODE_EN bit settings */

#define ENET_ADR_PROMISCUOUS  0x00000008 /* Bit 3: Enable snoop address comparison */
#define ENET_ADR_BROADCAST    0x00000004 /* Bit 2: Enable broadcast address comparison */
#define ENET_ADDR_LCOMPARE    0x00000002 /* Bit 1: Enable logical address comparison */
#define ENET_ADDR_PCOMPARE    0x00000001 /* Bit 0: Enable physical address comparison */

/* ENET0_MODE bit settings */

                                         /* Bits 16-31: reserved */
#define ENET_MODO_FIFO_EN     0x00008000 /* Bit 15: Fifo enable */
                                         /* Bits 8-14: reserved */
#define ENET_MODE_RJCT_SFE    0x00000080 /* Bit 7: Reject short frames during receive */
#define ENET_MODE_DPNET       0x00000040 /* Bit 6: Demand priority networkd vs CSMA/CD */
#define ENET_MODE_MWIDTH      0x00000020 /* Bit 5: Select nibble mode MII port */
#define ENET_MODE_WRAP        0x00000010 /* Bit 4: Internal MAC loopback */
#define ENET_MODE_FDWRAP      0x00000008 /* Bit 3: Full duplex wrap */
#define ENET_MODE_FULLDUPLEX  0x00000004 /* Bit 2: 1:Full duplex */
#define ENET_MODE_HALFDUPLEX  0x00000000 /*        0:Half duplex */
                                         /* Bit 1: reserved */
#define ENET_MODE_ENABLE      0x00000001 /* Bit 0: Port enable */

/* PHY registers */

#define MD_PHY_CONTROL_REG    0x00
#define MD_PHY_MSB_REG        0x02
#define MD_PHY_LSB_REG        0x03
#define MD_PHY_CTRL_STAT_REG  0x17

/*  Lucent LU3X31T-T64 transeiver ID */

#define LU3X31_T64_PHYID      0x00437421

/* PHY control register bit settings */

#define MODE_AUTONEG          0x1000
#define MODE_10MBIT_HALFDUP   0x0000
#define MODE_10MBIT_FULLDUP   0x0100
#define MODE_100MBIT_FULLDUP  0x2100
#define MODE_100MBIT_HALFDUP  0x2000

/* Inserts an ARM "nop" instruction */

#define nop() asm("    nop");

/* This is a helper pointer for accessing the contents of Ethernet header */

#define BUF ((struct eth_hdr_s *)priv->c_dev.d_buf)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* A single packet buffer is used */

static uint8_t g_pktbuf[CONFIG_C5471_NET_NINTERFACES]
                       [MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];

/* The c5471_driver_s encapsulates all state information for a single c5471
 * hardware interface
 */

struct c5471_driver_s
{
  bool    c_bifup;           /* true:ifup false:ifdown */
  struct wdog_s c_txtimeout; /* TX timeout timer */
  struct work_s c_irqwork;   /* For deferring interrupt work to the work queue */
  struct work_s c_pollwork;  /* For deferring poll work to the work queue */

  /* Note: According to the C547x documentation: "The software has to
   * maintain two pointers to the current RX-CPU and TX-CPU descriptors.
   * At init time, they have to be set to the first descriptors of each
   * queue, and they have to be incremented each time a descriptor
   * ownership is give to the SWITCH".
   */

  volatile uint32_t c_txcpudesc;
  volatile uint32_t c_rxcpudesc;

  /* Last TX descriptor saved for error handling */

  uint32_t c_lastdescstart;
  uint32_t c_lastdescend;

  /* Shadowed registers */

  uint32_t c_eimstatus;

#ifdef CONFIG_C5471_NET_STATS
  /* TX statistics */

  uint32_t c_txpackets;      /* Number of packets sent */
  uint32_t c_txmiss;         /* Miss */
  uint32_t c_txvlan;         /* VLAN */
  uint32_t c_txlframe;       /* Long frame errors */
  uint32_t c_txsframe;       /* Short frame errors */
  uint32_t c_txcrc;          /* CRC errors */
  uint32_t c_txoverrun;      /* Overrun errors */
  uint32_t c_txalign;        /* Non-octect align errors */
  uint32_t c_txtimeouts;     /* TX timeouts */

  uint32_t c_rxpackets;      /* Number of packets received */
  uint32_t c_rxretries;      /* Exceed retry errors */
  uint32_t c_rxheartbeat;    /* Heartbeat (SQE) */
  uint32_t c_rxlcollision;   /* Late collision errors */
  uint32_t c_rxcollision;    /* Collision */
  uint32_t c_rxcrc;          /* CRC errors */
  uint32_t c_rxunderrun;     /* Underrun errors */
  uint32_t c_rxloc;          /* Loss of carrier */
  uint32_t c_rxdropped;      /* Packets dropped because of size */
#endif

  /* This holds the information visible to the NuttX network */

  struct net_driver_s c_dev;  /* Interface understood by the network */
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct c5471_driver_s g_c5471[CONFIG_C5471_NET_NINTERFACES];

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Transceiver interface */

static void c5471_mdtxbit (int bit_state);
static int  c5471_mdrxbit (void);
static void c5471_mdwrite (int adr, int reg, int data);
static int  c5471_mdread (int adr, int reg);
static int  c5471_phyinit (void);

/* Support logic */

static inline void c5471_inctxcpu(struct c5471_driver_s *priv);
static inline void c5471_incrxcpu(struct c5471_driver_s *priv);

/* Common TX logic */

static int  c5471_transmit(struct c5471_driver_s *priv);
static int  c5471_txpoll(struct net_driver_s *dev);

/* Interrupt handling */

#ifdef CONFIG_C5471_NET_STATS
static void c5471_rxstatus(struct c5471_driver_s *priv);
#endif
static void c5471_receive(struct c5471_driver_s *priv);
#ifdef CONFIG_C5471_NET_STATS
static void c5471_txstatus(struct c5471_driver_s *priv);
#endif
static void c5471_txdone(struct c5471_driver_s *priv);

static void c5471_interrupt_work(void *arg);
static int  c5471_interrupt(int irq, void *context, void *arg);

/* Watchdog timer expirations */

static void c5471_txtimeout_work(void *arg);
static void c5471_txtimeout_expiry(wdparm_t arg);

/* NuttX callback functions */

static int c5471_ifup(struct net_driver_s *dev);
static int c5471_ifdown(struct net_driver_s *dev);

static void c5471_txavail_work(void *arg);
static int c5471_txavail(struct net_driver_s *dev);

#ifdef CONFIG_NET_MCASTGROUP
static int c5471_addmac(struct net_driver_s *dev, const uint8_t *mac);
static int c5471_rmmac(struct net_driver_s *dev, const uint8_t *mac);
#endif

/* Initialization functions */

static void c5471_eimreset (struct c5471_driver_s *priv);
static void c5471_eimconfig(struct c5471_driver_s *priv);
static void c5471_reset(struct c5471_driver_s *priv);
static void c5471_macassign(struct c5471_driver_s *priv);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: c5471_dumpbuffer
 *
 * Description:
 *   Debug only
 *
 ****************************************************************************/

#ifdef CONFIG_C5471_NET_DUMPBUFFER
static inline void c5471_dumpbuffer(const char *msg, const uint8_t *buffer,
                                    unsigned int nbytes)
{
  /* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_NET have to
   * be defined or the following does nothing.
   */

  ninfodumpbuffer(msg, buffer, nbytes);
}
#else
# define c5471_dumpbuffer(msg, buffer,nbytes)
#endif

/****************************************************************************
 * Name: c5471_mdtxbit
 *
 * Description:
 *   A helper routine used when serially communicating with the c547X's
 *   external ethernet transeiver device. GPIO pins are connected to the
 *   transeiver's MDCLK and MDIO pins and are used to accomplish the serial
 *   comm.
 *
 *   protocol:
 *                      ___________
 *     MDCLK   ________/           \_
 *              ________:____
 *     MDIO    <________:____>--------
 *                      :
 *                      ^
 *              Pin state internalized
 *
 ****************************************************************************/

static void c5471_mdtxbit (int bit_state)
{
  /* Note: any non-zero "bit_state" supplied by the caller means we should
   * clk a "1" out the MDIO pin.
   */

  /* Config MDIO as output pin. */

  putreg32((getreg32(GPIO_CIO) & ~GPIO_CIO_MDIO), GPIO_CIO);

  /* Select the bit output state */

  if (bit_state)
    {
      /* set MDIO state high. */

      putreg32((getreg32(GPIO_IO) | GPIO_CIO_MDIO), GPIO_IO);
    }
  else
    {
      /* set MDIO state low. */

      putreg32((getreg32(GPIO_IO) & ~GPIO_CIO_MDIO), GPIO_IO);
    }

  nop();
  nop();
  nop();
  nop();

  /* MDCLK rising edge */

  putreg32((getreg32(GPIO_IO) | GPIO_IO_MDCLK), GPIO_IO);
  nop();
  nop();

  /* release MDIO */

  putreg32((getreg32(GPIO_CIO) | GPIO_CIO_MDIO), GPIO_CIO);
  nop();
  nop();

  /* MDCLK falling edge. */

  putreg32((getreg32(GPIO_IO) & ~GPIO_IO_MDCLK), GPIO_IO);
}

/****************************************************************************
 * Name: c5471_mdrxbit
 *
 * Description:
 *    A helper routine used when serially communicating with the c547X's
 *    external ethernet transeiver device. GPIO pins are connected to the
 *    transeiver's MDCLK and MDIO pins and are used to accomplish the serial
 *    comm.
 *
 *    protocol:
 *                       ___________
 *       MDCLK  ________/           \_
 *              _______:_____
 *       MDIO   _______:_____>--------
 *                     :
 *                     ^
 *            pin state sample point
 *
 ****************************************************************************/

static int c5471_mdrxbit (void)
{
  register volatile uint32_t bit_state;

  /* config MDIO as input pin. */

  putreg32((getreg32(GPIO_CIO) | GPIO_CIO_MDIO), GPIO_CIO);

  /* Make sure the MDCLK is low */

  putreg32((getreg32(GPIO_IO) & ~GPIO_IO_MDCLK), GPIO_IO);
  nop();
  nop();
  nop();
  nop();

  /* Sample MDIO */

  bit_state = getreg32(GPIO_IO) & GPIO_CIO_MDIO;

  /* MDCLK rising edge */

  putreg32((getreg32(GPIO_IO) | GPIO_IO_MDCLK), GPIO_IO);
  nop();
  nop();
  nop();
  nop();

  /* MDCLK falling edge. */

  putreg32((getreg32(GPIO_IO) & ~GPIO_IO_MDCLK), GPIO_IO); /* MDCLK falling edge */
  if (bit_state)
    {
      return 1;
    }
  else
    {
      return OK;
    }
}

/****************************************************************************
 * Name: c5471_mdwrite
 *
 * Description:
 *    A helper routine used when serially communicating with the c547X's
 *    external ethernet transeiver device. GPIO pins are connected to the
 *    transeiver's MDCLK and MDIO pins and are used to accomplish the serial
 *    comm.
 *
 ****************************************************************************/

static void c5471_mdwrite (int adr, int reg, int data)
{
  int i;

  /* preamble: 11111111111111111111111111111111 */

  for (i = 0; i < 32; i++)
    {
      c5471_mdtxbit(1);
    }

  /* start of frame: 01 */

  c5471_mdtxbit(0);
  c5471_mdtxbit(1);

  /* operation code: 01 - write */

  c5471_mdtxbit(0);
  c5471_mdtxbit(1);

  /* PHY device address: AAAAA, msb first */

  for (i = 0; i < 5; i++)
    {
      c5471_mdtxbit(adr & 0x10);
      adr = adr << 1;
    }

  /* MII register address: RRRRR, msb first */

  for (i = 0; i < 5; i++)
    {
      c5471_mdtxbit(reg & 0x10);
      reg = reg << 1;
    }

  /* Turnaround time: ZZ */

  c5471_mdtxbit(1);
  c5471_mdtxbit(0);

  /* data: DDDDDDDDDDDDDDDD, msb first */

  for (i = 0; i < 16; i++)
    {
      c5471_mdtxbit(data & 0x8000);
      data = data << 1;
    }
}

/****************************************************************************
 * Name: c5471_mdread
 *
 * Description:
 *    A helper routine used when serially communicating with the c547X's
 *    external ethernet transeiver device. GPIO pins are connected to the
 *    transeiver's MDCLK and MDIO pins and are used to accomplish the serial
 *    comm.
 *
 ****************************************************************************/

static int c5471_mdread (int adr, int reg)
{
  int i;
  int data = 0;

  /* preamble: 11111111111111111111111111111111 */

  for (i = 0; i < 32; i++)
    {
      c5471_mdtxbit(1);
    }

  /* start of frame: 01 */

  c5471_mdtxbit(0);
  c5471_mdtxbit(1);

  /* operation code: 10 - read */

  c5471_mdtxbit(1);
  c5471_mdtxbit(0);

  /* PHY device address: AAAAA, msb first */

  for (i = 0; i < 5; i++)
    {
      c5471_mdtxbit(adr & 0x10);
      adr = adr << 1;
    }

  /* MII register address: RRRRR, msb first */

  for (i = 0; i < 5; i++)
    {
      c5471_mdtxbit(reg & 0x10);
      reg = reg << 1;
    }

  /* turnaround time: ZZ */

  c5471_mdrxbit();
  c5471_mdrxbit(); /* PHY should drive a 0 */

  /* data: DDDDDDDDDDDDDDDD, msb first */

  for (i = 0; i < 16; i++)
    {
      data = data << 1;
      data |= c5471_mdrxbit();
    }

  return data;
}

/****************************************************************************
 * Name: c5471_phyinit
 *
 * Description:
 *   The c547X EVM board uses a Lucent LU3X31T-T64 transeiver device to
 *   handle the physical layer (PHY). It's a h/w block that on the one end
 *   offers a Media Independent Interface (MII) which is connected to the
 *   Ethernet Interface Module (EIM) internal to the C547x and on the other
 *   end offers either the 10baseT or 100baseT electrical interface
 *   connecting to an RJ45 onboard network connector. The PHY transeiver has
 *   several internal registers allowing host configuration and status
 *   access. These internal registers are accessible by clocking serial data
 *   in/out of the MDIO pin of the LU3X31T-T64 chip. For c547X, the MDC and
 *   the MDIO pins are connected to the C547x GPIO15 and GPIO14 pins
 *   respectively. Host software twiddles the GPIO pins appropriately to get
 *   data serially into and out of the chip. This is typically a one time
 *   operation at boot and normal operation of the transeiver involves
 *   EIM/Transeiver interaction at the other pins of the transeiver chip and
 *   doesn't require host intervention at the MDC and MDIO pins.
 *
 ****************************************************************************/

#if defined(CONFIG_C5471_PHY_LU3X31T_T64)
static int c5471_phyinit (void)
{
  int phyid;
  int status;

  /* Next, Setup GPIO pins to talk serially to the Lucent transeiver chip */

  /* enable gpio bits 15,14 */

  putreg32((getreg32(GPIO_EN) | 0x0000c000), GPIO_EN);

  /* config gpio(15); out -> MDCLK */

  putreg32((getreg32(GPIO_CIO) & ~0x00008000), GPIO_CIO);

  /* config gpio(14); in <- MDIO */

  putreg32((getreg32(GPIO_CIO) | 0x00004000), GPIO_CIO);

  /* initial pin state; MDCLK = 0 */

  putreg32((getreg32(GPIO_IO) & 0x000f3fff), GPIO_IO);

  /* Next, request a chip reset */

  c5471_mdwrite(0, MD_PHY_CONTROL_REG, 0x8000);
  while (c5471_mdread(0, MD_PHY_CONTROL_REG) & 0x8000)
    {
      /* wait for chip reset to complete */
    }

  /* Next, Read out the chip ID */

  phyid = (c5471_mdread(0, MD_PHY_MSB_REG) << 16) |
          c5471_mdread(0, MD_PHY_LSB_REG);
  if (phyid != LU3X31_T64_PHYID)
    {
      nerr("ERROR: Unrecognized PHY ID: %08x\n", phyid);
      return -ENODEV;
    }

  /* Next, Set desired network rate, 10BaseT, 100BaseT, or auto. */

#ifdef CONFIG_C5471_AUTONEGOTIATION
  ninfo("Setting PHY Transceiver for Autonegotiation\n");
  c5471_mdwrite(0, MD_PHY_CONTROL_REG, MODE_AUTONEG);
#endif
#ifdef CONFIG_C5471_BASET100
  ninfo("Setting PHY Transceiver for 100BaseT FullDuplex\n");
  c5471_mdwrite(0, MD_PHY_CONTROL_REG, MODE_100MBIT_FULLDUP);
#endif
#ifdef CONFIG_C5471_BASET10
  ninfo("Setting PHY Transceiver for 10BaseT FullDuplex\n");
  c5471_mdwrite(0, MD_PHY_CONTROL_REG, MODE_10MBIT_FULLDUP);
#endif

  status = c5471_mdread(0, MD_PHY_CTRL_STAT_REG);
  return status;
}

#elif defined(CONFIG_C5471_PHY_AC101L)

static int c5471_phyinit (void)
{
  int phyid;
  int status;

  /* Next, Setup GPIO pins to talk serially to the Lucent transeiver chip */

  putreg32((getreg32(GPIO_EN)  |  0x0000c000), GPIO_EN);  /* enable gpio bits 15,14 */
  putreg32((getreg32(GPIO_CIO) & ~0x00008000), GPIO_CIO); /* config gpio(15); out -> MDCLK */
  putreg32((getreg32(GPIO_CIO) |  0x00004000), GPIO_CIO); /* config gpio(14); in <- MDIO */
  putreg32((getreg32(GPIO_IO)  &  0x000f3fff), GPIO_IO);  /* initial pin state; MDCLK = 0 */

  return 1;
}

#else
#  define c5471_phyinit()
#  warning "Assuming no PHY"
#endif

/****************************************************************************
 * Name: c5471_inctxcpu
 *
 * Description:
 *
 ****************************************************************************/

static inline void c5471_inctxcpu(struct c5471_driver_s *priv)
{
  if (EIM_TXDESC_WRAP_NEXT & getreg32(priv->c_txcpudesc))
    {
      /* Loop back around to base of descriptor queue */

      priv->c_txcpudesc = getreg32(EIM_CPU_TXBA) + EIM_RAM_START;
    }
  else
    {
      priv->c_txcpudesc += 2*sizeof(uint32_t);
    }

  ninfo("TX CPU desc: %08" PRIx32 "\n", priv->c_txcpudesc);
}

/****************************************************************************
 * Name: c5471_incrxcpu
 *
 * Description:
 *
 ****************************************************************************/

static inline void c5471_incrxcpu(struct c5471_driver_s *priv)
{
  if (EIM_RXDESC_WRAP_NEXT & getreg32(priv->c_rxcpudesc))
    {
      /* Loop back around to base of descriptor queue */

      priv->c_rxcpudesc = getreg32(EIM_CPU_RXBA) + EIM_RAM_START;
    }
  else
    {
      priv->c_rxcpudesc += 2*sizeof(uint32_t);
    }

  ninfo("RX CPU desc: %08" PRIx32 "\n", priv->c_rxcpudesc);
}

/****************************************************************************
 * Function: c5471_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
 *
 * Returned Value:
 *   OK on success; a negated errno on failure
 *
 * Assumptions:
 *
 ****************************************************************************/

static int c5471_transmit(struct c5471_driver_s *priv)
{
  struct net_driver_s *dev = &priv->c_dev;
  volatile uint16_t *packetmem;
  uint16_t framelen;
  bool bfirstframe;
  int nbytes;
  int nshorts;
  unsigned int i;
  unsigned int j;

  nbytes                = (dev->d_len + 1) & ~1;
  j                     = 0;
  bfirstframe           = true;
  priv->c_lastdescstart = priv->c_rxcpudesc;

  ninfo("Packet size: %d RX CPU desc: %08" PRIx32 "\n",
        nbytes, priv->c_rxcpudesc);
  c5471_dumpbuffer("Transmit packet", dev->d_buf, dev->d_len);

  while (nbytes)
    {
      /* Verify that the hardware is ready to send another packet
       * Words #0 and #1 of descriptor
       */

      while (EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc))
        {
          /* Loop until the SWITCH lets go of the descriptor giving us access
           * rights to submit our new ether frame to it.
           */
        }

      if (bfirstframe)
        {
          putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_FIF),
                   priv->c_rxcpudesc);
        }
      else
        {
          putreg32((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_FIF),
                   priv->c_rxcpudesc);
        }

      putreg32((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_PADCRC),
               priv->c_rxcpudesc);

      if (bfirstframe)
        {
          putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_PADCRC),
                   priv->c_rxcpudesc);
        }

      if (nbytes >= EIM_PACKET_BYTES)
        {
          framelen = EIM_PACKET_BYTES;
        }
      else
        {
          framelen = nbytes;
        }

      /* Submit ether frame bytes to the C5472 Ether Module packet memory
       * space. Get the number of 16-bit values to transfer by dividing by
       * 2 with round up.
       */

      nshorts = (framelen + 1) >> 1;

      /* Words #2 and #3 of descriptor */

      packetmem = (uint16_t *)getreg32(priv->c_rxcpudesc + sizeof(uint32_t));
      for (i = 0; i < nshorts; i++, j++)
        {
          /* 16-bits at a time. */

          packetmem[i] = HTONS(((uint16_t *)dev->d_buf)[j]);
        }

      putreg32(((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_BYTEMASK) |
                framelen), priv->c_rxcpudesc);
      nbytes -= framelen;
      ninfo("Wrote framelen: %d nbytes: %d nshorts: %d\n",
            framelen, nbytes, nshorts);

      if (0 == nbytes)
        {
          putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_LIF),
                   priv->c_rxcpudesc);
        }
      else
        {
          putreg32((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_LIF),
                   priv->c_rxcpudesc);
        }

      /* We're done with that descriptor; give access rights back to h/w */

      putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_OWN_HOST),
               priv->c_rxcpudesc);

      /* Next, tell Ether Module that
       * those submitted bytes are ready for the wire
       */

      putreg32(0x00000001, EIM_CPU_RXREADY);
      priv->c_lastdescend = priv->c_rxcpudesc;

      /* Advance to the next free descriptor */

      c5471_incrxcpu(priv);
      bfirstframe = false;
    }

  /* Packet transferred .. Update statistics */

#ifdef CONFIG_C5471_NET_STATS
  priv->c_txpackets++;
#endif

  /* Setup the TX timeout watchdog (perhaps restarting the timer) */

  wd_start(&priv->c_txtimeout, C5471_TXTIMEOUT,
           c5471_txtimeout_expiry, (wdparm_t)priv);
  return OK;
}

/****************************************************************************
 * Function: c5471_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:
 *
 ****************************************************************************/

static int c5471_txpoll(struct net_driver_s *dev)
{
  struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;

  /* Send the packet */

  c5471_transmit(priv);

  /* Check if the ESM has let go of the RX descriptor giving us
   * access rights to submit another Ethernet frame.
   */

  if ((EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) != 0)
    {
      /* No, then return non-zero to terminate the poll */

      return 1;
    }

  /* If zero is returned, the polling will continue until all connections
   * have been examined.
   */

  return 0;
}

/****************************************************************************
 * Function: c5471_rxstatus
 *
 * Description:
 *   An interrupt was received indicating that the last RX packet(s) is done
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

#ifdef CONFIG_C5471_NET_STATS
static void c5471_rxstatus(struct c5471_driver_s *priv)
{
  uint32_t desc = priv->c_txcpudesc;
  uint32_t rxstatus;

  /* Walk that last packet we just received to collect xmit status bits. */

  rxstatus = 0;
  for (; ; )
    {
      if (EIM_TXDESC_OWN_HOST & getreg32(desc))
        {
          /* The incoming packet queue is empty. */

          break;
        }

      rxstatus |= (getreg32(desc) & EIM_TXDESC_STATUSMASK);

      if ((getreg32(desc) & EIM_TXDESC_LIF) != 0)
        {
          break;
        }

      /* This packet is made up of several descriptors,
       * find next one in chain.
       */

      if (EIM_TXDESC_WRAP_NEXT & getreg32(desc))
        {
          /* Loop back around to base of descriptor queue. */

          desc = getreg32(EIM_CPU_TXBA) + EIM_RAM_START;
        }
      else
        {
          desc += 2 * sizeof(uint32_t);
        }
    }

  if (rxstatus != 0)
    {
      if ((rxstatus & EIM_TXDESC_RETRYERROR) != 0)
        {
          priv->c_rxretries++;
          ninfo("c_rxretries: %d\n", priv->c_rxretries);
        }

      if ((rxstatus & EIM_TXDESC_HEARTBEAT) != 0)
        {
          priv->c_rxheartbeat++;
          ninfo("c_rxheartbeat: %d\n", priv->c_rxheartbeat);
        }

      if ((rxstatus & EIM_TXDESC_LCOLLISON) != 0)
        {
          priv->c_rxlcollision++;
          ninfo("c_rxlcollision: %d\n", priv->c_rxlcollision);
        }

      if ((rxstatus & EIM_TXDESC_COLLISION) != 0)
        {
          priv->c_rxcollision++;
          ninfo("c_rxcollision: %d\n", priv->c_rxcollision);
        }

      if ((rxstatus & EIM_TXDESC_CRCERROR) != 0)
        {
          priv->c_rxcrc++;
          ninfo("c_rxcrc: %d\n", priv->c_rxcrc);
        }

      if ((rxstatus & EIM_TXDESC_UNDERRUN) != 0)
        {
          priv->c_rxunderrun++;
          ninfo("c_rxunderrun: %d\n", priv->c_rxunderrun);
        }

      if ((rxstatus & EIM_TXDESC_LOC) != 0)
        {
          priv->c_rxloc++;
          ninfo("c_rxloc: %d\n", priv->c_rxloc);
        }
    }
}
#endif

/****************************************************************************
 * Function: c5471_receive
 *
 * Description:
 *   An interrupt was received indicating the availability of a new RX packet
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void c5471_receive(struct c5471_driver_s *priv)
{
  struct net_driver_s *dev = &priv->c_dev;
  uint16_t *packetmem;
  bool bmore = true;
  int packetlen = 0;
  int framelen;
  int nshorts;
  int i;
  int j = 0;

  /* Walk the newly received packet contained within the EIM and transfer
   * its contents to the network buffer. This frees up the memory contained
   * within the EIM for additional packets that might be received later from
   * the network.
   */

  ninfo("Reading TX CPU desc: %08" PRIx32 "\n", priv->c_txcpudesc);
  while (bmore)
    {
      /* Words #0 and #1 of descriptor */

      if (EIM_TXDESC_OWN_HOST & getreg32(priv->c_txcpudesc))
        {
          /* No further packets to receive. */

          break;
        }

      /* Get the size of the frame from words #0 and #1 of the descriptor
       * and update the accumulated packet size
       */

      framelen   = (getreg32(priv->c_txcpudesc) & EIM_TXDESC_BYTEMASK);
      packetlen += framelen;

      /* Check if the received packet will fit
       * within the network packet buffer
       */

      if (packetlen < (CONFIG_NET_ETH_PKTSIZE + 4))
        {
          /* Get the packet memory from words #2 and #3 of descriptor */

          packetmem = (uint16_t *)
            getreg32(priv->c_txcpudesc + sizeof(uint32_t));

          /* Divide by 2 with round up to get the number of 16-bit words. */

          nshorts = (framelen + 1) >> 1;
          ninfo("Reading framelen: "
                "%d packetlen: %d nshorts: %d packetmen: %p\n",
                 framelen, packetlen, nshorts, packetmem);

          for (i = 0 ; i < nshorts; i++, j++)
            {
              /* Copy the data data from the hardware to priv->c_dev.d_buf
               * 16-bits at a time.
               */

              ((uint16_t *)dev->d_buf)[j] = HTONS(packetmem[i]);
            }
        }
      else
        {
          ninfo("Discarding framelen: %d packetlen %d\n",
                framelen, packetlen);
        }

      if (getreg32(priv->c_txcpudesc) & EIM_TXDESC_LIF)
        {
          bmore = false;
        }

      /* Next, Clear all bits of words0/1 of the emptied descriptor
       * except preserve the settings of a select few. Can leave
       * descriptor words 2/3 alone.
       */

      putreg32((getreg32(priv->c_txcpudesc) &
               (EIM_TXDESC_WRAP_NEXT | EIM_TXDESC_INTRE)),
               priv->c_txcpudesc);

      /* Next, Give ownership of now emptied descriptor back to
       * the Ether Module's SWITCH
       */

      putreg32((getreg32(priv->c_txcpudesc) | EIM_TXDESC_OWN_HOST),
               priv->c_txcpudesc);

      /* Advance to the next data buffer */

      c5471_inctxcpu(priv);
    }

  /* Adjust the packet length to remove the CRC bytes
   * that the network doesn't care about.
   */

  packetlen -= 4;

#ifdef CONFIG_C5471_NET_STATS
  /* Increment the count of received packets */

  priv->c_rxpackets++;
#endif

  /* If we successfully transferred the data into the network buffer,
   * then pass it on to the network for processing.
   */

  if (packetlen > 0 && packetlen < CONFIG_NET_ETH_PKTSIZE)
    {
      /* Set amount of data in priv->c_dev.d_len. */

      dev->d_len = packetlen;
      ninfo("Received packet, packetlen: %d type: %02x\n",
            packetlen, NTOHS(BUF->type));
      c5471_dumpbuffer("Received packet", dev->d_buf, dev->d_len);

#ifdef CONFIG_NET_PKT
      /* When packet sockets are enabled, feed the frame into the tap */

      pkt_input(dev);
#endif

      /* We only accept IP packets of the configured type and ARP packets */

#ifdef CONFIG_NET_IPv4
      if (BUF->type == HTONS(ETHTYPE_IP))
        {
          ninfo("IPv4 frame\n");

          /* Receive an IPv4 packet from the network device */

          ipv4_input(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.
           * Send that data now if ESM has let go of the RX descriptor giving
           * us access rights to submit another Ethernet frame.
           */

          if (dev->d_len > 0 &&
             (EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
            {
              /* And send the packet */

               c5471_transmit(priv);
            }
        }
      else
#endif
#ifdef CONFIG_NET_IPv6
      if (BUF->type == HTONS(ETHTYPE_IP6))
        {
          ninfo("IPv6 frame\n");

          /* Give the IPv6 packet to the network layer */

          ipv6_input(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.
           * Send that data now if ESM has let go of the RX descriptor giving
           * us access rights to submit another Ethernet frame.
           */

          if (dev->d_len > 0 &&
             (EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
            {
              /* And send the packet */

               c5471_transmit(priv);
            }
        }
      else
#endif
#ifdef CONFIG_NET_ARP
      if (BUF->type == HTONS(ETHTYPE_ARP))
        {
          arp_input(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.
           * Send that data now if ESM has let go of the RX descriptor giving
           * us access rights to submit another Ethernet frame.
           */

          if (dev->d_len > 0 &&
             (EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
            {
              c5471_transmit(priv);
            }
        }
#endif
    }
#ifdef CONFIG_C5471_NET_STATS
  else
    {
      /* Increment the count of dropped packets */

      nwarn("WARNING: Too big! packetlen: %d\n", packetlen);
      priv->c_rxdropped++;
    }
#endif
}

/****************************************************************************
 * Function: c5471_txstatus
 *
 * Description:
 *   An interrupt was received indicating that the last TX packet(s) is done
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

#ifdef CONFIG_C5471_NET_STATS
static void c5471_txstatus(struct c5471_driver_s *priv)
{
  uint32_t desc = priv->c_lastdescstart;
  uint32_t txstatus;

  /* Walk that last packet we just sent to collect xmit status bits. */

  txstatus = 0;
  if (priv->c_lastdescstart && priv->c_lastdescend)
    {
      for (; ; )
        {
          txstatus |= (getreg32(desc) & EIM_RXDESC_STATUSMASK);
          if (desc == priv->c_lastdescend)
            {
              break;
            }

          /* This packet is made up of several descriptors,
           * find next one in chain.
           */

          if (EIM_RXDESC_WRAP_NEXT & getreg32(priv->c_rxcpudesc))
            {
              /* Loop back around to base of descriptor queue. */

              desc = getreg32(EIM_CPU_RXBA) + EIM_RAM_START;
            }
          else
            {
              desc += 2 * sizeof(uint32_t);
            }
        }
    }

  if (txstatus)
    {
      if ((txstatus & EIM_RXDESC_MISS) != 0)
        {
          priv->c_txmiss++;
          ninfo("c_txmiss: %d\n", priv->c_txmiss);
        }

      if ((txstatus & EIM_RXDESC_VLAN) != 0)
        {
          priv->c_txvlan++;
          ninfo("c_txvlan: %d\n", priv->c_txvlan);
        }

      if ((txstatus & EIM_RXDESC_LFRAME) != 0)
        {
          priv->c_txlframe++;
          ninfo("c_txlframe: %d\n", priv->c_txlframe);
        }

      if ((txstatus & EIM_RXDESC_SFRAME) != 0)
        {
          priv->c_txsframe++;
          ninfo("c_txsframe: %d\n", priv->c_txsframe);
        }

      if ((txstatus & EIM_RXDESC_CRCERROR) != 0)
        {
          priv->c_txcrc++;
          ninfo("c_txcrc: %d\n", priv->c_txcrc);
        }

      if ((txstatus & EIM_RXDESC_OVERRUN) != 0)
        {
          priv->c_txoverrun++;
          ninfo("c_txoverrun: %d\n", priv->c_txoverrun);
        }

      if ((txstatus & EIM_RXDESC_OVERRUN) != 0)
        {
          priv->c_txalign++;
          ninfo("c_txalign: %d\n", priv->c_txalign);
        }
    }
}
#endif

/****************************************************************************
 * Function: c5471_txdone
 *
 * Description:
 *   An interrupt was received indicating that the last TX packet(s) is done
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void c5471_txdone(struct c5471_driver_s *priv)
{
  /* If no further xmits are pending, then cancel the TX timeout */

  wd_cancel(&priv->c_txtimeout);

  /* Then poll the network for new XMIT data */

  devif_poll(&priv->c_dev, c5471_txpoll);
}

/****************************************************************************
 * Function: c5471_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:
 *   The network is locked.
 *
 ****************************************************************************/

static void c5471_interrupt_work(void *arg)
{
  struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;

  /* Process pending Ethernet interrupts */

  net_lock();

  /* Get and clear interrupt status bits */

  priv->c_eimstatus = getreg32(EIM_STATUS);

  /* Handle interrupts according to status bit settings */

  /* Check if we received an incoming packet, if so, call c5471_receive() */

  if ((EIM_STATUS_CPU_TX & priv->c_eimstatus) != 0)
    {
      /* An incoming packet has been received by the EIM from the network and
       * the interrupt associated with EIM's CPU TX queue has been asserted.
       * It is the EIM's CPU TX queue that we need to read from to get those
       * packets.  We use this terminology to stay consistent with the Orion
       * documentation.
       */

#ifdef CONFIG_C5471_NET_STATS
      /* Check for RX errors */

      c5471_rxstatus(priv);
#endif

      /* Process the received packet */

      c5471_receive(priv);
    }

  /* Check is a packet transmission just completed.
   * If so, call c5471_txdone
   */

  if ((EIM_STATUS_CPU_RX & priv->c_eimstatus) != 0)
    {
      /* An outgoing packet has been processed by the EIM and the interrupt
       * associated with EIM's CPU RX queue has been asserted. It is EIM's
       * CPU RX queue that we put packets on to send them *out*. TWe use this
       * terminology to stay consistent with the Orion documentation.
       */

#ifdef CONFIG_C5471_NET_STATS
      /* Check for TX errors */

      c5471_txstatus(priv);
#endif

      /* Handle the transmission done event */

      c5471_txdone(priv);
    }

  net_unlock();

  /* Re-enable Ethernet interrupts */

  up_enable_irq(C5471_IRQ_ETHER);
}

/****************************************************************************
 * Function: c5471_interrupt
 *
 * Description:
 *   Hardware interrupt handler
 *
 * Input Parameters:
 *   irq     - Number of the IRQ that generated the interrupt
 *   context - Interrupt register state save info (architecture-specific)
 *
 * Returned Value:
 *   OK on success
 *
 * Assumptions:
 *
 ****************************************************************************/

static int c5471_interrupt(int irq, void *context, void *arg)
{
#if CONFIG_C5471_NET_NINTERFACES == 1
  register struct c5471_driver_s *priv = &g_c5471[0];
#else
# error "Additional logic needed to support multiple interfaces"
#endif

  /* 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(C5471_IRQ_ETHER);

  /* TODO: Determine if a TX transfer just completed */

    {
      /* If a TX transfer just completed, then cancel the TX timeout so
       * there will be no race condition between any subsequent timeout
       * expiration and the deferred interrupt processing.
       */

       wd_cancel(&priv->c_txtimeout);
    }

  /* Schedule to perform the interrupt processing on the worker thread. */

  work_queue(ETHWORK, &priv->c_irqwork, c5471_interrupt_work, priv, 0);
  return OK;
}

/****************************************************************************
 * Function: c5471_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:
 *   The network is locked.
 *
 ****************************************************************************/

static void c5471_txtimeout_work(void *arg)
{
  struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;

  /* Increment statistics */

  net_lock();
#ifdef CONFIG_C5471_NET_STATS
  priv->c_txtimeouts++;
  ninfo("c_txtimeouts: %d\n", priv->c_txtimeouts);
#endif

  /* Then try to restart the hardware */

  c5471_ifdown(&priv->c_dev);
  c5471_ifup(&priv->c_dev);

  /* Then poll the network for new XMIT data */

  devif_poll(&priv->c_dev, c5471_txpoll);
  net_unlock();
}

/****************************************************************************
 * Function: c5471_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:
 *   Global interrupts are disabled by the watchdog logic.
 *
 ****************************************************************************/

static void c5471_txtimeout_expiry(wdparm_t arg)
{
  struct c5471_driver_s *priv = (struct c5471_driver_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(C5471_IRQ_ETHER);

  /* Schedule to perform the TX timeout processing on the worker thread,
   * canceling any pending IRQ work.
   */

  work_queue(ETHWORK, &priv->c_irqwork, c5471_txtimeout_work, priv, 0);
}

/****************************************************************************
 * Function: c5471_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 user has assigned a MAC to the driver
 *
 ****************************************************************************/

static int c5471_ifup(struct net_driver_s *dev)
{
  struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;
  volatile uint32_t clearbits;

  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));

  /* Initialize Ethernet interface */

  c5471_reset(priv);

  /* Assign the MAC to the device */

  c5471_macassign(priv);

  /* Clear pending interrupts by reading the EIM status register */

  clearbits = getreg32(EIM_STATUS);
  UNUSED(clearbits);

  /* Enable interrupts going from EIM Module to Interrupt Module. */

  putreg32(((getreg32(EIM_INTEN) | EIM_INTEN_CPU_TX | EIM_INTEN_CPU_RX)),
           EIM_INTEN);

  /* Next, go on-line. According to the C547X documentation the enables have
   * to occur in this order to insure proper operation; ESM first then ENET.
   */

  putreg32((getreg32(EIM_CTRL) | EIM_CTRL_ESM_EN), EIM_CTRL);      /* enable ESM */
  putreg32((getreg32(ENET0_MODE) | ENET_MODE_ENABLE), ENET0_MODE); /* enable ENET */
  up_mdelay(100);

  /* Enable the Ethernet interrupt */

  priv->c_bifup = true;
  up_enable_irq(C5471_IRQ_ETHER);
  return OK;
}

/****************************************************************************
 * Function: c5471_ifdown
 *
 * Description:
 *   NuttX Callback: Stop the interface.
 *
 * Input Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static int c5471_ifdown(struct net_driver_s *dev)
{
  struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;
  irqstate_t flags;

  ninfo("Stopping\n");

  /* Disable the Ethernet interrupt */

  flags = enter_critical_section();
  up_disable_irq(C5471_IRQ_ETHER);

  /* Disable interrupts going from EIM Module to Interrupt Module. */

  putreg32((getreg32(EIM_INTEN) & ~(EIM_INTEN_CPU_TX | EIM_INTEN_CPU_RX)),
           EIM_INTEN);

  /* Disable ENET */

  putreg32((getreg32(ENET0_MODE) & ~ENET_MODE_ENABLE), ENET0_MODE); /* disable ENET */

  /* Disable ESM */

  putreg32((getreg32(EIM_CTRL) & ~EIM_CTRL_ESM_EN), EIM_CTRL);  /* disable ESM */

  /* Cancel the TX timeout timers */

  wd_cancel(&priv->c_txtimeout);

  /* Reset the device */

  priv->c_bifup = false;
  leave_critical_section(flags);
  return OK;
}

/****************************************************************************
 * Function: c5471_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:
 *   Called on the higher priority worker thread.
 *
 ****************************************************************************/

static void c5471_txavail_work(void *arg)
{
  struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;

  ninfo("Polling\n");

  /* Ignore the notification if the interface is not yet up */

  net_lock();
  if (priv->c_bifup)
    {
      /* Check if the ESM has let go of the RX descriptor giving us access
       * rights to submit another Ethernet frame.
       */

      if ((EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
        {
          /* If so, then poll the network for new XMIT data */

          devif_poll(&priv->c_dev, c5471_txpoll);
        }
    }

  net_unlock();
}

/****************************************************************************
 * Function: c5471_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:
 *   Called in normal user mode
 *
 ****************************************************************************/

static int c5471_txavail(struct net_driver_s *dev)
{
  struct c5471_driver_s *priv = (struct c5471_driver_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->c_pollwork))
    {
      /* Schedule to serialize the poll on the worker thread. */

      work_queue(ETHWORK, &priv->c_pollwork, c5471_txavail_work, priv, 0);
    }

  return OK;
}

/****************************************************************************
 * Function: c5471_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:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

#ifdef CONFIG_NET_MCASTGROUP
static int c5471_addmac(struct net_driver_s *dev, const uint8_t *mac)
{
  struct c5471_driver_s *priv =
    (struct c5471_driver_s *)dev->d_private;

  /* Add the MAC address to the hardware multicast routing table */

#warning "Multicast MAC support not implemented"
  return OK;
}
#endif

/****************************************************************************
 * Function: c5471_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:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

#ifdef CONFIG_NET_MCASTGROUP
static int c5471_rmmac(struct net_driver_s *dev, const uint8_t *mac)
{
  struct c5471_driver_s *priv =
    (struct c5471_driver_s *)dev->d_private;

  /* Add the MAC address to the hardware multicast routing table */

#warning "Multicast MAC support not implemented"
  return OK;
}
#endif

/****************************************************************************
 * Name: c5471_eimreset
 *
 * Description:
 *   The C547x docs states that a module should generally be reset according
 *   to the following algorithm:
 *
 *   1. Put the module in reset.
 *   2. Switch on the module clock.
 *   3. Wait for eight clock cycles.
 *   4. Release the reset.
 *
 ****************************************************************************/

static void c5471_eimreset (struct c5471_driver_s *priv)
{
  /* Stop the EIM module clock */

  putreg32((getreg32(CLKM) | CLKM_EIM_CLK_STOP), CLKM);

  /* Put EIM module in reset */

  putreg32((getreg32(CLKM_RESET) & ~CLKM_RESET_EIM), CLKM_RESET);

  /* Start the EIM module clock */

  putreg32((getreg32(CLKM) & ~CLKM_EIM_CLK_STOP), CLKM);

  /* Assert nRESET to reset the board's PHY0/1 chips */

  putreg32((CLKM_CTL_RST_EXT_RESET | CLKM_CTL_RST_LEAD_RESET),
           CLKM_CTL_RST);
  up_mdelay(2);

  /* Release the peripheral nRESET signal */

  putreg32(CLKM_CTL_RST_LEAD_RESET, CLKM_CTL_RST);

  /* Release EIM module reset */

  putreg32((getreg32(CLKM_RESET) | CLKM_RESET_EIM), CLKM_RESET);

  /* All EIM register should now be in there power-up default states */

  priv->c_lastdescstart = 0;
  priv->c_lastdescend   = 0;
}

/****************************************************************************
 * Name: c5471_eimconfig
 *
 * Description:
 *    Assumes that all registers are currently in the power-up reset state.
 *    This routine then modifies that state to provide our specific ethernet
 *    configuration.
 *
 ****************************************************************************/

static void c5471_eimconfig(struct c5471_driver_s *priv)
{
  volatile uint32_t pbuf;
  volatile uint32_t desc;
  volatile uint32_t val;
  int i;

  desc = EIM_RAM_START;
  pbuf = EIM_RAM_START + 0x6c0;

  /* TX ENET 0 */

  ninfo("TX ENET0 desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
  putreg32((desc & 0x0000ffff), ENET0_TDBA); /* 16-bit offset address */
  for (i = NUM_DESC_TX - 1; i >= 0; i--)
    {
      if (i == 0)
        val = EIM_TXDESC_WRAP_NEXT;
      else
        val = EIM_TXDESC_WRAP_FIRST;

      val |= EIM_TXDESC_OWN_HOST | EIM_TXDESC_INTRE | EIM_TXDESC_PADCRC |
             EIM_PACKET_BYTES;

      putreg32(val, desc);
      desc += sizeof(uint32_t);

      putreg32(pbuf, desc);
      desc += sizeof(uint32_t);

      putreg32(0, pbuf);
      pbuf += EIM_PACKET_BYTES;

      putreg32(0, pbuf);
      pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
    }

  /* RX ENET 0 */

  ninfo("RX ENET0 desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
  putreg32((desc & 0x0000ffff), ENET0_RDBA); /* 16-bit offset address */
  for (i = NUM_DESC_RX - 1; i >= 0; i--)
    {
      if (i == 0)
        val = EIM_RXDESC_WRAP_NEXT;
      else
        val = EIM_RXDESC_WRAP_FIRST;

      val |= EIM_RXDESC_OWN_ENET | EIM_RXDESC_INTRE | EIM_RXDESC_PADCRC |
             EIM_PACKET_BYTES;

      putreg32(val, desc);
      desc += sizeof(uint32_t);

      putreg32(pbuf, desc);
      desc += sizeof(uint32_t);

      putreg32(0, pbuf);
      pbuf += EIM_PACKET_BYTES;

      putreg32(0, pbuf);
      pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
    }

  /* TX CPU */

  ninfo("TX CPU desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
  priv->c_txcpudesc = desc;
  putreg32((desc & 0x0000ffff), EIM_CPU_TXBA); /* 16-bit offset address */
  for (i = NUM_DESC_TX - 1; i >= 0; i--)
    {
      /* Set words 1+2 of the TXDESC */

      if (i == 0)
        val = EIM_TXDESC_WRAP_NEXT;
      else
        val = EIM_TXDESC_WRAP_FIRST;

      val |= EIM_TXDESC_OWN_HOST | EIM_TXDESC_INTRE | EIM_TXDESC_PADCRC |
             EIM_PACKET_BYTES;

      putreg32(val, desc);
      desc += sizeof(uint32_t);

      putreg32(pbuf, desc);
      desc += sizeof(uint32_t);

      putreg32(0, pbuf);
      pbuf += EIM_PACKET_BYTES;

      putreg32(0, pbuf);
      pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
    }

  /* RX CPU */

  ninfo("RX CPU desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
  priv->c_rxcpudesc = desc;
  putreg32((desc & 0x0000ffff), EIM_CPU_RXBA); /* 16-bit offset address */
  for (i = NUM_DESC_RX - 1; i >= 0; i--)
    {
      /* Set words 1+2 of the RXDESC */

      if (i == 0)
        val = EIM_RXDESC_WRAP_NEXT;
      else
        val = EIM_RXDESC_WRAP_FIRST;

      val |= EIM_RXDESC_OWN_ENET | EIM_RXDESC_INTRE | EIM_RXDESC_PADCRC |
             EIM_PACKET_BYTES;

      putreg32(val, desc);
      desc += sizeof(uint32_t);

      putreg32(pbuf, desc);
      desc += sizeof(uint32_t);

      putreg32(0, pbuf);
      pbuf += EIM_PACKET_BYTES;

      putreg32(0, pbuf);
      pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
    }

  ninfo("END desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);

  /* Save the descriptor packet size */

  putreg32(EIM_PACKET_BYTES, EIM_BUFSIZE);

  /* Set the filter mode */

#if 0
  putreg32(EIM_FILTER_UNICAST, EIM_CPU_FILTER);
#elif 0
  putreg32(EIM_FILTER_LOGICAL | EIM_FILTER_UNICAST | EIM_FILTER_MULTICAST |
           EIM_FILTER_BROADCAST, EIM_CPU_FILTER);
#else
  putreg32(EIM_FILTER_UNICAST | EIM_FILTER_MULTICAST | EIM_FILTER_BROADCAST,
           EIM_CPU_FILTER);
#endif

  /* Disable all Ethernet interrupts */

  putreg32(0x00000000, EIM_INTEN);

  /* Setup the EIM control register */

#if 1
  putreg32(EIM_CTRL_ENET0_EN | EIM_CTRL_RXENET0_EN | EIM_CTRL_TXENET0_EN |
           EIM_CTRL_RXCPU_EN | EIM_CTRL_TXCPU_EN, EIM_CTRL);
#else
  putreg32(EIM_CTRL_ENET0_EN | EIM_CTRL_ENET0_FLW | EIM_CTRL_RXENET0_EN |
           EIM_CTRL_TXENET0_EN | EIM_CTRL_RXCPU_EN | EIM_CTRL_TXCPU_EN,
           EIM_CTRL);
#endif

#if 1
  putreg32(0x00000000, EIM_MFVHI);
#else
  putreg32(0x0000ff00, EIM_MFVHI);
#endif

  putreg32(0x00000000, EIM_MFVLO);
  putreg32(0x00000000, EIM_MFMHI);
  putreg32(0x00000000, EIM_MFMLO);
  putreg32(0x00000018, EIM_RXTH);
  putreg32(0x00000000, EIM_CPU_RXREADY);

  /* Setup the ENET0 mode register */

#if 1
  putreg32(ENET_MODE_RJCT_SFE | ENET_MODE_MWIDTH | ENET_MODE_FULLDUPLEX,
           ENET0_MODE);
#else
  putreg32(ENET_MODE_RJCT_SFE | ENET_MODE_MWIDTH | ENET_MODE_HALFDUPLEX,
           ENET0_MODE);
#endif

  putreg32(0x00000000, ENET0_BOFFSEED);
  putreg32(0x00000000, ENET0_FLWPAUSE);
  putreg32(0x00000000, ENET0_FLWCONTROL);
  putreg32(0x00000000, ENET0_VTYPE);

#if 0
  putreg32(ENET_ADR_BROADCAST | ENET_ADR_PROMISCUOUS, ENET0_ADRMODE_EN);
#else
  /* The CPU port is not PROMISCUOUS, it wants a no-promiscuous address
   * match yet the SWITCH receives packets from the PROMISCUOUS ENET0
   * which routes all packets for filter matching at the CPU port which
   * then allows the s/w to see the new incoming packetes that passed
   * the filter. Here we are setting the main SWITCH closest the ether
   * wire.
   */

  putreg32(ENET_ADR_PROMISCUOUS, ENET0_ADRMODE_EN);
#endif

  putreg32(0x00000000, ENET0_DRP);
  up_mdelay(500);
}

/****************************************************************************
 * Name: c5471_reset
 *
 * Description:
 *
 ****************************************************************************/

static void c5471_reset(struct c5471_driver_s *priv)
{
#if defined(CONFIG_C5471_PHY_LU3X31T_T64)
  ninfo("EIM reset\n");
  c5471_eimreset(priv);
#endif
  ninfo("PHY init\n");
  c5471_phyinit();

  ninfo("EIM config\n");
  c5471_eimconfig(priv);
}

/****************************************************************************
 * Name: c5471_macassign
 *
 * Description:
 *    Set the mac address of our CPU ether port so that when the SWITCH
 *    receives packets from the PROMISCUOUS ENET0 it will switch them to the
 *    CPU port and cause a packet arrival event on the Switch's CPU TX queue
 *    when an address match occurs. The CPU port is not PROMISCUOUS and wants
 *    to see only packets specifically addressed to this device.
 *
 ****************************************************************************/

static void c5471_macassign(struct c5471_driver_s *priv)
{
  struct net_driver_s *dev = &priv->c_dev;
  uint8_t *mptr = dev->d_mac.ether.ether_addr_octet;
  register uint32_t tmp;

  ninfo("MAC: %0x:%0x:%0x:%0x:%0x:%0x\n",
        mptr[0], mptr[1], mptr[2], mptr[3], mptr[4], mptr[5]);

  /* Set CPU port MAC address. S/W will only see incoming packets that match
   * this destination address.
   */

  tmp = (((uint32_t)mptr[0]) << 8) | ((uint32_t)mptr[1]);
  putreg32(tmp, EIM_CPU_DAHI);

  tmp = (((uint32_t)mptr[2]) << 24) | (((uint32_t)mptr[3]) << 16) |
        (((uint32_t)mptr[4]) <<  8) |  ((uint32_t)mptr[5]);
  putreg32(tmp, EIM_CPU_DALO);

#if 0
  /* Set the ENET MAC address */

  putreg32(getreg32(EIM_CPU_DAHI), ENET0_PARHI);
  putreg32(getreg32(EIM_CPU_DALO), ENET0_PARLO);
  putreg32(getreg32(EIM_CPU_DAHI), ENET0_LARHI);
  putreg32(getreg32(EIM_CPU_DALO), ENET0_LARLO);

#else
  /* ENET MAC assignment not needed for its PROMISCUOUS mode */

  putreg32(0x00000000, ENET0_PARHI);
  putreg32(0x00000000, ENET0_PARLO);
  putreg32(0x00000000, ENET0_LARHI);
  putreg32(0x00000000, ENET0_LARLO);

#endif
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Function: c5471_initialize
 *
 * Description:
 *   Initialize the Ethernet driver
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   OK on success; Negated errno on failure.
 *
 * Assumptions:
 *
 ****************************************************************************/

/* Initialize the DM90x0 chip and driver */

void arm_netinitialize(void)
{
  /* Attach the IRQ to the driver */

  if (irq_attach(C5471_IRQ_ETHER, c5471_interrupt, NULL))
    {
      /* We could not attach the ISR to the ISR */

      nerr("ERROR: irq_attach() failed\n");
      return;
    }

  /* Initialize the driver structure */

  memset(g_c5471, 0,
    CONFIG_C5471_NET_NINTERFACES * sizeof(struct c5471_driver_s));
  g_c5471[0].c_dev.d_buf     = g_pktbuf[0];     /* Single packet buffer */
  g_c5471[0].c_dev.d_ifup    = c5471_ifup;      /* I/F down callback */
  g_c5471[0].c_dev.d_ifdown  = c5471_ifdown;    /* I/F up (new IP address) callback */
  g_c5471[0].c_dev.d_txavail = c5471_txavail;   /* New TX data callback */
#ifdef CONFIG_NET_MCASTGROUP
  g_c5471[0].c_dev.d_addmac  = c5471_addmac;    /* Add multicast MAC address */
  g_c5471[0].c_dev.d_rmmac   = c5471_rmmac;     /* Remove multicast MAC address */
#endif
  g_c5471[0].c_dev.d_private = g_c5471;         /* Used to recover private state from dev */

  /* Register the device with the OS so that socket IOCTLs can be performed */

  netdev_register(&g_c5471[0].c_dev, NET_LL_ETHERNET);
}

#endif /* CONFIG_NET */
