You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2022/06/03 05:01:28 UTC

[incubator-nuttx] branch master updated: Add driver for WIZnet W5500 Ethernet controller

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9be3848491 Add driver for WIZnet W5500 Ethernet controller
9be3848491 is described below

commit 9be3848491af54417eacc39324d5739e84092afd
Author: Michael Jung <mi...@secore.ly>
AuthorDate: Tue May 31 12:46:49 2022 +0200

    Add driver for WIZnet W5500 Ethernet controller
    
    A device driver based on drivers/net/skeleton.c, which uses the W5500 in
    MACRAW mode (i.e. bypassing the integrated protocol stack).
    
    Signed-off-by: Michael Jung <mi...@secore.ly>
---
 drivers/net/Kconfig       |   21 +
 drivers/net/Make.defs     |    4 +
 drivers/net/w5500.c       | 2232 +++++++++++++++++++++++++++++++++++++++++++++
 include/nuttx/net/w5500.h |  251 +----
 4 files changed, 2267 insertions(+), 241 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 26899f2522..2d9d1e54e3 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -351,6 +351,27 @@ menuconfig NET_LAN91C111
 if NET_LAN91C111
 endif # NET_LAN91C111
 
+menuconfig NET_W5500
+	bool "WIZnet W5500 Support"
+	default n
+	select SPI
+	select ARCH_HAVE_NETDEV_STATISTICS
+	---help---
+		References:
+		W5500 Datasheet, Version 1.0.9, 2013 WIZnet Co., Ltd.
+
+if NET_W5500
+
+config NET_W5500_NINTERFACES
+	int "Number of physical W5500 devices"
+	default 1
+	range 1 1
+	---help---
+		Specifies the number of physical WIZnet W5500
+		devices that will be supported.
+
+endif # W5500
+
 if ARCH_HAVE_PHY
 
 comment "External Ethernet PHY Device Support"
diff --git a/drivers/net/Make.defs b/drivers/net/Make.defs
index 1735c8e21d..708100ac97 100644
--- a/drivers/net/Make.defs
+++ b/drivers/net/Make.defs
@@ -64,6 +64,10 @@ ifeq ($(CONFIG_NET_LAN91C111),y)
   CSRCS += lan91c111.c
 endif
 
+ifeq ($(CONFIG_NET_W5500),y)
+  CSRCS += w5500.c
+endif
+
 ifeq ($(CONFIG_ARCH_PHY_INTERRUPT),y)
   CSRCS += phy_notify.c
 endif
diff --git a/drivers/net/w5500.c b/drivers/net/w5500.c
new file mode 100644
index 0000000000..18ae561b9b
--- /dev/null
+++ b/drivers/net/w5500.c
@@ -0,0 +1,2232 @@
+/****************************************************************************
+ * 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 timeout = 1 minute
+ * CLK_TCK is the number of clock ticks per second
+ */
+
+#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_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);
+
+/* 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 whether 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 -EBUSY;
+            }
+        }
+    }
+
+  /* If zero is returned, the polling will continue until all connections
+   * have been examined.
+   */
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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_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
+
+  /* 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 timeout timer */
+
+  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_poll(&priv->sk_dev, 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
+   * address that we expect to receive ICMPv6 Router Solicitation
+   * packets.
+   */
+
+  w5500_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet);
+
+#endif /* CONFIG_NET_ICMPv6_ROUTER */
+}
+#endif /* CONFIG_NET_ICMPv6 */
+
+/****************************************************************************
+ * Name: w5500_ioctl
+ *
+ * Description:
+ *   Handle network IOCTL commands directed to this device.
+ *
+ * Input Parameters:
+ *   dev - Reference to the NuttX driver state structure
+ *   cmd - The IOCTL command
+ *   arg - The argument for the IOCTL command
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_IOCTL
+static int w5500_ioctl(FAR struct net_driver_s *dev, int cmd,
+                      unsigned long arg)
+{
+  FAR struct w5500_driver_s *priv =
+    (FAR struct w5500_driver_s *)dev->d_private;
+  int ret;
+
+  /* Decode and dispatch the driver-specific IOCTL command */
+
+  switch (cmd)
+    {
+      /* Add cases here to support the IOCTL commands */
+
+      default:
+        nerr("ERROR: Unrecognized IOCTL command: %d\n", command);
+        return -ENOTTY;  /* Special return value for this case */
+    }
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: w5500_initialize
+ *
+ * Description:
+ *   Initialize the Ethernet controller and driver
+ *
+ * Parameters:
+ *   spi   - A reference to the platform's SPI driver for the W5500.
+ *   lower - The lower half driver instance for this W5500 chip.
+ *   devno - If more than one W5500 is supported, then this is the
+ *           zero based number that identifies the W5500.
+ *
+ * Returned Value:
+ *   OK on success; Negated errno on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+int w5500_initialize(FAR struct spi_dev_s *spi_dev,
+                     FAR const struct w5500_lower_s *lower,
+                     unsigned int devno)
+{
+  FAR struct w5500_driver_s *self;
+
+  /* Get the interface structure associated with this interface number. */
+
+  DEBUGASSERT(devno < CONFIG_NET_W5500_NINTERFACES);
+  self = &g_w5500[devno];
+
+  /* Check if a Ethernet chip is recognized at its I/O base */
+
+  /* Attach the IRQ to the driver */
+
+  if (lower->attach(lower, w5500_interrupt, self))
+    {
+      /* We could not attach the ISR to the interrupt */
+
+      return -EAGAIN;
+    }
+
+  /* Initialize the driver structure */
+
+  memset(self, 0, sizeof(struct w5500_driver_s));
+  self->sk_dev.d_buf     = g_pktbuf;       /* Single packet buffer */
+  self->sk_dev.d_ifup    = w5500_ifup;     /* I/F up (new IP address) callback */
+  self->sk_dev.d_ifdown  = w5500_ifdown;   /* I/F down callback */
+  self->sk_dev.d_txavail = w5500_txavail;  /* New TX data callback */
+#ifdef CONFIG_NET_MCASTGROUP
+  self->sk_dev.d_addmac  = w5500_addmac;   /* Add multicast MAC address */
+  self->sk_dev.d_rmmac   = w5500_rmmac;    /* Remove multicast MAC address */
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  self->sk_dev.d_ioctl   = w5500_ioctl;    /* Handle network IOCTL commands */
+#endif
+  self->sk_dev.d_private = g_w5500;        /* Used to recover private state from dev */
+  self->spi_dev          = spi_dev;        /* SPI hardware interconnect */
+  self->lower            = lower;          /* Low-level MCU specific support */
+
+  /* Put the interface in the down state.  This usually amounts to resetting
+   * the device and/or calling w5500_ifdown().
+   */
+
+  w5500_ifdown(&self->sk_dev);
+
+  /* Register the device with the OS so that socket IOCTLs can be performed */
+
+  netdev_register(&self->sk_dev, NET_LL_ETHERNET);
+  return OK;
+}
+
+#endif /* !defined(CONFIG_SCHED_WORKQUEUE) */
+
+#endif /* CONFIG_NET_W5500 */
diff --git a/include/nuttx/net/w5500.h b/include/nuttx/net/w5500.h
index 965d62eb74..30d4c4f379 100644
--- a/include/nuttx/net/w5500.h
+++ b/include/nuttx/net/w5500.h
@@ -27,248 +27,12 @@
  ****************************************************************************/
 
 #include <nuttx/config.h>
-
+#include <nuttx/spi/spi.h>
 #include <stdint.h>
 #include <stdbool.h>
 
 #ifdef CONFIG_NET_W5500
 
-/****************************************************************************
- * Included Files
- ****************************************************************************/
-
-/* 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_SPI          (1 << 1   /* Bit 2:  Speed Status */
-#define PHYCFGR_DPX          (1 << 2)  /* Bit 3:  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
-
 /****************************************************************************
  * Public Types
  ****************************************************************************/
@@ -279,9 +43,9 @@
 
 struct w5500_lower_s
 {
-  uint32_t frequency;         /* Frequency to use with SPI_SETFREQUENCY() */
-  uint16_t spidevid;          /* Index used with SPIDEV_ETHERNET() macro */
-  enum spi_mode_e mode mode;  /* SPI more for use with SPI_SETMODE() */
+  uint32_t frequency;   /* Frequency to use with SPI_SETFREQUENCY() */
+  uint16_t spidevid;    /* Index used with SPIDEV_ETHERNET() macro */
+  enum spi_mode_e mode; /* SPI more for use with SPI_SETMODE() */
 
   /* Lower-half callbacks:
    *
@@ -307,7 +71,10 @@ struct w5500_lower_s
  *   Initialize the Ethernet controller and driver
  *
  * Parameters:
+ *   spi   - A reference to the platform's SPI driver for the W5500.
  *   lower - The lower half driver instance for this W5500 chip.
+ *   devno - If more than one W5500 is supported, then this is the
+ *           zero based number that identifies the W5500.
  *
  * Returned Value:
  *   OK on success; Negated errno on failure.
@@ -316,7 +83,9 @@ struct w5500_lower_s
  *
  ****************************************************************************/
 
-int w5500_initialize(FAR struct w5500_lower_s *lower);
+int w5500_initialize(FAR struct spi_dev_s *spi_dev,
+                     FAR const struct w5500_lower_s *lower,
+                     unsigned int devno);
 
 #endif /* CONFIG_NET_W5500 */
 #endif /* __INCLUDE_NUTTX_NET_W5500_H */