You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by GitBox <gi...@apache.org> on 2022/06/01 23:06:59 UTC

[GitHub] [incubator-nuttx] acassis commented on a diff in pull request #6353: Add driver for WIZnet W5500 Ethernet controller

acassis commented on code in PR #6353:
URL: https://github.com/apache/incubator-nuttx/pull/6353#discussion_r887359481


##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */

Review Comment:
   ```suggestion
     /* Check whether socket 0 is open in MACRAW mode. */



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get

Review Comment:
   ```suggestion
     /* Verify that the hardware is ready to send another packet. If we get



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform

Review Comment:
   ```suggestion
      /* Check if there is room in the send another TX packet. We cannot perform



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &priv->sk_pollwork, w5500_poll_work, priv, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_ifup
+ *
+ * Description:
+ *   NuttX Callback: Bring up the Ethernet interface when an IP address is
+ *   provided
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifup(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  int ret;
+
+#ifdef CONFIG_NET_IPv4
+  ninfo("Bringing up: %d.%d.%d.%d\n",
+        (int)dev->d_ipaddr & 0xff,
+        (int)(dev->d_ipaddr >> 8) & 0xff,
+        (int)(dev->d_ipaddr >> 16) & 0xff,
+        (int)dev->d_ipaddr >> 24);
+#endif
+#ifdef CONFIG_NET_IPv6
+  ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+        dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
+        dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
+        dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
+#endif
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  ret = w5500_unfence(self);
+
+  if (ret != OK)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_NET_ICMPv6
+  /* Set up IPv6 multicast address filtering */
+
+  w5500_ipv6multicast(self);
+#endif
+
+  /* Set and activate a timer process */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  /* Enable the Ethernet interrupt */
+
+  self->sk_bifup = true;
+  self->lower->enable(self->lower, true);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_ifdown
+ *
+ * Description:
+ *   NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifdown(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  irqstate_t flags;
+
+  /* Disable the Ethernet interrupt */
+
+  flags = enter_critical_section();
+  self->lower->enable(self->lower, false);
+
+  /* Cancel the TX poll timer and TX timeout timers */
+
+  wd_cancel(&self->sk_txpoll);
+  wd_cancel(&self->sk_txtimeout);
+
+  /* Put the EMAC in its reset, non-operational state.  This should be

Review Comment:
   ```suggestion
      /* Put the EMAC in its reset, non-operational state. This should be



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but

Review Comment:
   ```suggestion
          * this device. If so, just loop the packet back into the network but



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Disable further Ethernet interrupts.  This will prevent some race
+   * conditions with interrupt work.  There is still a potential race

Review Comment:
   ```suggestion
      * conditions with interrupt work. There is still a potential race



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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

Review Comment:
   ```suggestion
              * sent out on the network, the field d_len will set to a value



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a

Review Comment:
   ```suggestion
           * data. Hmmm.. might be bug here. Does this mean if there is a



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;

Review Comment:
   Shouldn't be better return -ENOMEM? This way it could be easier to handle the right kind of "error"



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.

Review Comment:
   ```suggestion
     *   Periodic timer handler. Called from the timer interrupt handler.



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are

Review Comment:
   ```suggestion
     /* Disable further Ethernet interrupts. Because Ethernet interrupts are



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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.

Review Comment:
   ```suggestion
    *   The last TX never completed. Reset the hardware and start again.



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case

Review Comment:
   ```suggestion
    *   may return with an outgoing packet. This function checks for that case



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &priv->sk_pollwork, w5500_poll_work, priv, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_ifup
+ *
+ * Description:
+ *   NuttX Callback: Bring up the Ethernet interface when an IP address is
+ *   provided
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifup(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  int ret;
+
+#ifdef CONFIG_NET_IPv4
+  ninfo("Bringing up: %d.%d.%d.%d\n",
+        (int)dev->d_ipaddr & 0xff,
+        (int)(dev->d_ipaddr >> 8) & 0xff,
+        (int)(dev->d_ipaddr >> 16) & 0xff,
+        (int)dev->d_ipaddr >> 24);
+#endif
+#ifdef CONFIG_NET_IPv6
+  ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+        dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
+        dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
+        dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
+#endif
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  ret = w5500_unfence(self);
+
+  if (ret != OK)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_NET_ICMPv6
+  /* Set up IPv6 multicast address filtering */
+
+  w5500_ipv6multicast(self);
+#endif
+
+  /* Set and activate a timer process */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  /* Enable the Ethernet interrupt */
+
+  self->sk_bifup = true;
+  self->lower->enable(self->lower, true);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_ifdown
+ *
+ * Description:
+ *   NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifdown(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  irqstate_t flags;
+
+  /* Disable the Ethernet interrupt */
+
+  flags = enter_critical_section();
+  self->lower->enable(self->lower, false);
+
+  /* Cancel the TX poll timer and TX timeout timers */
+
+  wd_cancel(&self->sk_txpoll);
+  wd_cancel(&self->sk_txtimeout);
+
+  /* Put the EMAC in its reset, non-operational state.  This should be
+   * a known configuration that will guarantee the w5500_ifup() always
+   * successfully brings the interface back up.
+   */
+
+  w5500_fence(self);
+
+  /* Mark the device "down" */
+
+  self->sk_bifup = false;
+  leave_critical_section(flags);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_txavail_work
+ *
+ * Description:
+ *   Perform an out-of-cycle poll on the worker thread.
+ *
+ * Input Parameters:
+ *   arg - Reference to the NuttX driver state structure (cast to void*)
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_txavail_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Ignore the notification if the interface is not yet up */
+
+  if (priv->sk_bifup)
+    {
+      /* Check if there is room in the hardware to hold another packet. */
+
+      /* If so, then poll the network for new XMIT data */
+
+      devif_timer(&priv->sk_dev, 0, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txavail
+ *
+ * Description:
+ *   Driver callback invoked when new TX data is available.  This is a
+ *   stimulus perform an out-of-cycle poll and, thereby, reduce the TX
+ *   latency.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txavail(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* Is our single work structure available?  It may not be if there are

Review Comment:
   ```suggestion
      /* Is our single work structure available? It may not be if there are



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.

Review Comment:
   ```suggestion
    *   Our TX watchdog timed out. Called from the timer interrupt handler.



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local

Review Comment:
   ```suggestion
     *   Runs in the context of a the timer interrupt handler. Local



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Disable further Ethernet interrupts.  This will prevent some race

Review Comment:
   ```suggestion
     /* Disable further Ethernet interrupts. This will prevent some race



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &priv->sk_pollwork, w5500_poll_work, priv, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_ifup
+ *
+ * Description:
+ *   NuttX Callback: Bring up the Ethernet interface when an IP address is
+ *   provided
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifup(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  int ret;
+
+#ifdef CONFIG_NET_IPv4
+  ninfo("Bringing up: %d.%d.%d.%d\n",
+        (int)dev->d_ipaddr & 0xff,
+        (int)(dev->d_ipaddr >> 8) & 0xff,
+        (int)(dev->d_ipaddr >> 16) & 0xff,
+        (int)dev->d_ipaddr >> 24);
+#endif
+#ifdef CONFIG_NET_IPv6
+  ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+        dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
+        dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
+        dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
+#endif
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  ret = w5500_unfence(self);
+
+  if (ret != OK)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_NET_ICMPv6
+  /* Set up IPv6 multicast address filtering */
+
+  w5500_ipv6multicast(self);
+#endif
+
+  /* Set and activate a timer process */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  /* Enable the Ethernet interrupt */
+
+  self->sk_bifup = true;
+  self->lower->enable(self->lower, true);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_ifdown
+ *
+ * Description:
+ *   NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifdown(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  irqstate_t flags;
+
+  /* Disable the Ethernet interrupt */
+
+  flags = enter_critical_section();
+  self->lower->enable(self->lower, false);
+
+  /* Cancel the TX poll timer and TX timeout timers */
+
+  wd_cancel(&self->sk_txpoll);
+  wd_cancel(&self->sk_txtimeout);
+
+  /* Put the EMAC in its reset, non-operational state.  This should be
+   * a known configuration that will guarantee the w5500_ifup() always
+   * successfully brings the interface back up.
+   */
+
+  w5500_fence(self);
+
+  /* Mark the device "down" */
+
+  self->sk_bifup = false;
+  leave_critical_section(flags);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_txavail_work
+ *
+ * Description:
+ *   Perform an out-of-cycle poll on the worker thread.
+ *
+ * Input Parameters:
+ *   arg - Reference to the NuttX driver state structure (cast to void*)
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_txavail_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Ignore the notification if the interface is not yet up */
+
+  if (priv->sk_bifup)
+    {
+      /* Check if there is room in the hardware to hold another packet. */
+
+      /* If so, then poll the network for new XMIT data */
+
+      devif_timer(&priv->sk_dev, 0, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txavail
+ *
+ * Description:
+ *   Driver callback invoked when new TX data is available.  This is a

Review Comment:
   ```suggestion
     *   Driver callback invoked when new TX data is available. This is a



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &priv->sk_pollwork, w5500_poll_work, priv, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_ifup
+ *
+ * Description:
+ *   NuttX Callback: Bring up the Ethernet interface when an IP address is
+ *   provided
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifup(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  int ret;
+
+#ifdef CONFIG_NET_IPv4
+  ninfo("Bringing up: %d.%d.%d.%d\n",
+        (int)dev->d_ipaddr & 0xff,
+        (int)(dev->d_ipaddr >> 8) & 0xff,
+        (int)(dev->d_ipaddr >> 16) & 0xff,
+        (int)dev->d_ipaddr >> 24);
+#endif
+#ifdef CONFIG_NET_IPv6
+  ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+        dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
+        dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
+        dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
+#endif
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  ret = w5500_unfence(self);
+
+  if (ret != OK)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_NET_ICMPv6
+  /* Set up IPv6 multicast address filtering */
+
+  w5500_ipv6multicast(self);
+#endif
+
+  /* Set and activate a timer process */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  /* Enable the Ethernet interrupt */
+
+  self->sk_bifup = true;
+  self->lower->enable(self->lower, true);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_ifdown
+ *
+ * Description:
+ *   NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifdown(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  irqstate_t flags;
+
+  /* Disable the Ethernet interrupt */
+
+  flags = enter_critical_section();
+  self->lower->enable(self->lower, false);
+
+  /* Cancel the TX poll timer and TX timeout timers */
+
+  wd_cancel(&self->sk_txpoll);
+  wd_cancel(&self->sk_txtimeout);
+
+  /* Put the EMAC in its reset, non-operational state.  This should be
+   * a known configuration that will guarantee the w5500_ifup() always
+   * successfully brings the interface back up.
+   */
+
+  w5500_fence(self);
+
+  /* Mark the device "down" */
+
+  self->sk_bifup = false;
+  leave_critical_section(flags);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_txavail_work
+ *
+ * Description:
+ *   Perform an out-of-cycle poll on the worker thread.
+ *
+ * Input Parameters:
+ *   arg - Reference to the NuttX driver state structure (cast to void*)
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_txavail_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Ignore the notification if the interface is not yet up */
+
+  if (priv->sk_bifup)
+    {
+      /* Check if there is room in the hardware to hold another packet. */
+
+      /* If so, then poll the network for new XMIT data */
+
+      devif_timer(&priv->sk_dev, 0, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txavail
+ *
+ * Description:
+ *   Driver callback invoked when new TX data is available.  This is a
+ *   stimulus perform an out-of-cycle poll and, thereby, reduce the TX
+ *   latency.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txavail(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_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->sk_pollwork))
+    {
+      /* Schedule to serialize the poll on the worker thread. */
+
+      work_queue(ETHWORK, &priv->sk_pollwork, w5500_txavail_work, priv, 0);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_addmac
+ *
+ * Description:
+ *   NuttX Callback: Add the specified MAC address to the hardware multicast
+ *   address filtering
+ *
+ * Input Parameters:
+ *   dev  - Reference to the NuttX driver state structure
+ *   mac  - The MAC address to be added
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int w5500_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* Add the MAC address to the hardware multicast routing table */
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: w5500_rmmac
+ *
+ * Description:
+ *   NuttX Callback: Remove the specified MAC address from the hardware
+ *   multicast address filtering
+ *
+ * Input Parameters:
+ *   dev  - Reference to the NuttX driver state structure
+ *   mac  - The MAC address to be removed
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_MCASTGROUP
+static int w5500_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* Add the MAC address to the hardware multicast routing table */
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: w5500_ipv6multicast
+ *
+ * Description:
+ *   Configure the IPv6 multicast MAC address.
+ *
+ * Input Parameters:
+ *   priv - A reference to the private driver state structure
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv)
+{
+  FAR struct net_driver_s *dev;
+  uint16_t tmp16;
+  uint8_t mac[6];
+
+  /* For ICMPv6, we need to add the IPv6 multicast address
+   *
+   * For IPv6 multicast addresses, the Ethernet MAC is derived by
+   * the four low-order octets OR'ed with the MAC 33:33:00:00:00:00,
+   * so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map
+   * to the Ethernet MAC address 33:33:00:01:00:03.
+   *
+   * NOTES:  This appears correct for the ICMPv6 Router Solicitation
+   * Message, but the ICMPv6 Neighbor Solicitation message seems to
+   * use 33:33:ff:01:00:03.
+   */
+
+  mac[0] = 0x33;
+  mac[1] = 0x33;
+
+  dev    = &priv->dev;
+  tmp16  = dev->d_ipv6addr[6];
+  mac[2] = 0xff;
+  mac[3] = tmp16 >> 8;
+
+  tmp16  = dev->d_ipv6addr[7];
+  mac[4] = tmp16 & 0xff;
+  mac[5] = tmp16 >> 8;
+
+  ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n",
+        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+
+  w5500_addmac(dev, mac);
+
+#ifdef CONFIG_NET_ICMPv6_AUTOCONF
+  /* Add the IPv6 all link-local nodes Ethernet address.  This is the
+   * address that we expect to receive ICMPv6 Router Advertisement
+   * packets.
+   */
+
+  w5500_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet);
+
+#endif /* CONFIG_NET_ICMPv6_AUTOCONF */
+
+#ifdef CONFIG_NET_ICMPv6_ROUTER
+  /* Add the IPv6 all link-local routers Ethernet address.  This is the

Review Comment:
   ```suggestion
      /* Add the IPv6 all link-local routers Ethernet address. This is the



##########
drivers/net/w5500.c:
##########
@@ -0,0 +1,2326 @@
+/****************************************************************************
+ * drivers/net/w5500.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * References:
+ *   [W5500]  W5500 Datasheet, Version 1.0.9, May 2019, WIZnet Co., Ltd.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <arpa/inet.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/net/arp.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/w5500.h>
+#include <nuttx/signal.h>
+
+#ifdef CONFIG_NET_PKT
+#  include <nuttx/net/pkt.h>
+#endif
+
+#ifdef CONFIG_NET_W5500
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
+#else
+
+/* 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_NET_W5500_NINTERFACES determines the number of physical interfaces
+ * that will be supported.
+ */
+
+#ifndef CONFIG_NET_W5500_NINTERFACES
+# define CONFIG_NET_W5500_NINTERFACES 1
+#endif
+
+/* TX poll delay = 1 seconds.
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#define W5500_WDDELAY   (1 * CLK_TCK)
+
+/* TX timeout = 1 minute */
+
+#define W5500_TXTIMEOUT (60 * CLK_TCK)
+
+/* This is a helper pointer for accessing the contents of Ethernet header */
+
+#define ETH_HDR ((FAR struct eth_hdr_s *)self->sk_dev.d_buf)
+
+/* Number of Ethernet frame transmission buffers maintained in W5500's 16 KiB
+ * Tx RAM.  A maximum size is conservatively assumed per Ethernet frame in
+ * order to simplify buffer management logic.
+ */
+
+#define NUM_TXBUFS ((16 * 1024) / (CONFIG_NET_ETH_PKTSIZE))
+
+/* W5500 SPI Host Interface *************************************************/
+
+#define W5500_BSB_COMMON_REGS         0x00u
+#define W5500_BSB_SOCKET_REGS(n)      (((n & 0x7u) << 5) | 0x08u)
+#define W5500_BSB_SOCKET_TX_BUFFER(n) (((n & 0x7u) << 5) | 0x10u)
+#define W5500_BSB_SOCKET_RX_BUFFER(n) (((n & 0x7u) << 5) | 0x18u)
+
+#define W5500_RWB_READ                0x00u
+#define W5500_RWB_WRITE               0x04u
+
+#define W5500_OM_DATA_LEN_VAR         0x00u
+#define W5500_OM_DATA_LEN_1           0x01u
+#define W5500_OM_DATA_LEN_2           0x02u
+#define W5500_OM_DATA_LEN_4           0x03u
+
+/* W5500 Register Addresses *************************************************/
+
+/* Common Register Block */
+
+#define W5500_MR             0x0000  /* Mode */
+#define W5500_GAR0           0x0001  /* Gateway Address */
+#define W5500_GAR1           0x0002
+#define W5500_GAR2           0x0003
+#define W5500_GAR3           0x0004
+#define W5500_SUBR0          0x0005  /* Subnet Mask Address */
+#define W5500_SUBR1          0x0006
+#define W5500_SUBR2          0x0007
+#define W5500_SUBR3          0x0008
+#define W5500_SHAR0          0x0009  /* Source Hardware Address */
+#define W5500_SHAR1          0x000a
+#define W5500_SHAR2          0x000b
+#define W5500_SHAR3          0x000c
+#define W5500_SHAR4          0x000d
+#define W5500_SHAR5          0x000e
+#define W5500_SIPR0          0x000f  /* Source IP Address */
+#define W5500_SIPR1          0x0010
+#define W5500_SIPR2          0x0011
+#define W5500_SIPR3          0x0012
+#define W5500_INTLEVEL0      0x0013  /* Interrupt Low Level Timer */
+#define W5500_INTLEVEL1      0x0014
+#define W5500_IR             0x0015  /* Interrupt */
+#define W5500_IMR            0x0016  /* Interrupt Mask */
+#define W5500_SIR            0x0017  /* Socket Interrupt */
+#define W5500_SIMR           0x0018  /* Socket Interrupt Mask */
+#define W5500_RTR0           0x0019  /* Retry Time */
+#define W5500_RTR1           0x001a
+#define W5500_RCR            0x001b  /* Retry Count */
+#define W5500_PTIMER         0x001c  /* PPP LCP Request Timer */
+#define W5500_PMAGIC         0x001d  /* PPP LCP Magic number */
+#define W5500_PHAR0          0x001e  /* PPP Destination MAC Address */
+#define W5500_PHAR1          0x001f
+#define W5500_PHAR2          0x0020
+#define W5500_PHAR3          0x0021
+#define W5500_PHAR4          0x0022
+#define W5500_PHAR5          0x0023
+#define W5500_PSID0          0x0024  /* PPP Session Identification */
+#define W5500_PSID1          0x0025
+#define W5500_PMRU0          0x0026  /* PPP Maximum Segment Size */
+#define W5500_PMRU1          0x0027
+#define W5500_UIPR0          0x0028  /* Unreachable IP address */
+#define W5500_UIPR1          0x0029
+#define W5500_UIPR2          0x002a
+#define W5500_UIPR3          0x002b
+#define W5500_UPORTR0        0x002c  /* Unreachable Port */
+#define W5500_UPORTR1        0x002d
+#define W5500_PHYCFGR        0x002e  /* PHY Configuration */
+                                     /* 0x002f-0x0038: Reserved */
+#define W5500_VERSIONR       0x0039  /* Chip version */
+                                     /* 0x003a-0xffff: Reserved */
+
+/* Socket Register Block */
+
+#define W5500_SN_MR          0x0000  /* Socket n Mode */
+#define W5500_SN_CR          0x0001  /* Socket n Command */
+#define W5500_SN_IR          0x0002  /* Socket n Interrupt */
+#define W5500_SN_SR          0x0003  /* Socket n Status */
+#define W5500_SN_PORT0       0x0004  /* Socket n Source Port */
+#define W5500_SN_PORT1       0x0005
+#define W5500_SN_DHAR0       0x0006  /* Socket n Destination Hardware Address */
+#define W5500_SN_DHAR1       0x0007
+#define W5500_SN_DHAR2       0x0008
+#define W5500_SN_DHAR3       0x0009
+#define W5500_SN_DHAR4       0x000a
+#define W5500_SN_DHAR5       0x000b
+#define W5500_SN_DIPR0       0x000c  /* Socket n Destination IP Address */
+#define W5500_SN_DIPR1       0x000d
+#define W5500_SN_DIPR2       0x000e
+#define W5500_SN_DIPR3       0x000f
+#define W5500_SN_DPORT0      0x0010  /* Socket n Destination Port */
+#define W5500_SN_DPORT1      0x0011
+#define W5500_SN_MSSR0       0x0012  /* Socket n Maximum Segment Size */
+#define W5500_SN_MSSR1       0x0013
+                                     /* 0x0014: Reserved */
+#define W5500_SN_TOS         0x0015  /* Socket n IP TOS */
+#define W5500_SN_TTL         0x0016  /* Socket n IP TTL */
+                                     /* 0x0017-0x001d: Reserved */
+#define W5500_SN_RXBUF_SIZE  0x001e  /* Socket n Receive Buffer Size */
+#define W5500_SN_TXBUF_SIZE  0x001f  /* Socket n Transmit Buffer Size */
+#define W5500_SN_TX_FSR0     0x0020  /* Socket n TX Free Size */
+#define W5500_SN_TX_FSR1     0x0021
+#define W5500_SN_TX_RD0      0x0022  /* Socket n TX Read Pointer */
+#define W5500_SN_TX_RD1      0x0023
+#define W5500_SN_TX_WR0      0x0024  /* Socket n TX Write Pointer */
+#define W5500_SN_TX_WR1      0x0025
+#define W5500_SN_RX_RSR0     0x0026  /* Socket n RX Received Size */
+#define W5500_SN_RX_RSR1     0x0027
+#define W5500_SN_RX_RD0      0x0028  /* Socket n RX Read Pointer */
+#define W5500_SN_RX_RD1      0x0029
+#define W5500_SN_RX_WR0      0x002a  /* Socket n RX Write Pointer */
+#define W5500_SN_RX_WR1      0x002b
+#define W5500_SN_IMR         0x002c  /* Socket n Interrupt Mask */
+#define W5500_SN_FRAG0       0x002d  /* Socket n Fragment Offset in IP header */
+#define W5500_SN_FRAG1       0x002e
+#define W5500_SN_KPALVTR     0x002f  /* Keep alive timer */
+                                     /* 0x0030-0xffff: Reserved */
+
+/* W5500 Register Bitfield Definitions **************************************/
+
+/* Common Register Block */
+
+/* Mode Register (MR) */
+
+#define MR_FARP              (1 << 1)  /* Bit 1: Force ARP */
+#define MR_PPPOE             (1 << 3)  /* Bit 3: PPPoE Mode */
+#define MR_PB                (1 << 4)  /* Bit 4: Ping Block Mode */
+#define MR_WOL               (1 << 5)  /* Bit 5: Wake on LAN */
+#define MR_RST               (1 << 7)  /* Bit 7: Reset registers */
+
+/* Interrupt Register (IR), Interrupt Mask Register (IMR) */
+
+#define INT_MP               (1 << 4)  /* Bit 4:  Magic Packet */
+#define INT_PPPOE            (1 << 5)  /* Bit 5:  PPPoE Connection Close */
+#define INT_UNREACH          (1 << 6)  /* Bit 6:  Destination unreachable */
+#define INT_CONFLICT         (1 << 7)  /* Bit 7:  IP Conflict */
+
+/* Socket Interrupt Register (SIR) */
+
+#define SIR(n)               (1 << (n))
+
+/* Socket Interrupt Mask Register (SIMR)) */
+
+#define SIMR(n)              (1 << (n))
+
+/* PHY Configuration Register (PHYCFGR) */
+
+#define PHYCFGR_LNK          (1 << 0)  /* Bit 0:  Link Status */
+#define PHYCFGR_SPD          (1 << 1)  /* Bit 1:  Speed Status */
+#define PHYCFGR_DPX          (1 << 2)  /* Bit 2:  Duplex Status */
+#define PHYCFGR_OPMDC_SHIFT  (3)       /* Bits 3-5: Operation Mode Configuration */
+#define PHYCFGR_OPMDC_MASK   (7 << PHYCFGR_OPMDC_SHIFT)
+#  define PHYCFGR_OPMDC_10BT_HD_NAN  (0 << PHYCFGR_OPMDC_SHIFT) /* 10BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_HFD_NAN (1 << PHYCFGR_OPMDC_SHIFT) /* 10BT Full-duplex */
+#  define PHYCFGR_OPMDC_100BT_HD_NAN (2 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex */
+#  define PHYCFGR_OPMDC_10BT_FD_NAN  (3 << PHYCFGR_OPMDC_SHIFT) /* 100BT Full-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_100BT_HD_AN  (4 << PHYCFGR_OPMDC_SHIFT) /* 100BT Half-duplex,
+                                                                 * Auto-negotiation */
+#  define PHYCFGR_OPMDC_POWER_DOWN   (6 << PHYCFGR_OPMDC_SHIFT) /* Power Down mode */
+#  define PHYCFGR_OPMDC_ALLCAP_AN    (7 << PHYCFGR_OPMDC_SHIFT) /* All capable,
+                                                                 * Auto-negotiation */
+
+#define PHYCFGR_OPMD         (1 << 6)  /* Bit 6:  Configure PHY Operation Mode */
+#define PHYCFGR_RST          (1 << 7)  /* Bit 7:  Reset */
+
+/* Socket Register Block */
+
+/* Socket n Mode Register (SN_MR) */
+
+#define SN_MR_PROTOCOL_SHIFT (0)       /* Bits 0-3:  Protocol */
+#define SN_MR_PROTOCOL_MASK  (15 << SN_MR_PROTOCOL_SHIFT)
+#  define SN_MR_P0           (1 << (SN_MR_PROTOCOL_SHIFT + 0))
+#  define SN_MR_P1           (1 << (SN_MR_PROTOCOL_SHIFT + 1))
+#  define SN_MR_P2           (1 << (SN_MR_PROTOCOL_SHIFT + 2))
+#  define SN_MR_P3           (1 << (SN_MR_PROTOCOL_SHIFT + 3))
+#  define SM_MR_CLOSED       0
+#  define SM_MR_TCP          SN_MR_P0
+#  define SM_MR_UDP          SN_MR_P1
+#  define SM_MR_MACRAW       SN_MR_P2
+#define SN_MR_UCASTB         (1 << 4)  /* Bit 4:  UNICAST Blocking in UDP mode */
+#define SN_MR_MIP6B          (1 << 4)  /* Bit 4:  IPv6 packet Blocking in MACRAW mode */
+#define SN_MR_ND             (1 << 5)  /* Bit 5:  Use No Delayed ACK */
+#define SN_MR_MC             (1 << 5)  /* Bit 5:  Multicast */
+#define SN_MR_MMB            (1 << 5)  /* Bit 5:  Multicast Blocking in MACRAW mode */
+#define SN_MR_BCASTB         (1 << 6)  /* Bit 6:  Broadcast Blocking in MACRAW and
+                                        *         UDP mode */
+#define SN_MR_MULTI          (1 << 7)  /* Bit 7:  Multicasting in UDP mode */
+#define SN_MR_MFEN           (1 << 7)  /* Bit 7:  MAC Filter Enable in MACRAW mode */
+
+/* Socket n Command Register (SN_CR) */
+
+#define SN_CR_OPEN           0x01      /* Socket n is initialized and opened according
+                                        * to the protocol selected in SN_MR */
+#define SN_CR_LISTEN         0x02      /* Socket n operates as a 'TCP server' and waits
+                                        * for connection request from any 'TCP client' */
+#define SN_CR_CONNECT        0x04      /* 'TCP client' connection request */
+#define SN_CR_DISCON         0x08      /* TCP disconnection request */
+#define SN_CR_CLOSE          0x10      /* Close socket n */
+#define SN_CR_SEND           0x20      /* Transmit all data in Socket n TX buffer */
+#define SN_CR_SEND_MAC       0x21      /* Transmit all UDP data (no ARP) */
+#define SN_CR_SEND_KEEP      0x22      /* Send TCP keep-alive packet */
+#define SN_CR_RECV           0x40      /* Complete received data in Socket n RX buffer */
+
+/* Socket n Interrupt Register (SN_IR) and
+ * Socket n Interrupt Mask Register (SN_IMR)
+ */
+
+#define SN_INT_CON           (1 << 0)  /* Bit 0:  Connection with peer successful */
+#define SN_INT_DISCON        (1 << 1)  /* Bit 1:  FIN or FIN/ACK received from peer */
+#define SN_INT_RECV          (1 << 2)  /* Bit 2:  Data received from peer */
+#define SN_INT_TIMEOUT       (1 << 3)  /* Bit 3:  ARP or TCP timeout */
+#define SN_INT_SEND_OK       (1 << 4)  /* Bit 4:  SEND command completed */
+
+/* Socket n Status Register (SN_SR) */
+
+#define SN_SR_SOCK_CLOSED      0x00
+#define SN_SR_SOCK_INIT        0x13
+#define SN_SR_SOCK_LISTEN      0x14
+#define SN_SR_SOCK_ESTABLISHED 0x17
+#define SN_SR_SOCK_CLOSE_WAIT  0x1c
+#define SN_SR_SOCK_UDP         0x22
+#define SN_SR_SOCK_MACRAW      0x42
+
+#define SN_SR_SOCK_SYNSENT     0x15    /* Transitional status */
+#define SN_SR_SOCK_SYNRECV     0x16
+#define SN_SR_SOCK_FIN_WAIT    0x18
+#define SN_SR_SOCK_CLOSING     0x1a
+#define SN_SR_SOCK_TIME_WAIT   0x1b
+#define SN_SR_SOCK_LAST_ACK    0x1d
+
+/* Socket n RX Buffer Size Register (SN_RXBUF) */
+
+#define SN_RXBUF_0KB          0
+#define SN_RXBUF_1KB          1
+#define SN_RXBUF_2KB          2
+#define SN_RXBUF_4KB          4
+#define SN_RXBUF_8KB          5
+#define SN_RXBUF_16KB         16
+
+/* Socket n TX Buffer Size Register (SN_TXBUF) */
+
+#define SN_TXBUF_0KB          0
+#define SN_TXBUF_1KB          1
+#define SN_TXBUF_2KB          2
+#define SN_TXBUF_4KB          4
+#define SN_TXBUF_8KB          5
+#define SN_TXBUF_16KB         16
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The w5500_driver_s encapsulates all state information for a single
+ * hardware interface
+ */
+
+struct w5500_driver_s
+{
+  bool sk_bifup;               /* true:ifup false:ifdown */
+  struct wdog_s sk_txpoll;     /* TX poll timer */
+  struct wdog_s sk_txtimeout;  /* TX timeout timer */
+  struct work_s sk_irqwork;    /* For deferring interrupt work to the work queue */
+  struct work_s sk_pollwork;   /* For deferring poll work to the work queue */
+
+  /* Ethernet frame transmission buffer management */
+
+  uint16_t txbuf_offset[NUM_TXBUFS + 1];
+  uint8_t  txbuf_rdptr;
+  uint8_t  txbuf_wrptr;
+
+  /* The hardware interconnect to the W5500 chip */
+
+  FAR struct spi_dev_s           *spi_dev; /* SPI hardware access    */
+  FAR const struct w5500_lower_s *lower;   /* Low-level MCU specific */
+
+  /* This holds the information visible to the NuttX network */
+
+  struct net_driver_s sk_dev;  /* Interface understood by the network */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* These statically allocated structures would mean that only a single
+ * instance of the device could be supported.  In order to support multiple
+ * devices instances, this data would have to be allocated dynamically.
+ */
+
+/* A single packet buffer per device is used in this example.  There might
+ * be multiple packet buffers in a more complex, pipelined design.  Many
+ * contemporary Ethernet interfaces, for example,  use multiple, linked DMA
+ * descriptors in rings to implement such a pipeline.  This example assumes
+ * much simpler hardware that simply handles one packet at a time.
+ *
+ * NOTE that if CONFIG_NET_W5500_NINTERFACES were greater than 1, you would
+ * need a minimum on one packet buffer per instance.  Much better to be
+ * allocated dynamically in cases where more than one are needed.
+ */
+
+static uint8_t g_pktbuf[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
+
+/* Driver state structure */
+
+static struct w5500_driver_s g_w5500[CONFIG_NET_W5500_NINTERFACES];
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Common TX logic */
+
+static void w5500_transmit(FAR struct w5500_driver_s *priv);
+static int  w5500_txpoll(FAR struct net_driver_s *dev);
+
+/* Interrupt handling */
+
+static void w5500_reply(struct w5500_driver_s *priv);
+static void w5500_receive(FAR struct w5500_driver_s *priv);
+static void w5500_txdone(FAR struct w5500_driver_s *priv);
+
+static void w5500_interrupt_work(FAR void *arg);
+static int  w5500_interrupt(int irq, FAR void *context, FAR void *arg);
+
+/* Watchdog timer expirations */
+
+static void w5500_txtimeout_work(FAR void *arg);
+static void w5500_txtimeout_expiry(wdparm_t arg);
+
+static void w5500_poll_work(FAR void *arg);
+static void w5500_poll_expiry(wdparm_t arg);
+
+/* NuttX callback functions */
+
+static int  w5500_ifup(FAR struct net_driver_s *dev);
+static int  w5500_ifdown(FAR struct net_driver_s *dev);
+
+static void w5500_txavail_work(FAR void *arg);
+static int  w5500_txavail(FAR struct net_driver_s *dev);
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int  w5500_addmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#ifdef CONFIG_NET_MCASTGROUP
+static int  w5500_rmmac(FAR struct net_driver_s *dev,
+              FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv);
+#endif
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int  w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+              unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* Hardware interface to W5500 **********************************************/
+
+/****************************************************************************
+ * Name: w5500_reset
+ *
+ * Description:
+ *   Apply the hardware reset sequence to the W5500 (See [W5500], section
+ *   5.5.1 Reset Timing).  Optinonally, keep hardware reset asserted.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *   keep  - Whether the hardware reset shall be left asserted.
+ *
+ * Assumptions:
+ *   This function must only be called in interrupt context, if argument
+ *   keep is set to false (otherwise it sleeps, which is not allowed in
+ *   interrupt context).
+ *
+ ****************************************************************************/
+
+static void w5500_reset(FAR struct w5500_driver_s *self, bool keep)
+{
+  self->lower->reset(self->lower, true);
+
+  if (!keep)
+    {
+      nxsig_usleep(500);   /* [W5500]: T_RC (Reset Cycle Time) min 500 us   */
+
+      self->lower->reset(self->lower, false);
+
+      nxsig_usleep(1000);  /* [W5500]: T_PL (RSTn to internal PLL lock) 1ms */
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_lock
+ *
+ * Description:
+ *   Acquire exclusive access to the W5500's SPI bus and configure it
+ *   accordingly.
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_lock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  ret = SPI_LOCK(self->spi_dev, true);
+  DEBUGASSERT(ret == OK);
+
+  SPI_SETMODE(self->spi_dev, self->lower->mode);
+  SPI_SETBITS(self->spi_dev, 8);
+  SPI_HWFEATURES(self->spi_dev, 0);
+  SPI_SETFREQUENCY(self->spi_dev, self->lower->frequency);
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), true);
+}
+
+/****************************************************************************
+ * Name: w5500_unlock
+ *
+ * Description:
+ *   Relinquish exclusive access to the W5500's SPI bus
+ *
+ * Input Parameters:
+ *   self  - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_unlock(FAR struct w5500_driver_s *self)
+{
+  int ret;
+
+  SPI_SELECT(self->spi_dev, SPIDEV_ETHERNET(self->lower->spidevid), false);
+  ret = SPI_LOCK(self->spi_dev, false);
+  DEBUGASSERT(ret == OK);
+}
+
+/****************************************************************************
+ * Name: w5500_read
+ *
+ * Description:
+ *   Read a number of bytes from one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   buffer            - Buffer to copy read data into
+ *   len               - Number of bytes to read from block
+ *
+ ****************************************************************************/
+
+static void w5500_read(FAR struct w5500_driver_s *self,
+                       uint8_t                   block_select_bits,
+                       uint16_t                  offset,
+                       void                      *buffer,
+                       uint16_t                  len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_READ | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_RECVBLOCK(self->spi_dev, buffer, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_write
+ *
+ * Description:
+ *   Write a number of bytes to one of the W5500's register or buffer
+ *   blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   Data              - Data to write to block
+ *   len               - Number of bytes to write to block
+ *
+ ****************************************************************************/
+
+static void w5500_write(FAR struct w5500_driver_s *self,
+                        uint8_t                   block_select_bits,
+                        off_t                     offset,
+                        const void                *data,
+                        size_t                    len)
+{
+  uint8_t addr_cntl[3];
+
+  addr_cntl[0] = (uint8_t)(offset >> 8);
+  addr_cntl[1] = (uint8_t)offset;
+  addr_cntl[2] = block_select_bits | W5500_RWB_WRITE | W5500_OM_DATA_LEN_VAR;
+
+  w5500_lock(self);
+  SPI_SNDBLOCK(self->spi_dev, addr_cntl, sizeof(addr_cntl));
+  SPI_SNDBLOCK(self->spi_dev, data, len);
+  w5500_unlock(self);
+}
+
+/****************************************************************************
+ * Name: w5500_read8
+ *
+ * Description:
+ *   Read a single byte from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The byte read
+ *
+ ****************************************************************************/
+
+static uint8_t w5500_read8(FAR struct w5500_driver_s *self,
+                           uint8_t                   block_select_bits,
+                           uint16_t                  offset)
+{
+  uint8_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return value;
+}
+
+/****************************************************************************
+ * Name: w5500_write8
+ *
+ * Description:
+ *   Write a single byte to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The byte value to write
+ *
+ ****************************************************************************/
+
+static void w5500_write8(FAR struct w5500_driver_s *self,
+                         uint8_t                   block_select_bits,
+                         uint16_t                  offset,
+                         uint8_t                   value)
+{
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16
+ *
+ * Description:
+ *   Read a two byte value from one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *
+ * Returned Value:
+ *   The two byte value read in host byte order.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_read16(FAR struct w5500_driver_s *self,
+                             uint8_t                   block_select_bits,
+                             uint16_t                  offset)
+{
+  uint16_t value;
+
+  w5500_read(self,
+             block_select_bits,
+             offset,
+             &value,
+             sizeof(value));
+
+  return NTOHS(value);
+}
+
+/****************************************************************************
+ * Name: w5500_write16
+ *
+ * Description:
+ *   Write a two byte value to one of the W5500's register or buffer blocks.
+ *
+ * Input Parameters:
+ *   self              - The respective w5500 device
+ *   block_select_bits - Register or buffer block to read from
+ *   offset            - The offset address within the block
+ *   value             - The two byte value to write in host byte order.
+ *
+ ****************************************************************************/
+
+static void w5500_write16(FAR struct w5500_driver_s *self,
+                          uint8_t                   block_select_bits,
+                          uint16_t                  offset,
+                          uint16_t                  value)
+{
+  value = HTONS(value);
+
+  w5500_write(self,
+              block_select_bits,
+              offset,
+              &value,
+              sizeof(value));
+}
+
+/****************************************************************************
+ * Name: w5500_read16_atomic
+ *
+ * Description:
+ *   Read a two-byte value that is concurrently updated by the W5500 hardware
+ *   in a safe fashion.  In [W5500] it is recommended to read the 16 bits
+ *   multiple times until one gets the same value twice in a row.
+ *
+ * Input Parameters:
+ *   self               - The respective w5500 device
+ *   block_select_bits  - Register block to read from  (See [W5500], section
+ *                          2.2.2 Control Phase)
+ *   offset             - Offset address of the register to read.
+ *   value              - The 16-bit value read in host byte-order.
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure.
+ *
+ ****************************************************************************/
+
+static int w5500_read16_atomic(FAR struct w5500_driver_s *self,
+                               uint8_t                   block_select_bits,
+                               uint16_t                  offset,
+                               FAR uint16_t              *value)
+{
+  int i;
+
+  *value = w5500_read16(self, block_select_bits, offset);
+
+  for (i = 0; i < 100; i++)
+    {
+      uint16_t temp;
+
+      temp = w5500_read16(self, block_select_bits, offset);
+
+      if (*value == temp)
+        {
+          return OK;
+        }
+
+      *value = temp;
+    }
+
+  nerr("Failed to get consistent value.\n");
+
+  return -EIO;
+}
+
+/* Ethernet frame tranmission buffer management *****************************/
+
+/****************************************************************************
+ * Name: w5500_txbuf_reset
+ *
+ * Description:
+ *   Reset state that manages the storage of multiple outgoing Ethernet
+ *   frames in W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_txbuf_reset(FAR struct w5500_driver_s *self)
+{
+  memset(self->txbuf_offset, 0, sizeof(self->txbuf_offset));
+  self->txbuf_rdptr = 0;
+  self->txbuf_wrptr = 0;
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numfree
+ *
+ * Description:
+ *   Return the number of Ethernet frames that can still be stored in W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that can still be stored.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numfree(FAR struct w5500_driver_s *self)
+{
+  if (self->txbuf_wrptr >= self->txbuf_rdptr)
+    {
+      return NUM_TXBUFS - (self->txbuf_wrptr - self->txbuf_rdptr);
+    }
+  else
+    {
+      return self->txbuf_rdptr - self->txbuf_wrptr - 1;
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_numpending
+ *
+ * Description:
+ *   Return the number of Ethernet frames that are still pending for trans-
+ *   mission from W5500's 16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The number of Ethernet frames that are pending for transmission.
+ *
+ ****************************************************************************/
+
+static int w5500_txbuf_numpending(FAR struct w5500_driver_s *self)
+{
+  return NUM_TXBUFS - w5500_txbuf_numfree(self);
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_copy
+ *
+ * Description:
+ *   Copy an Ethernet frame from the socket device's buffer to the W5500's
+ *   16KiB Tx RAM.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   The byte offset of the first byte after the frame that was copied into
+ *   W5500's 16KiB Tx RAM.
+ *
+ ****************************************************************************/
+
+static uint16_t w5500_txbuf_copy(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numfree(self) > 0);
+
+  offset = self->txbuf_offset[self->txbuf_wrptr];
+
+  w5500_write(self,
+              W5500_BSB_SOCKET_TX_BUFFER(0),
+              offset,
+              self->sk_dev.d_buf,
+              self->sk_dev.d_len);
+
+  self->txbuf_wrptr = (self->txbuf_wrptr + 1) % (NUM_TXBUFS + 1);
+  self->txbuf_offset[self->txbuf_wrptr] = offset + self->sk_dev.d_len;
+
+  return self->txbuf_offset[self->txbuf_wrptr];
+}
+
+/****************************************************************************
+ * Name: w5500_txbuf_next
+ *
+ * Description:
+ *   Release storage for an Ethernet frame that has been successfully
+ *   transmitted.  Also triggers transmission of the next Ethernet frame,
+ *   if applicable.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   Whether transmission of another Ethernet frame was triggered.
+ *
+ ****************************************************************************/
+
+static bool w5500_txbuf_next(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  DEBUGASSERT(w5500_txbuf_numpending(self));
+
+  self->txbuf_rdptr = (self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1);
+
+  offset = w5500_read16(self,
+                        W5500_BSB_SOCKET_REGS(0),
+                        W5500_SN_TX_RD0);
+
+  DEBUGASSERT(self->txbuf_offset[self->txbuf_rdptr] == offset);
+
+  if (!w5500_txbuf_numpending(self))
+    {
+      return false;
+    }
+
+  offset = self->txbuf_offset[(self->txbuf_rdptr + 1) % (NUM_TXBUFS + 1)];
+
+  w5500_write16(self,
+                W5500_BSB_SOCKET_REGS(0),
+                W5500_SN_TX_WR0,
+                offset);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR,
+               SN_CR_SEND);
+
+  /* (Re-)start the TX timeout watchdog timer */
+
+  wd_start(&self->sk_txtimeout,
+           W5500_TXTIMEOUT,
+           w5500_txtimeout_expiry,
+           (wdparm_t)self);
+
+  return true;
+}
+
+/****************************************************************************
+ * Name: w5500_fence
+ *
+ * Description:
+ *   Put the W5500 into reset and disable respective interrupt handling.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ ****************************************************************************/
+
+static void w5500_fence(FAR struct w5500_driver_s *self)
+{
+  self->lower->enable(self->lower, false);
+  w5500_reset(self, true);  /* Reset and keep reset asserted */
+  self->sk_bifup = false;
+}
+
+/****************************************************************************
+ * Name: w5500_unfence
+ *
+ * Description:
+ *   Release W5500 from reset, initialize it and wait up to ten seconds
+ *   for link up.
+ *
+ * Input Parameters:
+ *   self - The respective w5500 device
+ *
+ * Returned Value:
+ *   OK in case of success.  A negated errno value in case of failure, in
+ *   which case the W5500 is fenced again.
+ *
+ ****************************************************************************/
+
+static int w5500_unfence(FAR struct w5500_driver_s *self)
+{
+  uint8_t value;
+  int i;
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  w5500_reset(self, false);  /* Reset sequence and keep reset de-asserted */
+
+  /* Set the Ethernet interface's MAC address */
+
+  w5500_write(self,
+              W5500_BSB_COMMON_REGS,
+              W5500_SHAR0, /* Source Hardware Address Register */
+              self->sk_dev.d_mac.ether.ether_addr_octet,
+              sizeof(self->sk_dev.d_mac.ether.ether_addr_octet));
+
+  /* Configure socket 0 for raw MAC access with MAC filtering enabled. */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_MR,
+               SM_MR_MACRAW | SN_MR_MFEN);
+
+  /* Allocate all TX and RX buffer space to socket 0 ... */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_RXBUF_SIZE,
+               SN_RXBUF_16KB);
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_TXBUF_SIZE,
+               SN_TXBUF_16KB);
+
+  /* ... and none to sockets 1 to 7. */
+
+  for (i = 1; i < 8; i++)
+    {
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_RXBUF_SIZE,
+                   SN_RXBUF_0KB);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(i),
+                   W5500_SN_TXBUF_SIZE,
+                   SN_TXBUF_0KB);
+    }
+
+  /* Enable RECV interrupts on socket 0 (SEND_OK interrupts will only be
+   * enabled as long as a transmission is in progress).
+   */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IMR,
+               SN_INT_RECV);
+
+  /* Enable interrupts on socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_COMMON_REGS,
+               W5500_SIMR, /* Socket Interrupt Mask Register */
+               SIMR(0));
+
+  /* Open socket 0 */
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_CR, /* Control Register */
+               SN_CR_OPEN);
+
+  /* Check wheter socket 0 is open in MACRAW mode. */
+
+  value = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_SR);
+
+  if (value != SN_SR_SOCK_MACRAW)
+    {
+      nerr("Unexpected status: %02" PRIx8 "\n", value);
+      goto error;
+    }
+
+  /* Reset Tx buffer management state. */
+
+  w5500_txbuf_reset(self);
+
+  /* Wait up to 10 seconds for link-up */
+
+  value = w5500_read8(self,
+                      W5500_BSB_COMMON_REGS,
+                      W5500_PHYCFGR);
+
+  for (i = 0; (i < 100) && !(value & PHYCFGR_LNK); i++)
+    {
+      value = w5500_read8(self,
+                          W5500_BSB_COMMON_REGS,
+                          W5500_PHYCFGR);
+
+      nxsig_usleep(100000); /* 100 ms x 100 = 10 sec */
+    }
+
+  if (value & PHYCFGR_LNK)
+    {
+      ninfo("Link up (%d Mbps / %s duplex)\n",
+            (value & PHYCFGR_SPD) ? 100 : 10,
+            (value & PHYCFGR_DPX) ? "full" : "half");
+    }
+  else
+    {
+      nwarn("Link still down.  Cable plugged?\n");
+      goto error;
+    }
+
+  return OK;
+
+error:
+  w5500_fence(self);
+  return -EIO;
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_transmit(FAR struct w5500_driver_s *self)
+{
+  uint16_t offset;
+
+  /* Verify that the hardware is ready to send another packet.  If we get
+   * here, then we are committed to sending a packet; Higher level logic
+   * must have assured that there is no transmission in progress.
+   */
+
+  if (!w5500_txbuf_numfree(self))
+    {
+      ninfo("Dropping Tx packet due to no buffer available.\n");
+      NETDEV_TXERRORS(self->sk_dev);
+      return;
+    }
+
+  /* Increment statistics */
+
+  NETDEV_TXPACKETS(self->sk_dev);
+
+  /* Copy packet data to TX buffer */
+
+  offset = w5500_txbuf_copy(self);
+
+  /* If there have not been any Tx buffers in use this means we need to start
+   * transmission.  Otherwise, this is done either in w5500_txdone or in
+   * w5500_txtimeout_work.
+   */
+
+  if (w5500_txbuf_numpending(self) == 1)
+    {
+      /* Set TX Write Pointer to indicate packet length */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_TX_WR0,
+                    offset);
+
+      /* Enable Tx interrupts (Rx ones are always enabled). */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR,
+                   SN_INT_RECV | SN_INT_SEND_OK);
+
+      /* Send the packet */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR, /* Control Register */
+                   SN_CR_SEND);
+
+      /* Setup the TX timeout watchdog (perhaps restarting the timer) */
+
+      wd_start(&self->sk_txtimeout, W5500_TXTIMEOUT,
+               w5500_txtimeout_expiry, (wdparm_t)self);
+    }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+  ninfodumpbuffer("Transmitted:", self->sk_dev.d_buf, self->sk_dev.d_len);
+#endif
+}
+
+/****************************************************************************
+ * Name: w5500_txpoll
+ *
+ * Description:
+ *   The transmitter is available, check if the network has any outgoing
+ *   packets ready to send.  This is a callback from devif_poll().
+ *   devif_poll() may be called:
+ *
+ *   1. When the preceding TX packet send is complete,
+ *   2. When the preceding TX packet send timesout and the interface is reset
+ *   3. During normal TX polling
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txpoll(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* If the polling resulted in data that should be sent out on the network,
+   * the field d_len is set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Look up the destination MAC address and add it to the Ethernet
+       * header.
+       */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv4 */
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif /* CONFIG_NET_IPv6 */
+
+      /* Check if the network is sending this packet to the IP address of
+       * this device.  If so, just loop the packet back into the network but
+       * don't attempt to put it on the wire.
+       */
+
+      if (!devif_loopback(&self->sk_dev))
+        {
+          /* Send the packet */
+
+          w5500_transmit(self);
+
+          /* Check if there is room in the device to hold another packet.
+           * If not, return a non-zero value to terminate the poll.
+           */
+
+          if (!w5500_txbuf_numfree(self))
+            {
+              return 1;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: w5500_reply
+ *
+ * Description:
+ *   After a packet has been received and dispatched to the network, it
+ *   may return with an outgoing packet.  This function checks for that case
+ *   and performs the transmission if necessary.
+ *
+ * Input Parameters:
+ *   self - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_reply(struct w5500_driver_s *self)
+{
+  /* If the packet dispatch resulted in data that should be sent out on the
+   * network, the field d_len will set to a value > 0.
+   */
+
+  if (self->sk_dev.d_len > 0)
+    {
+      /* Update the Ethernet header with the correct MAC address */
+
+#ifdef CONFIG_NET_IPv4
+      if (IFF_IS_IPv4(self->sk_dev.d_flags))
+        {
+          arp_out(&self->sk_dev);
+        }
+#endif
+
+#ifdef CONFIG_NET_IPv6
+      if (IFF_IS_IPv6(self->sk_dev.d_flags))
+        {
+          neighbor_out(&self->sk_dev);
+        }
+#endif
+
+      /* And send the packet */
+
+      w5500_transmit(self);
+    }
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_receive(FAR struct w5500_driver_s *self)
+{
+  do
+    {
+      uint16_t s0_rx_rd;
+      uint16_t s0_rx_rsr;
+      uint16_t pktlen;
+      int ret;
+
+      /* Check if the packet is a valid size for the network buffer
+       * configuration.
+       */
+
+      ret = w5500_read16_atomic(self,
+                                W5500_BSB_SOCKET_REGS(0),
+                                W5500_SN_RX_RSR0,
+                                &s0_rx_rsr);
+      if (ret != OK)
+        {
+          goto error;
+        }
+
+      if (s0_rx_rsr == 0)
+        {
+          ninfo("No data left to read.  We are done.\n");
+          break;
+        }
+
+      /* The W5500 prepends each packet with a 2-byte length field in
+       * network byte order.  The length value includes the length field
+       * itself.  At least this 2-byte packet length must be available.
+       */
+
+      if (s0_rx_rsr < sizeof(pktlen))
+        {
+          nerr("Received size too small. S0_RX_RSR %"PRIu16"\n", s0_rx_rsr);
+          goto error;
+        }
+
+      /* Get the Socket 0 RX Read Pointer. */
+
+      s0_rx_rd = w5500_read16(self,
+                              W5500_BSB_SOCKET_REGS(0),
+                              W5500_SN_RX_RD0);
+
+      /* Read 16-bit length field. */
+
+      pktlen = w5500_read16(self,
+                            W5500_BSB_SOCKET_RX_BUFFER(0),
+                            s0_rx_rd);
+
+      if (pktlen > s0_rx_rsr)
+        {
+          nerr("Incomplete packet: pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+               pktlen,
+               s0_rx_rd);
+
+          goto error;
+        }
+
+      if (pktlen < s0_rx_rsr)
+        {
+          ninfo("More than one packet in RX buffer. "
+                "pktlen %"PRIu16", S0_RX_RSR %"PRIu16"\n",
+                pktlen,
+                s0_rx_rsr);
+        }
+
+      self->sk_dev.d_len = pktlen - sizeof(pktlen);
+
+      /* Copy the data data from the hardware to priv->sk_dev.d_buf.  Set
+       * amount of data in priv->sk_dev.d_len
+       */
+
+      if (self->sk_dev.d_len <= CONFIG_NET_ETH_PKTSIZE)
+        {
+          w5500_read(self,
+                     W5500_BSB_SOCKET_RX_BUFFER(0),
+                     s0_rx_rd + sizeof(pktlen),
+                     self->sk_dev.d_buf,
+                     self->sk_dev.d_len);
+        }
+
+      /* Acknowledge data receiption to W5500 */
+
+      w5500_write16(self,
+                    W5500_BSB_SOCKET_REGS(0),
+                    W5500_SN_RX_RD0,
+                    s0_rx_rd + pktlen);
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_CR,
+                   SN_CR_RECV);
+
+      /* Check for errors and update statistics */
+
+      if (self->sk_dev.d_len > CONFIG_NET_ETH_PKTSIZE ||
+          self->sk_dev.d_len < ETH_HDRLEN)
+        {
+          nerr("Bad packet size dropped (%"PRIu16")\n", self->sk_dev.d_len);
+          self->sk_dev.d_len = 0;
+          NETDEV_RXERRORS(&priv->dev);
+          continue;
+        }
+
+#ifdef CONFIG_DEBUG_NET_INFO
+      ninfodumpbuffer("Received Packet:",
+                      self->sk_dev.d_buf,
+                      self->sk_dev.d_len);
+#endif
+
+#ifdef CONFIG_NET_PKT
+      /* When packet sockets are enabled, feed the frame into the tap */
+
+      pkt_input(&self->sk_dev);
+#endif
+
+#ifdef CONFIG_NET_IPv4
+      /* Check for an IPv4 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP))
+        {
+          ninfo("IPv4 frame\n");
+          NETDEV_RXIPV4(&self->sk_dev);
+
+          /* Handle ARP on input, then dispatch IPv4 packet to the network
+           * layer.
+           */
+
+          arp_ipin(&self->sk_dev);
+          ipv4_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv4 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_IPv6
+      /* Check for an IPv6 packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_IP6))
+        {
+          ninfo("IPv6 frame\n");
+          NETDEV_RXIPV6(&self->sk_dev);
+
+          /* Dispatch IPv6 packet to the network layer */
+
+          ipv6_input(&self->sk_dev);
+
+          /* Check for a reply to the IPv6 packet */
+
+          w5500_reply(self);
+        }
+      else
+#endif
+#ifdef CONFIG_NET_ARP
+      /* Check for an ARP packet */
+
+      if (ETH_HDR->type == HTONS(ETHTYPE_ARP))
+        {
+          ninfo("ARP frame\n");
+
+          /* Dispatch ARP packet to the network layer */
+
+          arp_arpin(&self->sk_dev);
+          NETDEV_RXARP(&self->sk_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 (self->sk_dev.d_len > 0)
+            {
+              w5500_transmit(self);
+            }
+        }
+      else
+#endif
+        {
+          ninfo("Dropped frame\n");
+
+          NETDEV_RXDROPPED(&self->sk_dev);
+        }
+    }
+  while (true); /* While there are more packets to be processed */
+
+  return;
+
+error:
+  w5500_fence(self);
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static void w5500_txdone(FAR struct w5500_driver_s *self)
+{
+  /* Check for errors and update statistics */
+
+  NETDEV_TXDONE(self->sk_dev);
+
+  /* Check if there are pending transmissions. */
+
+  if (!w5500_txbuf_next(self))
+    {
+      ninfo("No further transmissions pending.\n");
+
+      /* If no further transmissions are pending, then cancel the TX timeout
+       * and disable further Tx interrupts.
+       */
+
+      wd_cancel(&self->sk_txtimeout);
+
+      /* And disable further TX interrupts. */
+
+      w5500_write8(self,
+                   W5500_BSB_SOCKET_REGS(0),
+                   W5500_SN_IMR, SN_INT_RECV);
+    }
+
+  /* In any event, poll the network for new TX data */
+
+  devif_poll(&self->sk_dev, w5500_txpoll);
+}
+
+/****************************************************************************
+ * Name: w5500_interrupt_work
+ *
+ * Description:
+ *   Perform interrupt related work from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Runs on a worker thread.
+ *
+ ****************************************************************************/
+
+static void w5500_interrupt_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+  uint8_t ir[3];
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Process pending Ethernet interrupts.  Read IR, MIR and SIR in one shot
+   * to optimize latency, although MIR is not actually used.
+   */
+
+  w5500_read(self,
+             W5500_BSB_COMMON_REGS,
+             W5500_IR,
+             ir,
+             sizeof(ir));
+
+  /* We expect none of the common (integrated network stack related)
+   * interrupts and only interrupts from socket 0.
+   */
+
+  if (ir[0] != 0)
+    {
+      nwarn("Ignoring unexpected interrupts. IR: 0x%02"PRIx8"\n", ir[0]);
+
+      w5500_write8(self,
+                   W5500_BSB_COMMON_REGS,
+                   W5500_IR,
+                   ir[0]);
+    }
+
+  if (ir[2] & ~SIR(0))
+    {
+      nwarn("Interrupt pending for unused socket. SIR: 0x%02"PRIx8"\n",
+            ir[2]);
+
+      goto error;
+    }
+
+  if (ir[2] == 0)
+    {
+      nwarn("Overinitiative interrupt work.\n");
+
+      goto done;
+    }
+
+  /* Get and clear interrupt status bits */
+
+  ir[0] = w5500_read8(self,
+                      W5500_BSB_SOCKET_REGS(0),
+                      W5500_SN_IR);
+
+  if ((ir[0] == 0) || ir[0] & ~(SN_INT_RECV | SN_INT_SEND_OK))
+    {
+      nerr("Unsupported socket interrupts: %02"PRIx8"\n", ir[0]);
+
+      goto error;
+    }
+
+  w5500_write8(self,
+               W5500_BSB_SOCKET_REGS(0),
+               W5500_SN_IR,
+               ir[0]);
+
+  /* Handle interrupts according to status bit settings */
+
+  /* Check if a packet transmission just completed.  If so, call
+   * w5500_txdone.  This may disable further Tx interrupts if there are no
+   * pending transmissions.
+   */
+
+  if (ir[0] & SN_INT_SEND_OK)
+    {
+      w5500_txdone(self);
+    }
+
+  /* Check if we received an incoming packet, if so, call w5500_receive() */
+
+  if (ir[0] & SN_INT_RECV)
+    {
+      w5500_receive(self);
+    }
+
+done:
+  net_unlock();
+
+  /* Re-enable Ethernet interrupts */
+
+  self->lower->enable(self->lower, true);
+
+  return;
+
+error:
+  w5500_fence(self);
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_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:
+ *   Runs in the context of a the Ethernet interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static int w5500_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  DEBUGASSERT(self != NULL);
+
+  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
+   * also disabled if the TX timeout event occurs, there can be no race
+   * condition here.
+   */
+
+  self->lower->enable(self->lower, false);
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_interrupt_work, self, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_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
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Increment statistics and dump debug info */
+
+  NETDEV_TXTIMEOUTS(self->sk_dev);
+
+  /* Then reset the hardware */
+
+  if (w5500_unfence(self) == OK)
+    {
+      self->lower->enable(self->lower, true);
+
+      /* Then poll the network for new XMIT data */
+
+      devif_poll(&self->sk_dev, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txtimeout_expiry
+ *
+ * Description:
+ *   Our TX watchdog timed out.  Called from the timer interrupt handler.
+ *   The last TX never completed.  Reset the hardware and start again.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_txtimeout_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_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.
+   */
+
+  w5500_fence(self);
+
+  /* Schedule to perform the TX timeout processing on the worker thread. */
+
+  work_queue(ETHWORK, &self->sk_irqwork, w5500_txtimeout_work, self, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_poll_work
+ *
+ * Description:
+ *   Perform periodic polling from the worker thread
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() as called.
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *   Run on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *self = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Perform the poll */
+
+  /* Check if there is room in the send another TX packet.  We cannot perform
+   * the TX poll if we are unable to accept another packet for transmission.
+   */
+
+  if (w5500_txbuf_numfree(self) > 0)
+    {
+      /* If so, update TCP timing states and poll the network for new XMIT
+       * data.  Hmmm.. might be bug here.  Does this mean if there is a
+       * transmit in progress, we will missing TCP time state updates?
+       */
+
+      devif_timer(&self->sk_dev, W5500_WDDELAY, w5500_txpoll);
+    }
+
+  /* Setup the watchdog poll timer again */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_poll_expiry
+ *
+ * Description:
+ *   Periodic timer handler.  Called from the timer interrupt handler.
+ *
+ * Input Parameters:
+ *   arg  - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs in the context of a the timer interrupt handler.  Local
+ *   interrupts are disabled by the interrupt logic.
+ *
+ ****************************************************************************/
+
+static void w5500_poll_expiry(wdparm_t arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Schedule to perform the interrupt processing on the worker thread. */
+
+  work_queue(ETHWORK, &priv->sk_pollwork, w5500_poll_work, priv, 0);
+}
+
+/****************************************************************************
+ * Name: w5500_ifup
+ *
+ * Description:
+ *   NuttX Callback: Bring up the Ethernet interface when an IP address is
+ *   provided
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifup(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  int ret;
+
+#ifdef CONFIG_NET_IPv4
+  ninfo("Bringing up: %d.%d.%d.%d\n",
+        (int)dev->d_ipaddr & 0xff,
+        (int)(dev->d_ipaddr >> 8) & 0xff,
+        (int)(dev->d_ipaddr >> 16) & 0xff,
+        (int)dev->d_ipaddr >> 24);
+#endif
+#ifdef CONFIG_NET_IPv6
+  ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+        dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
+        dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
+        dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
+#endif
+
+  /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
+
+  ret = w5500_unfence(self);
+
+  if (ret != OK)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_NET_ICMPv6
+  /* Set up IPv6 multicast address filtering */
+
+  w5500_ipv6multicast(self);
+#endif
+
+  /* Set and activate a timer process */
+
+  wd_start(&self->sk_txpoll, W5500_WDDELAY,
+           w5500_poll_expiry, (wdparm_t)self);
+
+  /* Enable the Ethernet interrupt */
+
+  self->sk_bifup = true;
+  self->lower->enable(self->lower, true);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_ifdown
+ *
+ * Description:
+ *   NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_ifdown(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *self =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  irqstate_t flags;
+
+  /* Disable the Ethernet interrupt */
+
+  flags = enter_critical_section();
+  self->lower->enable(self->lower, false);
+
+  /* Cancel the TX poll timer and TX timeout timers */
+
+  wd_cancel(&self->sk_txpoll);
+  wd_cancel(&self->sk_txtimeout);
+
+  /* Put the EMAC in its reset, non-operational state.  This should be
+   * a known configuration that will guarantee the w5500_ifup() always
+   * successfully brings the interface back up.
+   */
+
+  w5500_fence(self);
+
+  /* Mark the device "down" */
+
+  self->sk_bifup = false;
+  leave_critical_section(flags);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_txavail_work
+ *
+ * Description:
+ *   Perform an out-of-cycle poll on the worker thread.
+ *
+ * Input Parameters:
+ *   arg - Reference to the NuttX driver state structure (cast to void*)
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Runs on a work queue thread.
+ *
+ ****************************************************************************/
+
+static void w5500_txavail_work(FAR void *arg)
+{
+  FAR struct w5500_driver_s *priv = (FAR struct w5500_driver_s *)arg;
+
+  /* Lock the network and serialize driver operations if necessary.
+   * NOTE: Serialization is only required in the case where the driver work
+   * is performed on an LP worker thread and where more than one LP worker
+   * thread has been configured.
+   */
+
+  net_lock();
+
+  /* Ignore the notification if the interface is not yet up */
+
+  if (priv->sk_bifup)
+    {
+      /* Check if there is room in the hardware to hold another packet. */
+
+      /* If so, then poll the network for new XMIT data */
+
+      devif_timer(&priv->sk_dev, 0, w5500_txpoll);
+    }
+
+  net_unlock();
+}
+
+/****************************************************************************
+ * Name: w5500_txavail
+ *
+ * Description:
+ *   Driver callback invoked when new TX data is available.  This is a
+ *   stimulus perform an out-of-cycle poll and, thereby, reduce the TX
+ *   latency.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+static int w5500_txavail(FAR struct net_driver_s *dev)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_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->sk_pollwork))
+    {
+      /* Schedule to serialize the poll on the worker thread. */
+
+      work_queue(ETHWORK, &priv->sk_pollwork, w5500_txavail_work, priv, 0);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: w5500_addmac
+ *
+ * Description:
+ *   NuttX Callback: Add the specified MAC address to the hardware multicast
+ *   address filtering
+ *
+ * Input Parameters:
+ *   dev  - Reference to the NuttX driver state structure
+ *   mac  - The MAC address to be added
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
+static int w5500_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* Add the MAC address to the hardware multicast routing table */
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: w5500_rmmac
+ *
+ * Description:
+ *   NuttX Callback: Remove the specified MAC address from the hardware
+ *   multicast address filtering
+ *
+ * Input Parameters:
+ *   dev  - Reference to the NuttX driver state structure
+ *   mac  - The MAC address to be removed
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_MCASTGROUP
+static int w5500_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_driver_s *)dev->d_private;
+
+  /* Add the MAC address to the hardware multicast routing table */
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: w5500_ipv6multicast
+ *
+ * Description:
+ *   Configure the IPv6 multicast MAC address.
+ *
+ * Input Parameters:
+ *   priv - A reference to the private driver state structure
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_ICMPv6
+static void w5500_ipv6multicast(FAR struct w5500_driver_s *priv)
+{
+  FAR struct net_driver_s *dev;
+  uint16_t tmp16;
+  uint8_t mac[6];
+
+  /* For ICMPv6, we need to add the IPv6 multicast address
+   *
+   * For IPv6 multicast addresses, the Ethernet MAC is derived by
+   * the four low-order octets OR'ed with the MAC 33:33:00:00:00:00,
+   * so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map
+   * to the Ethernet MAC address 33:33:00:01:00:03.
+   *
+   * NOTES:  This appears correct for the ICMPv6 Router Solicitation

Review Comment:
   ```suggestion
      * NOTES: This appears correct for the ICMPv6 Router Solicitation



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org