summaryrefslogtreecommitdiff
path: root/cmd/sbc_harness
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/sbc_harness')
-rw-r--r--cmd/sbc_harness/hw/w5500.c352
-rw-r--r--cmd/sbc_harness/hw/w5500.h58
-rw-r--r--cmd/sbc_harness/main.c14
3 files changed, 401 insertions, 23 deletions
diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c
index 993c2b5..b4000f4 100644
--- a/cmd/sbc_harness/hw/w5500.c
+++ b/cmd/sbc_harness/hw/w5500.c
@@ -1,4 +1,53 @@
+/*
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/wizchip_conf.c
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.h
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/socket.c
+ *
+ * Copyright (c) 2013, WIZnet Co., LTD.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <ORGANIZATION> nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pico/time.h> /* for sleep_ms() */
+
#include "hw/w5500.h"
+#include "config.h"
+
+/* These are the default values of the Linux kernel's
+ * net.ipv4.ip_local_port_range, so I figure they're probably good
+ * values to use. */
+#ifndef CONFIG_W5500_LOCAL_PORT_MIN
+# define CONFIG_W5500_LOCAL_PORT_MIN 32768
+#endif
+#ifndef CONFIG_W5500_LOCAL_PORT_MAX
+# define CONFIG_W5500_LOCAL_PORT_MAX 60999
+#endif
+
+/* Low-level protocol built on SPI frames. ***********************************/
/* A u8 control byte has 3 parts: block-ID, R/W, and
* operating-mode. */
@@ -56,7 +105,7 @@ void w5500_spiframe_write(struct spi *spidev, uint16_t addr, uint8_t block, void
spidev->vtable->readwritev(spidev, iov, 2);
}
-void w5500_spiframe_read(uint16_t addr, uint8_t ctl, void *data, size_t data_len) {
+void w5500_spiframe_read(struct spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
assert(spidev);
assert((block & ~CTL_MASK_BLOCK) == 0);
assert(data);
@@ -74,10 +123,7 @@ void w5500_spiframe_read(uint16_t addr, uint8_t ctl, void *data, size_t data_len
spidev->vtable->readwritev(spidev, iov, 2);
}
-void _w5500_init(struct w5500 *self, struct spi* spi, uint pin_intr) {
- self->spidev = spi;
- gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, cbfn);
-}
+/* Offsets and sizes for use with that protocol. *****************************/
struct w5500_block_common_reg {
uint8_t mode; /* MR */
@@ -103,7 +149,7 @@ struct w5500_block_common_reg {
uint8_t ppp_max_seg_size[2]; /* PMRU0 ... PMRU1 */
uint8_t unreachable_ip_addr[4]; /* UIPR0 ... UIPR3 */
- uint8_t unreachable_port[2]; /* UPORTR0, UPROTR1 */
+ uint8_t unreachable_port[2]; /* UPORTR0, UPORTR1 */
uint8_t phy_cfg; /* PHYCFGR */
@@ -114,14 +160,14 @@ struct w5500_block_common_reg {
static_assert(sizeof(struct w5500_block_common_reg) == 0x3A);
struct w5500_block_sock_reg {
- uint8_t mode; /* Sn_MR */
- uint8_t command; /* Sn_CR */
+ uint8_t mode; /* Sn_MR; see MODE_{x} below */
+ uint8_t command; /* Sn_CR; see CMD_{x} below */
uint8_t interrupt; /* Sn_IR */
- uint8_t status; /* Sn_SR */
- uint8_t src_port[2]; /* Sn_PORT0, Sn_PORT1 */
- uint8_t dst_eth_addr[6]; /* Sn_DHAR0 ... SnDHAR5 */
- uint8_t dst_ip_addr[4]; /* Sn_DIPR0 ... Sn_DIP3 */
- uint8_t dst_port[2]; /* Sn_DPORT0 ... Sn_DPORT1 */
+ uint8_t status; /* Sn_SR; see STATUS_{x} below */
+ uint8_t local_port[2]; /* Sn_PORT0, Sn_PORT1 */
+ uint8_t remote_eth_addr[6]; /* Sn_DHAR0 ... SnDHAR5 */
+ uint8_t remote_ip_addr[4]; /* Sn_DIPR0 ... Sn_DIP3 */
+ uint8_t remote_port[2]; /* Sn_DPORT0 ... Sn_DPORT1 */
uint8_t max_seg_size[2]; /* Sn_MSSR0, Sn_MSSR1 */
uint8_t _reserved0[1];
@@ -144,8 +190,282 @@ struct w5500_block_sock_reg {
};
static_assert(sizeof(struct w5500_block_sock_reg) == 0x30);
+/* low 4 bits are the main enum, high 4 bits are flags */
+#define MODE_CLOSED ((uint8_t)0b0000)
+#define MODE_TCP ((uint8_t)0b0001)
+#define MODE_UDP ((uint8_t)0b0010)
+#define MODE_MACRAW ((uint8_t)0b0100)
+
+#define MODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5))
+
+#define MODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7))
+#define MODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6))
+#define MODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5))
+#define MODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4))
+
+#define MODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7))
+#define MODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6))
+#define MODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5))
+#define MODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4))
+
+#define CMD_OPEN ((uint8_t)0x01)
+#define CMD_LISTEN ((uint8_t)0x02) /* TCP-only */
+#define CMD_CONNECT ((uint8_t)0x04) /* TCP-only: dial */
+#define CMD_DISCON ((uint8_t)0x08) /* TCP-only: send/reply FIN */
+#define CMD_CLOSE ((uint8_t)0x10)
+#define CMD_SEND ((uint8_t)0x20)
+#define CMD_SEND_MAC ((uint8_t)0x21) /* UDP-only: send to remote_eth_addr without doing ARP on remote_ip_addr */
+#define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */
+#define CMD_RECV ((uint8_t)0x40)
+
+#define STATUS_CLOSED ((uint8_t)0x00)
+
+/* The status modes starting with an underscore are expected to be
+ * very short-lived. */
-struct w5500_socket {
- struct spi *spidev;
- uint8_t socknum; /* 0-7 */
+#define STATUS_TCP_INIT ((uint8_t)0x13)
+#define STATUS_TCP_LISTEN ((uint8_t)0x14)
+#define _STATUS_TCP_SYNSENT ((uint8_t)0x15) /* in the CMD_CONNECT INIT->SYNSENT->ESTABLISHED transition */
+#define _STATUS_TCP_SYNRECV ((uint8_t)0x16) /* in the CMD_LISTEN LISTEN->SYNRECV->ESTABLISHED transition */
+#define STATUS_TCP_ESTABLISHED ((uint8_t)0x17)
+/* The "graceful shutdown" state diagram (with the exception that
+ * reading the W5500's "status" register does not distinguish between
+ * FIN_WAIT_1 and FIN_WAIT_2; it just has a single FIN_WAIT):
+ *
+ * ║
+ * V
+ * ┌─────────────┐
+ * "active close" │ ESTABLISHED │ "passive close"
+ * (we send FIN first) └─────────────┘ (we send FIN second)
+ * V V
+ * ╔═══════════╝ ╚═════════╗
+ * (CMD_DISCON) ║
+ * ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┈┈┐ ║
+ * ┊ ║ └┈┈┈┈┈┈┈>(recv FIN)
+ * ┊ V ┌┈┈┈┈┈┈┈<(send ACK)
+ * "double active" ┊ ┌────────────┐ ┊ ║
+ * (both sides think ┊ │ FIN_WAIT_1 │ ┊ V
+ * they sent FIN first) ┊ └────────────┘ ┊ ┌────────────┐
+ * ┊ V V ┊ │ CLOSE_WAIT │
+ * ╔═════════════════╝ ╔══╝ ┊ └────────────┘
+ * (recv FIN)<┈┈┈┈┈┤ ║ ┊ V
+ * ┌┈┈<(send ACK)>┈┈┈┐ ┊ ║ ┊ ║
+ * ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┈┈┘ ║
+ * ┊ ║ ┊ ║ ║
+ * ┊ V ┊ V ║
+ * ┊ ┌─────────┐ ┊ ┌────────────┐ ║
+ * ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ║
+ * ┊ └─────────┘ ┊ └────────────┘ ║
+ * ┊ V ┊ V (CMD_DISCON)
+ * ┊ ║ ┊ ║ ┌┈┈┈┈┈┈┈<(send FIN)
+ * ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┈┈┘ ║
+ * ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┈┈┐ V
+ * └┈┈>(recv ACK)<┈┈┈┘ ║ ┊ ┌──────────┐
+ * ╚═════════════════╗ ╚═╗ ┊ │ LAST_ACK │
+ * V V ┊ └──────────┘
+ * ┌───────────┐ ┊ V
+ * │ TIME_WAIT │ ┊ ║
+ * └───────────┘ └┈┈┈┈┈┈┈>(recv ACK)
+ * V ║
+ * ║ ║
+ * (2*MSL has elapsed) ║
+ * ╚═══════════╗ ╔══════════╝
+ * V V
+ * ┌────────┐
+ * │ CLOSED │
+ * └────────┘
+ *
+ * One can still write in CLOSE_WAIT, and can still read in FIN_WAIT.
+ */
+#define _STATUS_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */
+#define _STATUS_TCP_CLOSING ((uint8_t)0x1a) /* during active close */
+#define _STATUS_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */
+#define STATUS_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */
+#define _STATUS_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */
+
+#define STATUS_UDP ((uint8_t)0x22)
+
+#define STATUS_MACRAW ((uint8_t)0x42)
+
+#define REGWRITE_COMMON(spidev, field, val) do { \
+ assert(sizeof(val) == sizeof((struct w5500_block_common_reg).field)); \
+ w5500_spiframe_write(spidev, \
+ offsetof(struct w5500_block_common_reg, field), \
+ CTL_BLOCK_COMMON_REG, \
+ &(val), \
+ sizeof(struct w5500_block_common_reg, field)); \
+ } while (0)
+
+#define REGREAD_COMMON(spidev, field, typ) ({ \
+ typ val; \
+ assert(sizeof(val) == sizeof((struct w5500_block_sock_reg).field)); \
+ w5500_spiframe_read(spidev, \
+ offsetof(struct w5500_block_sock_reg, field), \
+ CTL_BLOCK_COMMON, \
+ &(val), \
+ sizeof(struct w5500_block_sock_reg, field)); \
+ val; \
+ })
+
+#define REGWRITE_SOCK(spidev, socknum, field, val) do { \
+ assert(sizeof(val) == sizeof((struct w5500_block_sock_reg).field)); \
+ w5500_spiframe_write(spidev, \
+ offsetof(struct w5500_block_sock_reg, field), \
+ CTL_BLOCK_SOCK(socknum, REG), \
+ &(val), \
+ sizeof(struct w5500_block_sock_reg, field)); \
+ } while (0)
+
+#define REGREAD_SOCK(spidev, socknum, field, typ) ({ \
+ typ val; \
+ assert(sizeof(val) == sizeof((struct w5500_block_sock_reg).field)); \
+ w5500_spiframe_read(spidev, \
+ offsetof(struct w5500_block_sock_reg, field), \
+ CTL_BLOCK_SOCK(socknum, REG), \
+ &(val), \
+ sizeof(struct w5500_block_sock_reg, field)); \
+ val; \
+ })
+
+/* init() *********************************************************************/
+
+static struct libnet_conn *w5500_accept(struct libnet_listener *_listener);
+static ssize_t w5500_read(struct libnet_conn *conn, void *buf, size_t count);
+static ssize_t w5500_write(struct libnet_conn *conn, void *buf, size_t count);
+static int w5500_close(struct libnet_conn *conn, bool rd, bool wr);
+
+static struct libnet_listener_vtable w5500_listener_vtable = {
+ .accept = w5500_accept,
};
+
+static struct libnet_conn_vtable w5500_conn_vtable = {
+ .read = w5500_read,
+ .write = w5500_write,
+ .close = w5500_close,
+};
+
+uint16_t w5500_get_local_port(struct w5500 *self) {
+ uint16_t ret = self->_next_local_port++;
+ if (self->_next_local_port > CONFIG_W5500_LOCAL_PORT_MAX)
+ self->_next_local_port = CONFIG_W5500_LOCAL_PORT_MIN;
+ return ret;
+}
+
+void _w5500_init(struct w5500 *chip,
+ struct spi* spi, uint pin_intr, uint pin_reset,
+ struct eth_addr addr) {
+ chip->spidev = spi;
+ chip->pin_reset = pin_reset;
+ for (uint8_t i = 0; i < 8; i++) {
+ chip->_listeners[i].vtable = &w5500_listener_vtable;
+ chip->_listeners[i].chip = chip;
+ chip->_listeners[i].socknum = i;
+ chip->_listeners[i].active_conn.vtable = &w5500_conn_vtable;
+ chip->_listeners[i].active_conn.parent_listener = &chip->_listeners[i];
+ }
+ chip->_next_local_port = CONFIG_W5500_LOCAL_PORT_MIN;
+
+ gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, cbfn);
+ gpio_set_dir(chip->pin_reset, GPIO_OUT);
+
+ w5500_reset(chip);
+
+ REGWRITE_COMMON(spi, eth_addr, addr);
+}
+
+
+void w5500_reset(struct w5500 *chip) {
+ /* TODO: Replace blocking sleep_ms() with something libcr-friendly. */
+ gpio_put(chip->pin_reset, 0)
+ sleep_ms(1); /* minimum of 500us */
+ gpio_put(chip->pin_reset, 1)
+ sleep_ms(2); /* minimum of 1ms */
+}
+
+void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) {
+ REGWRITE_COMMON(chip->spidev, ip_gateway_addr, cfg.gateway_addr);
+ REGWRITE_COMMON(chip->spidev, ip_subnet_mask, cfg.subnet_mask);
+ REGWRITE_COMMON(chip->spidev, ip_addr, cfg.addr);
+}
+
+/* listen() *******************************************************************/
+
+struct libnet_listener *w5500_listen(struct w5500 *chip, uint8_t socknum, uint16_t port) {
+ assert(chip);
+ assert(socknum < 8);
+ assert(port);
+
+ w5500_close(&chip->_listeners[socknum].active_socket, true, true);
+ REGWRITE_SOCK(chip->spidev, socknum, mode, MODE_TCP);
+ REGWRITE_SOCK(chip->spidev, socknum, local_port, (uint8_t[2]){port>>8, port});
+ REGWRITE_SOCK(chip->spidev, socknum, command, CMD_OPEN);
+ while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
+ cr_yield();
+ while (REGREAD_SOCK(spidev, socknum, status, uint8_t) != STATUS_TCP_INIT)
+ cr_yield();
+
+ return (struct libnet_listener *)&chip->_listeners[socknum];
+}
+
+/* accept() *******************************************************************/
+
+#define ASSERT_LISTENER() \
+ struct w5500_listener *self = (struct w5500_listener *)_self; \
+ assert(self); \
+ \
+ struct w5500 *chip = self->chip; \
+ uint8_t socknum = self->socknum; \
+ assert(chip); \
+ assert(socknum < 8) \
+
+static struct libnet_conn *w5500_accept(struct libnet_listener *_self) {
+ ASSERT_LISTENER();
+
+ REGWRITE_SOCK(chip->spidev, socknum, command, CMD_LISTEN);
+ while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
+ cr_yield();
+ for (;;) {
+ uint8_t mode = REGREAD_SOCK(spidev, socknum, status, uint8_t);
+ switch (mode) {
+ case STATUS_TCP_LISTEN:
+ case _STATUS_TCP_SYNRECV:
+ cr_yield();
+ break;
+ default:
+ return &self->active_conn;
+ }
+ }
+}
+
+/* write() ********************************************************************/
+
+#define ASSERT_CONN() \
+ struct w5500_conn *self = (struct w5500_conn *)_self; \
+ assert(self); \
+ \
+ struct w5500 *chip = self->parent_listener->chip; \
+ uint8_t socknum = self->parent_listener->socknum; \
+ assert(chip); \
+ assert(socknum < 8)
+
+
+static ssize_t w5500_write(struct libnet_conn *_self, void *buf, size_t count) {
+ ASSERT_CONN();
+ assert(buf);
+ assert(count);
+
+
+}
+
+static int w5500_close(struct libnet_conn *_self, bool rd, bool wr) {
+ ASSERT_CONN();
+
+ REGWRITE_SOCK(chip->spidev, socknum, command, CMD_CLOSE);
+ while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
+ cr_yield();
+ REGWRITE_SOCK(chip->spidev, socknum, interrupt, (uint8_t)0xff);
+ while (REGREAD_SOCK(chip->spidev, socknum, status, uint8_t) != STATUS_CLOSED)
+ cr_yield();
+
+ return 0;
+}
diff --git a/cmd/sbc_harness/hw/w5500.h b/cmd/sbc_harness/hw/w5500.h
index 0f37797..90d4130 100644
--- a/cmd/sbc_harness/hw/w5500.h
+++ b/cmd/sbc_harness/hw/w5500.h
@@ -1,16 +1,38 @@
#ifndef _HW_W5500_H_
#define _HW_W5500_H_
+#include <libnet/libnet.h>
+
#include "hw/spi.h"
+struct _w5500_listener;
+
+struct _w5500_conn {
+ struct libnet_conn_vtable *vtable;
+
+ struct _w5500_listener *parent_listener;
+};
+
+struct _w5500_listener {
+ struct libnet_listener_vtable *vtable;
+
+ struct w5500 *chip;
+ uint8_t socknum;
+ struct _w5500_conn active_conn;
+};
+
struct w5500 {
- struct spi *spidev;
+ struct spi *spidev;
+ uint pin_reset;
+
+ struct _w5500_listener _listeners[8];
+ uint16_t _next_local_port;
};
/**
- * Initialize a WIZnet W5500 Ethernet and TCP/IP-offload chip.
+ * Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip.
*
- * The W5500 has 2 channels of communication with the MCU:
+ * The W5500 has 3 channels of communication with the MCU:
*
* - An SPI-based RPC protocol:
* + mode: mode 0 or mode 3
@@ -19,11 +41,33 @@ struct w5500 {
* - An interrupt pin that it pulls low when an event happens (to let
* the MCU know that it should do an SPI RPC "get" to see what
* happened.)
+ * - A reset pin that the MCU can pull low to reset the W5500.
*/
-#define w5500_init(self, name, spi, pin_intr) do { \
- bi_decl(bi_1_pin_with_name(pin_intr, name" interrupt")); \
- _w5500_init(self, spi, pin_intr); \
+#define w5500_init(self, name, spi, pin_intr, pin_reset, eth_addr) do { \
+ bi_decl(bi_2_pins_with_names(pin_intr, name" interrupt"), \
+ pin_reset, name" reset"); \
+ _w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \
} while (0)
-void _w5500_init(struct w5500 *self, struct spi* spi, uint pin_intr);
+void _w5500_init(struct w5500 *self,
+ struct spi* spi, uint pin_intr,
+ struct eth_addr addr);
+
+/**
+ * TODO.
+ */
+void w5500_reset(struct w5500 *self);
+
+struct w5500_netcfg {
+ struct ip4_addr gateway_addr;
+ struct ip4_addr subnet_mask;
+ struct ip4_addr addr;
+};
+
+/**
+ * TODO.
+ */
+void w5500_netcfg(struct w5500 *self, struct w5500_netcfg cfg);
+
+struct libnet_listener *w5500_listen(struct w5500 *self, uint8_t socknum, uint16_t port);
#endif /* _HW_W5500_H_ */
diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c
index 9f32037..1200608 100644
--- a/cmd/sbc_harness/main.c
+++ b/cmd/sbc_harness/main.c
@@ -12,6 +12,7 @@
#include <libusb/usb_common.h>
#include "hw/rp2040_hwspi.h"
+#include "hw/w5500.h"
#include "usb_keyboard.h"
COROUTINE hello_world_cr(void *_chan) {
@@ -34,6 +35,19 @@ int main() {
/* initialization */
stdio_uart_init();
+ struct rp2040_hwspi dev_spi;
+ struct w5500 dev_w5500;
+ rp2040_hwspi_init(&dev_spi, "W5500", RP2040_HWSPI_0,
+ SPI_MODE_0, /* the W5500 supports mode 0 or mode 3 */
+ 80*1000*1000, /* run at the W5500's max rate of 80MHz */
+ 16, /* PIN_MISO */
+ 19, /* PIN_MOSI */
+ 18, /* PIN_CLK */
+ 17); /* PIN_CS */
+ w5500_init(&dev_w5500, "W5500", &dev_spi,
+ 21, /* PIN_INTR */
+ 20); /* PIN_RESET */
+
usb_common_earlyinit();
usb_keyboard_init();
usb_common_lateinit();