You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by pk...@apache.org on 2022/06/21 09:06:45 UTC

[incubator-nuttx] 01/04: arch/risc-v/src/litex/litex_emac: add liteeth peripheral driver

This is an automated email from the ASF dual-hosted git repository.

pkarashchenko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git

commit 8f36649aadd6db2a89df5f6ad3feca4cbc72d42f
Author: Richard Tucker <ri...@motec.com.au>
AuthorDate: Mon May 30 11:43:34 2022 +1000

    arch/risc-v/src/litex/litex_emac: add liteeth peripheral driver
    
    See the following for details on the pepheral:
    https://github.com/enjoy-digital/liteeth
---
 arch/risc-v/include/litex/irq.h                  |    3 +-
 arch/risc-v/src/litex/Kconfig                    |   20 +
 arch/risc-v/src/litex/Make.defs                  |    4 +
 arch/risc-v/src/litex/hardware/litex_emac.h      |   87 ++
 arch/risc-v/src/litex/hardware/litex_memorymap.h |   19 +-
 arch/risc-v/src/litex/litex_emac.c               | 1555 ++++++++++++++++++++++
 6 files changed, 1680 insertions(+), 8 deletions(-)

diff --git a/arch/risc-v/include/litex/irq.h b/arch/risc-v/include/litex/irq.h
index 7056877776..24b8d53760 100644
--- a/arch/risc-v/include/litex/irq.h
+++ b/arch/risc-v/include/litex/irq.h
@@ -35,7 +35,8 @@
 
 #define LITEX_IRQ_UART0    (RISCV_IRQ_MEXT + 1)
 #define LITEX_IRQ_TIMER0   (RISCV_IRQ_MEXT + 2)
-#define LITEX_IRQ_SDCARD   (RISCV_IRQ_MEXT + 3)
+#define LITEX_IRQ_ETHMAC   (RISCV_IRQ_MEXT + 3)
+#define LITEX_IRQ_SDCARD   (RISCV_IRQ_MEXT + 4)
 
 /* Total number of IRQs */
 
diff --git a/arch/risc-v/src/litex/Kconfig b/arch/risc-v/src/litex/Kconfig
index b678910bea..1f20f37372 100644
--- a/arch/risc-v/src/litex/Kconfig
+++ b/arch/risc-v/src/litex/Kconfig
@@ -62,4 +62,24 @@ config LITEX_SD4BIT_FREQ
 		Frequency to use for transferring data to/from an SD card using all four data lines.
 
 endif
+
+config LITEX_ETHMAC
+	bool "ETHMAC"
+	default n
+	select ARCH_HAVE_PHY
+	select ARCH_HAVE_NETDEV_STATISTICS
+	select NET
+	select NETDEVICES
+
 endmenu
+
+menu "LITEX EMAC device driver options"
+	depends on LITEX_ETHMAC
+
+config LITEX_EMAC_PHYADDR
+	int "PHY address"
+	default 1
+	---help---
+		The 5-bit address of the PHY on the board.  Default: 1
+
+endmenu # PHY interface
diff --git a/arch/risc-v/src/litex/Make.defs b/arch/risc-v/src/litex/Make.defs
index 450961a8c9..5827658d93 100644
--- a/arch/risc-v/src/litex/Make.defs
+++ b/arch/risc-v/src/litex/Make.defs
@@ -34,3 +34,7 @@ CHIP_ASRCS += litex_cache.S
 ifeq ($(CONFIG_LITEX_SDIO),y)
 CHIP_CSRCS += litex_sdio.c
 endif
+
+ifeq ($(CONFIG_LITEX_ETHMAC),y)
+CHIP_CSRCS += litex_emac.c
+endif
diff --git a/arch/risc-v/src/litex/hardware/litex_emac.h b/arch/risc-v/src/litex/hardware/litex_emac.h
new file mode 100644
index 0000000000..10643c56e2
--- /dev/null
+++ b/arch/risc-v/src/litex/hardware/litex_emac.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+ * arch/risc-v/src/litex/hardware/litex_emac.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_RISCV_SRC_LITEX_HARDWARE_LITEX_EMAC_H
+#define __ARCH_RISCV_SRC_LITEX_HARDWARE_LITEX_EMAC_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include "hardware/litex_memorymap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define ETHMAC_RX_SLOTS   2
+#define ETHMAC_TX_SLOTS   2
+#define ETHMAC_SLOT_SIZE  2048
+
+/* EMAC Register Offsets ****************************************************/
+
+#define LITEX_ETHMAC_SRAM_WRITER_SLOT_OFFSET            0x0000
+#define LITEX_ETHMAC_SRAM_WRITER_LENGTH_OFFSET          0x0004
+#define LITEX_ETHMAC_SRAM_WRITER_ERRORS_OFFSET          0x0008
+#define LITEX_ETHMAC_SRAM_WRITER_EV_STATUS_OFFSET       0x000c
+#define LITEX_ETHMAC_SRAM_WRITER_EV_PENDING_OFFSET      0x0010
+#define LITEX_ETHMAC_SRAM_WRITER_EV_ENABLE_OFFSET       0x0014
+#define LITEX_ETHMAC_SRAM_READER_START_OFFSET           0x0018
+#define LITEX_ETHMAC_SRAM_READER_READY_OFFSET           0x001c
+#define LITEX_ETHMAC_SRAM_READER_LEVEL_OFFSET           0x0020
+#define LITEX_ETHMAC_SRAM_READER_SLOT_OFFSET            0x0024
+#define LITEX_ETHMAC_SRAM_READER_LENGTH_OFFSET          0x0028
+#define LITEX_ETHMAC_SRAM_READER_EV_STATUS_OFFSET       0x002c
+#define LITEX_ETHMAC_SRAM_READER_EV_PENDING_OFFSET      0x0030
+#define LITEX_ETHMAC_SRAM_READER_EV_ENABLE_OFFSET       0x0034
+#define LITEX_ETHMAC_PREAMBLE_CRC_OFFSET                0x0038
+#define LITEX_ETHMAC_RX_DATAPATH_PREAMBLE_ERRORS_OFFSET 0x003c
+#define LITEX_ETHMAC_RX_DATAPATH_CRC_ERRORS_OFFSET      0x0040
+
+#define LITEX_ETHPHY_CRG_RESET_OFFSET                   0x0000
+#define LITEX_ETHPHY_MDIO_W_OFFSET                      0x0004
+#define LITEX_ETHPHY_MDIO_R_OFFSET                      0x0008
+
+/* LITEX_EMAC register addresses ********************************************/
+
+#define LITEX_ETHMAC_SRAM_WRITER_SLOT                   (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_WRITER_SLOT_OFFSET)
+#define LITEX_ETHMAC_SRAM_WRITER_LENGTH                 (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_WRITER_LENGTH_OFFSET)
+#define LITEX_ETHMAC_SRAM_WRITER_ERRORS                 (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_WRITER_ERRORS_OFFSET)
+#define LITEX_ETHMAC_SRAM_WRITER_EV_STATUS              (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_WRITER_EV_STATUS_OFFSET)
+#define LITEX_ETHMAC_SRAM_WRITER_EV_PENDING             (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_WRITER_EV_PENDING_OFFSET)
+#define LITEX_ETHMAC_SRAM_WRITER_EV_ENABLE              (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_WRITER_EV_ENABLE_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_START                  (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_START_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_READY                  (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_READY_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_LEVEL                  (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_LEVEL_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_SLOT                   (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_SLOT_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_LENGTH                 (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_LENGTH_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_EV_STATUS              (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_EV_STATUS_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_EV_PENDING             (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_EV_PENDING_OFFSET)
+#define LITEX_ETHMAC_SRAM_READER_EV_ENABLE              (LITEX_ETHMAC_BASE + LITEX_ETHMAC_SRAM_READER_EV_ENABLE_OFFSET)
+#define LITEX_ETHMAC_PREAMBLE_CRC                       (LITEX_ETHMAC_BASE + LITEX_ETHMAC_PREAMBLE_CRC_OFFSET)
+#define LITEX_ETHMAC_RX_DATAPATH_PREAMBLE_ERRORS        (LITEX_ETHMAC_BASE + LITEX_ETHMAC_RX_DATAPATH_PREAMBLE_ERRORS_OFFSET)
+#define LITEX_ETHMAC_RX_DATAPATH_CRC_ERRORS             (LITEX_ETHMAC_BASE + LITEX_ETHMAC_RX_DATAPATH_CRC_ERRORS_OFFSET)
+
+#define LITEX_ETHPHY_CRG_RESET                          (LITEX_ETHPHY_BASE + LITEX_ETHPHY_CRG_RESET_OFFSET)
+#define LITEX_ETHPHY_MDIO_W                             (LITEX_ETHPHY_BASE + LITEX_ETHPHY_MDIO_W_OFFSET)
+#define LITEX_ETHPHY_MDIO_R                             (LITEX_ETHPHY_BASE + LITEX_ETHPHY_MDIO_R_OFFSET)
+
+#endif /* __ARCH_RISCV_SRC_LITEX_HARDWARE_LITEX_EMAC_H */
diff --git a/arch/risc-v/src/litex/hardware/litex_memorymap.h b/arch/risc-v/src/litex/hardware/litex_memorymap.h
index 0587fa429e..5211c5464b 100644
--- a/arch/risc-v/src/litex/hardware/litex_memorymap.h
+++ b/arch/risc-v/src/litex/hardware/litex_memorymap.h
@@ -32,12 +32,17 @@
  */
 
 #define LITEX_CPUTIMER_BASE     0xf0000800
-#define LITEX_SDBLOCK2MEM_BASE  0xf0002000
-#define LITEX_SDCORE_BASE       0xf0002800
-#define LITEX_SDIRQ_BASE        0xf0003000
-#define LITEX_SDMEM2BLOCK_BASE  0xf0003800
-#define LITEX_SDPHY_BASE        0xf0004000
-#define LITEX_TIMER0_BASE       0xf0005000
-#define LITEX_UART0_BASE        0xf0005800
+#define LITEX_ETHMAC_BASE       0xf0001000
+#define LITEX_ETHPHY_BASE       0xf0001800
+#define LITEX_SDBLOCK2MEM_BASE  0xf0003000
+#define LITEX_SDCORE_BASE       0xf0003800
+#define LITEX_SDIRQ_BASE        0xf0004000
+#define LITEX_SDMEM2BLOCK_BASE  0xf0004800
+#define LITEX_SDPHY_BASE        0xf0005000
+#define LITEX_TIMER0_BASE       0xf0006000
+#define LITEX_UART0_BASE        0xf0006800
+
+#define LITEX_ETHMAC_RXBASE     0x80000000
+#define LITEX_ETHMAC_TXBASE     0x80001000
 
 #endif /* __ARCH_RISCV_SRC_LITEX_HARDWARE_LITEX_MEMORYMAP_H */
diff --git a/arch/risc-v/src/litex/litex_emac.c b/arch/risc-v/src/litex/litex_emac.c
new file mode 100644
index 0000000000..1aef87ab47
--- /dev/null
+++ b/arch/risc-v/src/litex/litex_emac.c
@@ -0,0 +1,1555 @@
+/****************************************************************************
+ * arch/risc-v/src/litex/litex_emac.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>
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/spinlock.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/signal.h>
+#include <nuttx/net/mii.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/ioctl.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#include "riscv_internal.h"
+#include "chip.h"
+#include "hardware/litex_emac.h"
+
+#ifdef CONFIG_LITEX_ETHMAC
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* 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
+#else
+
+  /* Select work queue.  Always use the LP work queue if available.  If not,
+   * then LPWORK will re-direct to the HP work queue.
+   *
+   * 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
+#endif
+
+/* TX timeout - in seconds */
+
+#define LITEX_TXTIMEOUT           (60 * CLK_TCK)
+
+/* Wait for Ethernet link Timeout - in ms */
+
+#define LITEX_WAITLINKTIMEOUT     (5000)
+
+/* PHY Reset Timeout - in ms */
+
+#define LITEX_PHY_RESETTIMEOUT    (20)
+
+/* LITEX MDIO register bit definitions */
+
+#define LITEX_PHY_MDIO_CK         0x01
+#define LITEX_PHY_MDIO_OE         0x02
+#define LITEX_PHY_MDIO_DO         0x04
+
+#define LITEX_PHY_MDIO_DI         0x01
+
+#define LITEX_PHY_MDIO_PREAMBLE   0xffffffff
+#define LITEX_PHY_MDIO_START      0x1
+#define LITEX_PHY_MDIO_READ       0x2
+#define LITEX_PHY_MDIO_WRITE      0x1
+#define LITEX_PHY_MDIO_TURNAROUND 0x2
+
+/* PHY definitions */
+
+#if defined(CONFIG_ETH0_PHY_DP83848C)
+#  define BOARD_PHY_NAME        "DP83848C"
+#  define BOARD_PHYID1          MII_PHYID1_DP83848C
+#  define BOARD_PHYID2          MII_PHYID2_DP83848C
+#  define BOARD_PHY_STATUS      MII_DP83848C_STS
+#  define BOARD_PHY_10BASET(s)  (((s) & MII_DP83848C_PHYSTS_SPEED) != 0)
+#  define BOARD_PHY_100BASET(s) (((s) & MII_DP83848C_PHYSTS_SPEED) == 0)
+#  define BOARD_PHY_ISDUPLEX(s) (((s) & MII_DP83848C_PHYSTS_DUPLEX) != 0)
+#else
+#  error EMAC PHY unrecognized
+#endif
+
+#ifdef CONFIG_NET_DUMPPACKET
+#  define litex_dumppacket(m,a,n) lib_dumpbuffer(m,a,n)
+#else
+#  define litex_dumppacket(m,a,n)
+#endif
+
+/* Helpers ******************************************************************/
+
+/* This is a helper pointer for accessing the contents of the Ethernet
+ * header
+ */
+
+#define BUF ((struct eth_hdr_s *)priv->dev.d_buf)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The litex_emac_s encapsulates all state information for EMAC peripheral */
+
+struct litex_emac_s
+{
+  bool                  ifup;        /* true:ifup false:ifdown */
+  struct wdog_s         txpoll;      /* TX poll timer */
+  struct wdog_s         txtimeout;   /* TX timeout timer */
+  struct work_s         irqwork;     /* For deferring interrupt work to the work queue */
+  struct work_s         pollwork;    /* For deferring poll work to the work queue */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s   dev;         /* Interface understood by the network */
+  uint8_t               phyaddr;     /* PHY address (pre-defined by pins on reset) */
+
+  uint8_t               txslot;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* The driver state singleton */
+
+static struct litex_emac_s g_emac;
+
+/* Global Rx/Tx Buffer */
+
+static uint8_t g_buffer[ETHMAC_SLOT_SIZE]
+               aligned_data(8);
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void litex_txavail_work(void *arg);
+static int  litex_txavail(struct net_driver_s *dev);
+static int  litex_txpoll(struct net_driver_s *dev);
+static void litex_txdone(struct litex_emac_s *priv);
+static void litex_txtimeout_work(void *arg);
+static void litex_txtimeout_expiry(wdparm_t arg);
+static int  litex_transmit(struct litex_emac_s *priv);
+
+static void litex_receive(struct litex_emac_s *priv);
+
+static void litex_emac_interrupt_work(void *arg);
+static int  litex_emac_interrupt(int irq, void *context, void *arg);
+
+static int  litex_linkup(struct litex_emac_s *priv);
+static int  litex_ifup(struct net_driver_s *dev);
+static int  litex_ifdown(struct net_driver_s *dev);
+
+static void litex_phywriteraw(uint32_t word, uint8_t bitcount);
+static uint16_t litex_phyreadraw(void);
+static void litex_phyturnaround(void);
+static int  litex_phyread(struct litex_emac_s *priv, uint8_t phyaddr,
+                          uint8_t regaddr, uint16_t *phyval);
+#ifdef CONFIG_NETDEV_IOCTL
+static int  litex_phywrite(struct litex_emac_s *priv, uint8_t phyaddr,
+                           uint8_t regaddr, uint16_t phyval);
+#endif
+#if defined(CONFIG_DEBUG_NET) && defined(CONFIG_DEBUG_INFO)
+static void litex_phydump(struct litex_emac_s *priv);
+#else
+#  define litex_phydump(priv)
+#endif
+static int  litex_phyfind(struct litex_emac_s *priv, uint8_t phyaddr);
+static int  litex_phyinit(struct litex_emac_s *priv);
+
+#ifdef CONFIG_NETDEV_IOCTL
+static int  litex_ioctl(struct net_driver_s *dev, int cmd,
+                        unsigned long arg);
+#endif
+
+static int  litex_emac_configure(struct litex_emac_s *priv);
+static int  litex_buffer_init(struct litex_emac_s *priv);
+static void litex_ethinitialize(void);
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Function: litex_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 litex_txavail_work(void *arg)
+{
+  struct litex_emac_s *priv = (struct litex_emac_s *)arg;
+
+  /* Ignore the notification if the interface is not yet up */
+
+  net_lock();
+
+  if (priv->ifup)
+    {
+      devif_poll(&priv->dev, litex_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Function: litex_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 litex_txavail(struct net_driver_s *dev)
+{
+  struct litex_emac_s *priv = (struct litex_emac_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->pollwork))
+    {
+      /* Schedule to serialize the poll on the worker thread. */
+
+      work_queue(ETHWORK, &priv->pollwork, litex_txavail_work, priv, 0);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_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:
+ *   May or may not be called from an interrupt handler. In either case,
+ *   global interrupts are disabled, either explicitly or indirectly through
+ *   interrupt handling logic.
+ *
+ ****************************************************************************/
+
+static int litex_txpoll(struct net_driver_s *dev)
+{
+  struct litex_emac_s *priv = (struct litex_emac_s *)dev->d_private;
+
+  DEBUGASSERT(priv->dev.d_buf != NULL);
+
+  /* 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->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->dev.d_flags))
+#endif
+        {
+          arp_out(&priv->dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+#ifdef CONFIG_NET_IPv4
+      else
+#endif
+        {
+          neighbor_out(&priv->dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      if (!devif_loopback(&priv->dev))
+        {
+          /* Send the packet */
+
+          litex_transmit(priv);
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Function: litex_txdone
+ *
+ * Description:
+ *   An interrupt was received indicating that the last TX packet(s) is done
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Global interrupts are disabled by the watchdog logic.
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void litex_txdone(struct litex_emac_s *priv)
+{
+  /* We are here because a transmission completed, so the watchdog can be
+   * canceled.
+   */
+
+  wd_cancel(&priv->txtimeout);
+
+  /* Update statistics */
+
+  NETDEV_TXDONE(&priv->dev);
+
+  /* There should be space for a new TX in any event.  Poll the network for
+   * new XMIT data
+   */
+
+  devif_poll(&priv->dev, litex_txpoll);
+}
+
+/****************************************************************************
+ * Function: litex_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:
+ *
+ ****************************************************************************/
+
+static void litex_txtimeout_work(void *arg)
+{
+  struct litex_emac_s *priv = (struct litex_emac_s *)arg;
+
+  /* Increment statistics and dump debug info */
+
+  net_lock();
+
+  nerr("ERROR: Timeout!\n");
+  nerr("Resetting interface\n");
+
+  NETDEV_TXTIMEOUTS(&priv->dev);
+
+  /* Take the interface down and bring it back up.  That is the most
+   * aggressive hardware reset.
+   */
+
+  litex_ifdown(&priv->dev);
+  litex_ifup(&priv->dev);
+
+  /* Then poll the network for new XMIT data */
+
+  devif_poll(&priv->dev, litex_txpoll);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Function: litex_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 litex_txtimeout_expiry(wdparm_t arg)
+{
+  struct litex_emac_s *priv = (struct litex_emac_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(LITEX_IRQ_ETHMAC);
+
+  /* Schedule to perform the TX timeout processing on the worker thread,
+   * canceling any pending interrupt work.
+   */
+
+  work_queue(ETHWORK, &priv->irqwork, litex_txtimeout_work, priv, 0);
+}
+
+/****************************************************************************
+ * Function: litex_transmit
+ *
+ * Description:
+ *   Start hardware transmission. Called either from the txdone interrupt
+ *   handling or from watchdog based polling.
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   May or may not be called from an interrupt handler.  In either case,
+ *   global interrupts are disabled, either explicitly or indirectly through
+ *   interrupt handling logic.
+ *
+ ****************************************************************************/
+
+static int litex_transmit(struct litex_emac_s *priv)
+{
+  irqstate_t flags;
+
+  if (getreg32(LITEX_ETHMAC_SRAM_READER_READY) == 0)
+    {
+      nerr("Tx Busy\n");
+      return -EBUSY;
+    }
+
+  ninfo("Sending packet, length: %d slot: %d\n",
+         priv->dev.d_len, priv->txslot);
+  litex_dumppacket("Tx Packet", g_buffer, priv->dev.d_len);
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(&priv->dev);
+
+  /* Copy frame to the hardware buffer */
+
+  memcpy((void *)(LITEX_ETHMAC_TXBASE + (priv->txslot * ETHMAC_SLOT_SIZE)),
+         g_buffer, priv->dev.d_len);
+
+  /* Make the following operations atomic */
+
+  flags = spin_lock_irqsave(NULL);
+
+  /* Now start transmission */
+
+  putreg8(priv->txslot, LITEX_ETHMAC_SRAM_READER_SLOT);
+  putreg16(priv->dev.d_len, LITEX_ETHMAC_SRAM_READER_LENGTH);
+  putreg8(0x01, LITEX_ETHMAC_SRAM_READER_START);
+
+  /* Increment the hardware TX slot -> wrap around */
+
+  priv->txslot = (priv->txslot + 1) % ETHMAC_TX_SLOTS;
+
+  /* Setup the TX timeout watchdog */
+
+  wd_start(&priv->txtimeout, LITEX_TXTIMEOUT,
+           litex_txtimeout_expiry, (wdparm_t)priv);
+
+  spin_unlock_irqrestore(NULL, flags);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_receive
+ *
+ * Description:
+ *   An interrupt was received indicating the availability of one or more
+ *   new RX packets in FIFO memory.
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Global interrupts are disabled by interrupt handling logic.
+ *
+ ****************************************************************************/
+
+static void litex_receive(struct litex_emac_s *priv)
+{
+  /* Update statistics */
+
+  NETDEV_RXPACKETS(&priv->dev);
+
+  priv->dev.d_len = getreg16(LITEX_ETHMAC_SRAM_WRITER_LENGTH);
+
+  if (priv->dev.d_len == 0 || priv->dev.d_len > ETHMAC_SLOT_SIZE)
+    {
+      NETDEV_RXDROPPED(&priv->dev);
+      return;
+    }
+
+  /* Copy frame from hardware buffer at the slot which activated
+   * this reception
+   */
+
+  const uint8_t rxslot = getreg8(LITEX_ETHMAC_SRAM_WRITER_SLOT);
+  memcpy(g_buffer,
+         (void *)(LITEX_ETHMAC_RXBASE + (rxslot * ETHMAC_SLOT_SIZE)),
+         priv->dev.d_len);
+  litex_dumppacket("Rx Packet", g_buffer, priv->dev.d_len);
+
+#ifdef CONFIG_NET_PKT
+  /* When packet sockets are enabled, feed the frame into the tap */
+
+  pkt_input(&priv->dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+  /* Check for an IPv4 packet */
+
+  if (BUF->type == HTONS(ETHTYPE_IP))
+    {
+      ninfo("IPv4 frame\n");
+      NETDEV_RXIPV4(&priv->dev);
+
+      /* Handle ARP on input then give the IPv4 packet to the network
+       * layer
+       */
+
+      arp_ipin(&priv->dev);
+      ipv4_input(&priv->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->dev.d_len > 0)
+        {
+          /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv6
+          if (IFF_IS_IPv4(priv->dev.d_flags))
+#endif
+            {
+              arp_out(&priv->dev);
+            }
+#ifdef CONFIG_NET_IPv6
+          else
+            {
+              neighbor_out(&priv->dev);
+            }
+#endif
+
+          /* And send the packet */
+
+          litex_transmit(priv);
+        }
+    }
+  else
+#endif
+#ifdef CONFIG_NET_IPv6
+  if (BUF->type == HTONS(ETHTYPE_IP6))
+    {
+      ninfo("IPv6 frame\n");
+      NETDEV_RXIPV6(&priv->dev);
+
+      /* Give the IPv6 packet to the network layer */
+
+      ipv6_input(&priv->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->dev.d_len > 0)
+        {
+          /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+          if (IFF_IS_IPv4(priv->dev.d_flags))
+            {
+              arp_out(&priv->dev);
+            }
+          else
+#endif
+#ifdef CONFIG_NET_IPv6
+            {
+              neighbor_out(&priv->dev);
+            }
+#endif
+
+          /* And send the packet */
+
+          litex_transmit(priv);
+        }
+    }
+  else
+#endif
+#ifdef CONFIG_NET_ARP
+  /* Check for an ARP packet */
+
+  if (BUF->type == HTONS(ETHTYPE_ARP))
+    {
+      ninfo("ARP frame\n");
+      NETDEV_RXARP(&priv->dev);
+      arp_arpin(&priv->dev);
+
+      /* If the above function invocation resulted in data that should
+       * be sent out on the network, the field d_len will set to a
+       * value > 0.
+       */
+
+      if (priv->dev.d_len > 0)
+        {
+          litex_transmit(priv);
+        }
+    }
+#endif
+  else
+    {
+      NETDEV_RXDROPPED(&priv->dev);
+    }
+}
+
+/****************************************************************************
+ * Function: litex_emac_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.
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void litex_emac_interrupt_work(void *arg)
+{
+  struct litex_emac_s *priv = (struct litex_emac_s *)arg;
+
+  /* Process pending Ethernet interrupts */
+
+  net_lock();
+
+  /* Rx Available */
+
+  if (getreg32(LITEX_ETHMAC_SRAM_WRITER_EV_PENDING) & 0x01)
+    {
+      litex_receive(priv);
+
+      /* ACK the pending flag AFTER receiving and processing data.
+       * This transitions the receive slot in hardware.
+       */
+
+      putreg8(0x01, LITEX_ETHMAC_SRAM_WRITER_EV_PENDING);
+    }
+
+  /* Tx Done */
+
+  if (getreg32(LITEX_ETHMAC_SRAM_READER_EV_PENDING) & 0x01)
+    {
+      litex_txdone(priv);
+
+      /* ACK the pending flag AFTER processing Tx Done */
+
+      putreg8(0x01, LITEX_ETHMAC_SRAM_READER_EV_PENDING);
+    }
+
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  up_enable_irq(LITEX_IRQ_ETHMAC);
+}
+
+/****************************************************************************
+ * Function: litex_emac_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 litex_emac_interrupt(int irq, void *context, void *arg)
+{
+  struct litex_emac_s *priv = &g_emac;
+
+  /* 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(LITEX_IRQ_ETHMAC);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &priv->irqwork, litex_emac_interrupt_work, priv, 0);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_linkup
+ *
+ * Description:
+ *   Check if the link is up
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   true: The link is up
+ *
+ ****************************************************************************/
+
+static int litex_linkup(struct litex_emac_s *priv)
+{
+  int ret;
+  uint16_t i;
+  uint16_t msr;
+
+  for (i = 0; i < LITEX_WAITLINKTIMEOUT; ++i)
+    {
+      nxsig_usleep(1000);
+
+      ret = litex_phyread(priv, priv->phyaddr, MII_MSR, &msr);
+      if (ret < 0)
+        {
+          nerr("ERROR: Failed to read MSR: %d\n", ret);
+          return ret;
+        }
+
+      if ((msr & MII_MSR_LINKSTATUS) != 0)
+        {
+          return OK;
+        }
+    }
+
+  nerr("ERROR: Timeout to wait for PHY LINK UP\n");
+  return -ETIMEDOUT;
+}
+
+/****************************************************************************
+ * Function: litex_ifup
+ *
+ * Description:
+ *   NuttX Callback: Bring up the EMAC interface when an IP address is
+ *   provided
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int litex_ifup(struct net_driver_s *dev)
+{
+  struct litex_emac_s *priv = (struct litex_emac_s *)dev->d_private;
+  int ret;
+
+  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));
+
+  /* Configure the EMAC interface for normal operation. */
+
+  ninfo("Initialize the EMAC\n");
+  litex_emac_configure(priv);
+
+  /* Initialize for PHY access */
+
+  ret = litex_phyinit(priv);
+  if (ret < 0)
+    {
+      nerr("ERROR: litex_phyinit failed: %d\n", ret);
+      return ret;
+    }
+
+  ninfo("Wait for link up\n");
+
+  ret = litex_linkup(priv);
+  if (ret != 0)
+    {
+      nerr("ERROR: Failed to wait LINK UP error=%d\n", ret);
+      return ret;
+    }
+
+  ninfo("Link detected\n");
+
+  /* Enable normal MAC operation */
+
+  ninfo("Enable normal operation\n");
+
+  /* Enable the EMAC interrupt */
+
+  priv->ifup = true;
+  up_enable_irq(LITEX_IRQ_ETHMAC);
+
+  litex_phydump(priv);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_ifdown
+ *
+ * Description:
+ *   NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int litex_ifdown(struct net_driver_s *dev)
+{
+  struct litex_emac_s *priv = (struct litex_emac_s *)dev->d_private;
+  irqstate_t flags;
+
+  ninfo("Taking the network down\n");
+
+  /* Disable the EMAC interrupt */
+
+  flags = enter_critical_section();
+  up_disable_irq(LITEX_IRQ_ETHMAC);
+
+  /* Cancel the TX poll timer and TX timeout timers */
+
+  wd_cancel(&priv->txpoll);
+  wd_cancel(&priv->txtimeout);
+
+  /* Hold the PHY device in reset and mark the interface as "down" */
+
+  putreg32(1, LITEX_ETHPHY_CRG_RESET);
+  priv->ifup = false;
+  leave_critical_section(flags);
+
+  litex_phydump(priv);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_phywriteraw
+ *
+ * Description:
+ * Write 1 word to the MDIO interface.
+ * The hardware requires the data to be bitbashed in.
+ *
+ * Input Parameters:
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static void litex_phywriteraw(uint32_t word, uint8_t bitcount)
+{
+  word <<= 32 - bitcount;
+
+  while (bitcount > 0)
+    {
+      if (word & 0x80000000)
+        {
+          putreg32(LITEX_PHY_MDIO_DO | LITEX_PHY_MDIO_OE,
+                   LITEX_ETHPHY_MDIO_W);
+          up_udelay(1);
+          putreg32(LITEX_PHY_MDIO_DO | LITEX_PHY_MDIO_OE | LITEX_PHY_MDIO_CK,
+                   LITEX_ETHPHY_MDIO_W);
+          up_udelay(1);
+          putreg32(LITEX_PHY_MDIO_DO | LITEX_PHY_MDIO_OE,
+                   LITEX_ETHPHY_MDIO_W);
+        }
+      else
+        {
+          putreg32(LITEX_PHY_MDIO_OE,
+                   LITEX_ETHPHY_MDIO_W);
+          up_udelay(1);
+          putreg32(LITEX_PHY_MDIO_OE | LITEX_PHY_MDIO_CK,
+                   LITEX_ETHPHY_MDIO_W);
+          up_udelay(1);
+          putreg32(LITEX_PHY_MDIO_OE,
+                   LITEX_ETHPHY_MDIO_W);
+        }
+
+      word <<= 1;
+      bitcount--;
+    }
+}
+
+/****************************************************************************
+ * Function: litex_phyreadraw
+ *
+ * Description:
+ * Read 1 word from the MDIO interface.
+ * The hardware requires this interface to be bitbashed to get the data out.
+ *
+ * Input Parameters:
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static uint16_t litex_phyreadraw(void)
+{
+  uint16_t word;
+  uint8_t i;
+
+  word = 0;
+
+  for (i = 0; i < 16; ++i)
+    {
+      word <<= 1;
+
+      if (getreg32(LITEX_ETHPHY_MDIO_R) & LITEX_PHY_MDIO_DI)
+        {
+          word |= 1;
+        }
+
+      putreg32(LITEX_PHY_MDIO_CK, LITEX_ETHPHY_MDIO_W);
+      up_udelay(1);
+      putreg32(0x00, LITEX_ETHPHY_MDIO_W);
+      up_udelay(1);
+    }
+
+  return word;
+}
+
+/****************************************************************************
+ * Function: litex_phyturnaround
+ *
+ * Description:
+ *
+ * Input Parameters:
+ *   None
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static void litex_phyturnaround(void)
+{
+  int i;
+
+  for (i = 0; i < 2; ++i)
+    {
+      up_udelay(1);
+      putreg32(LITEX_PHY_MDIO_CK, LITEX_ETHPHY_MDIO_W);
+      up_udelay(1);
+      putreg32(0x00, LITEX_ETHPHY_MDIO_W);
+    }
+}
+
+/****************************************************************************
+ * Function: litex_phyread
+ *
+ * Description:
+ *  Read a PHY register.
+ *
+ * Input Parameters:
+ *   priv    - Reference to the private driver state structure.
+ *   phyaddr - The PHY device address.
+ *   regaddr - The PHY register address.
+ *   phyval  - The location to return the 16-bit PHY register value.
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int litex_phyread(struct litex_emac_s *priv, uint8_t phyaddr,
+                         uint8_t regaddr, uint16_t *phyval)
+{
+  putreg32(LITEX_PHY_MDIO_OE, LITEX_ETHPHY_MDIO_W);
+  litex_phywriteraw(LITEX_PHY_MDIO_PREAMBLE, 32);
+  litex_phywriteraw(LITEX_PHY_MDIO_START, 2);
+  litex_phywriteraw(LITEX_PHY_MDIO_READ, 2);
+  litex_phywriteraw(phyaddr, 5);
+  litex_phywriteraw(regaddr, 5);
+  litex_phyturnaround();
+  *phyval = litex_phyreadraw();
+  litex_phyturnaround();
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_phywrite
+ *
+ * Description:
+ *  Write to a PHY register.
+ *
+ * Input Parameters:
+ *   priv    - Reference to the private driver state structure.
+ *   phyaddr - The PHY device address.
+ *   regaddr - The PHY register address.
+ *   phyval  - The 16-bit value to write to the PHY register.
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_IOCTL
+static int litex_phywrite(struct litex_emac_s *priv, uint8_t phyaddr,
+                        uint8_t regaddr, uint16_t phyval)
+{
+  putreg32(LITEX_PHY_MDIO_OE, LITEX_ETHPHY_MDIO_W);
+  litex_phywriteraw(LITEX_PHY_MDIO_PREAMBLE, 32);
+  litex_phywriteraw(LITEX_PHY_MDIO_START, 2);
+  litex_phywriteraw(LITEX_PHY_MDIO_WRITE, 2);
+  litex_phywriteraw(phyaddr, 5);
+  litex_phywriteraw(regaddr, 5);
+  litex_phywriteraw(LITEX_PHY_MDIO_TURNAROUND, 2);
+  litex_phywriteraw(phyval, 16);
+  litex_phyturnaround();
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Function: litex_phydump
+ *
+ * Description:
+ *   Dump the contents of PHY registers
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_DEBUG_NET) && defined(CONFIG_DEBUG_INFO)
+static void litex_phydump(struct litex_emac_s *priv)
+{
+  uint16_t phyval;
+
+  ninfo("%s: MII Registers (Address %02x)\n", BOARD_PHY_NAME, priv->phyaddr);
+
+  litex_phyread(priv, priv->phyaddr, MII_MCR, &phyval);
+  ninfo("  MCR:       %04x\n", phyval);
+  litex_phyread(priv, priv->phyaddr, MII_MSR, &phyval);
+  ninfo("  MSR:       %04x\n", phyval);
+  litex_phyread(priv, priv->phyaddr, MII_ADVERTISE, &phyval);
+  ninfo("  ADVERTISE: %04x\n", phyval);
+  litex_phyread(priv, priv->phyaddr, MII_LPA, &phyval);
+  ninfo("  LPA:       %04x\n", phyval);
+  litex_phyread(priv, priv->phyaddr, MII_EXPANSION, &phyval);
+  ninfo("  EXPANSION: %04x\n", phyval);
+  litex_phyread(priv, priv->phyaddr, MII_DP83848C_10BTSCR, &phyval);
+  ninfo("  10BTCR:    %04x\n", phyval);
+  litex_phyread(priv, priv->phyaddr, BOARD_PHY_STATUS, &phyval);
+  ninfo("  PHYSR:     %04x\n", phyval);
+
+  if (phyval == 0xffff)
+    {
+      return;
+    }
+
+  if (BOARD_PHY_ISDUPLEX(phyval))
+    {
+      ninfo("%s: Full duplex\n",  BOARD_PHY_NAME);
+    }
+  else
+    {
+      ninfo("%s: Half duplex\n",  BOARD_PHY_NAME);
+    }
+
+  if (BOARD_PHY_10BASET(phyval))
+    {
+      /* 10 Mbps */
+
+      ninfo("%s: 10 Base-T\n",  BOARD_PHY_NAME);
+    }
+  else if (BOARD_PHY_100BASET(phyval))
+    {
+      /* 100 Mbps */
+
+      ninfo("%s: 100 Base-T\n",  BOARD_PHY_NAME);
+    }
+  else
+    {
+      /* This might happen if Autonegotiation did not complete(?) */
+
+      nerr("ERROR: Neither 10- nor 100-BaseT reported: PHY STATUS=%04x\n",
+          phyval);
+    }
+}
+#endif
+
+/****************************************************************************
+ * Function: litex_phyfind
+ *
+ * Description:
+ *  Verify the PHY address.
+ *
+ * Input Parameters:
+ *   priv    - Reference to the private driver state structure
+ *   phyaddr - MDIO address to try and find confgiured PHY IC
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int litex_phyfind(struct litex_emac_s *priv, uint8_t phyaddr)
+{
+  uint16_t phyval[2];
+  uint32_t oui;
+  uint16_t model;
+  uint16_t revision;
+
+  int ret;
+
+  ninfo("%s: Find a valid PHY at address: %d\n",
+        BOARD_PHY_NAME, phyaddr);
+
+  /* Read PHYID1 */
+
+  ret = litex_phyread(priv, phyaddr, MII_PHYID1, &phyval[0]);
+
+  /* Verify PHYID1 */
+
+  if (ret != OK)
+    {
+      nerr("ERROR: litex_phyread failed for PHY address %02x: %d\n",
+           phyaddr, ret);
+      return -EIO;
+    }
+
+  if (phyval[0] != BOARD_PHYID1)
+    {
+      nerr("ERROR: unexpected PHYID - Expected: %02x Got: %02x\n",
+           BOARD_PHYID1, phyval[0]);
+      return -ESRCH;
+    }
+
+  /* Read PHYID2 */
+
+  ret = litex_phyread(priv, phyaddr, MII_PHYID2, &phyval[1]);
+
+  /* Verify PHYID2 */
+
+  if (ret != OK)
+    {
+      nerr("ERROR: litex_phyread failed for PHY address %02x: %d\n",
+           phyaddr, ret);
+      return -EIO;
+    }
+
+  if (phyval[1] != BOARD_PHYID2)
+    {
+      nerr("ERROR: unexpected PHYID - Expected: %02x Got: %02x\n",
+           BOARD_PHYID2, phyval[1]);
+      return -ESRCH;
+    }
+
+  oui = ((phyval[0] << 16) | (phyval[1] & 0xfc00)) >> 10;
+  model = (phyval[1] & 0x03f0) >> 4;
+  revision = (phyval[1] & 0x000f);
+
+  ninfo("%s: PHY Found - OUI: 0x%04" PRIx32 "MODEL: %u REV: %u\n",
+        BOARD_PHY_NAME, oui, model, revision);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_phyinit
+ *
+ * Description:
+ *  Configure the PHY
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ ****************************************************************************/
+
+static int litex_phyinit(struct litex_emac_s *priv)
+{
+  int ret;
+
+  /* Reset PHY */
+
+  putreg32(1, LITEX_ETHPHY_CRG_RESET);
+  nxsig_usleep(LITEX_PHY_RESETTIMEOUT);
+  putreg32(0, LITEX_ETHPHY_CRG_RESET);
+  nxsig_usleep(LITEX_PHY_RESETTIMEOUT);
+
+  /* Check the PHY responds at configured address */
+
+  priv->phyaddr = CONFIG_LITEX_EMAC_PHYADDR;
+
+  ninfo("PHY ADDR: %d\n", priv->phyaddr);
+
+  ret = litex_phyfind(priv, priv->phyaddr);
+  if (ret < 0)
+    {
+      nerr("ERROR: litex_phyfind failed: %d\n", ret);
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_ioctl
+ *
+ * Description:
+ *   PHY ioctl command handler
+ *
+ * Input Parameters:
+ *   dev  - Reference to the NuttX driver state structure
+ *   cmd  - ioctl command
+ *   arg  - Argument accompanying the command
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_IOCTL
+static int litex_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg)
+{
+#ifdef CONFIG_NETDEV_PHY_IOCTL
+  struct litex_emac_s *priv = (struct litex_emac_s *)dev->d_private;
+#endif
+  int ret;
+
+  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 = priv->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);
+
+          /* Read from the requested register */
+
+          ret = litex_phyread(priv, req->phy_id,
+                              req->reg_num, &req->val_out);
+        }
+        break;
+
+      case SIOCSMIIREG: /* Set register in MII PHY */
+        {
+          struct mii_ioctl_data_s *req =
+            (struct mii_ioctl_data_s *)((uintptr_t)arg);
+
+          /* Write to the requested register */
+
+          ret = litex_phywrite(priv, req->phy_id,
+                               req->reg_num, req->val_in);
+        }
+        break;
+#endif /* ifdef CONFIG_NETDEV_PHY_IOCTL */
+
+      default:
+        ret = -ENOTTY;
+        break;
+    }
+
+  return ret;
+}
+#endif /* CONFIG_NETDEV_IOCTL */
+
+/****************************************************************************
+ * Function: litex_emac_configure
+ *
+ * Description:
+ *  Configure the EMAC interface for normal operation.
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int litex_emac_configure(struct litex_emac_s *priv)
+{
+  /* Clear pending events */
+
+  putreg8(0x01, LITEX_ETHMAC_SRAM_READER_EV_PENDING);
+  putreg8(0x01, LITEX_ETHMAC_SRAM_WRITER_EV_PENDING);
+
+  /* Enable Tx and Rx interrupts */
+
+  putreg8(0x01, LITEX_ETHMAC_SRAM_READER_EV_ENABLE);
+  putreg8(0x01, LITEX_ETHMAC_SRAM_WRITER_EV_ENABLE);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_buffer_init
+ *
+ * Description:
+ *   Allocate aligned TX and RX descriptors and buffers.  For the case of
+ *   pre-allocated structures, the function degenerates to a few assignments.
+ *
+ * Input Parameters:
+ *   priv - Reference to the private driver state structure
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *   Called very early in the initialization sequence.
+ *
+ ****************************************************************************/
+
+static int litex_buffer_init(struct litex_emac_s *priv)
+{
+  priv->txslot = 0;
+  priv->dev.d_buf = g_buffer;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: litex_ethinitialize
+ *
+ * Description:
+ *   Initialize the Ethernet driver for one interface.
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static void litex_ethinitialize(void)
+{
+  struct litex_emac_s *priv = &g_emac;
+  int ret;
+
+  /* Initialize the driver structure */
+
+  memset(priv, 0, sizeof(struct litex_emac_s));
+  priv->dev.d_ifup    = litex_ifup;       /* I/F up (new IP address) callback */
+  priv->dev.d_ifdown  = litex_ifdown;     /* I/F down callback */
+  priv->dev.d_txavail = litex_txavail;    /* New TX data callback */
+#ifdef CONFIG_NETDEV_IOCTL
+  priv->dev.d_ioctl   = litex_ioctl;      /* Support PHY ioctl() calls */
+#endif
+  priv->dev.d_private = &g_emac;          /* Used to recover private state from dev */
+
+  /* Init buffers */
+
+  ret = litex_buffer_init(priv);
+  if (ret < 0)
+    {
+      nerr("ERROR: litex_buffer_initialize failed: %d\n", ret);
+      return;
+    }
+
+  /* Attach the IRQ to the driver.  It will not be enabled at the AIC until
+   * the interface is in the 'up' state.
+   */
+
+  ret = irq_attach(LITEX_IRQ_ETHMAC, litex_emac_interrupt, NULL);
+  if (ret < 0)
+    {
+      nerr("ERROR: Failed to attach the handler to the IRQ%d\n",
+           LITEX_IRQ_ETHMAC);
+    }
+
+  /* Put the interface in the down state */
+
+  ret = litex_ifdown(&priv->dev);
+  if (ret < 0)
+    {
+      nerr("ERROR: Failed to put the interface in the down state: %d\n",
+           ret);
+    }
+
+  /* Register the device with the OS so that socket IOCTLs can be performed */
+
+  ret = netdev_register(&priv->dev, NET_LL_ETHERNET);
+  if (ret >= 0)
+    {
+      return;
+    }
+
+  nerr("ERROR: netdev_register() failed: %d\n", ret);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Function: riscv_netinitialize
+ *
+ * Description:
+ *   This is the "standard" network initialization logic called from the
+ *   low-level initialization logic in riscv_initialize.c.
+ *
+ * Parameters:
+ *   None.
+ *
+ * Returned Value:
+ *   None.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+#if !defined(CONFIG_NETDEV_LATEINIT)
+void riscv_netinitialize(void)
+{
+  litex_ethinitialize();
+}
+#endif
+
+#endif //CONFIG_LITEX_ETHMAC