diff --git a/drivers/include/net/netdev.h b/drivers/include/net/netdev.h index ab15342261b8..12266ffa64d7 100644 --- a/drivers/include/net/netdev.h +++ b/drivers/include/net/netdev.h @@ -333,6 +333,7 @@ typedef enum { NETDEV_ESP_WIFI, NETDEV_CDC_ECM, NETDEV_TINYUSB, + NETDEV_W5500, /* add more if needed */ } netdev_type_t; /** @} */ @@ -360,11 +361,11 @@ typedef enum { * be used by upper layers to store reference information. */ struct netdev { - const struct netdev_driver *driver; /**< ptr to that driver's interface. */ - netdev_event_cb_t event_callback; /**< callback for device events */ - void *context; /**< ptr to network stack context */ + const struct netdev_driver *driver; /**< ptr to that driver's interface. */ + netdev_event_cb_t event_callback; /**< callback for device events */ + void *context; /**< ptr to network stack context */ #ifdef MODULE_NETDEV_LAYER - netdev_t *lower; /**< ptr to the lower netdev layer */ + netdev_t *lower; /**< ptr to the lower netdev layer */ #endif #ifdef MODULE_L2FILTER l2filter_t filter[CONFIG_L2FILTER_LISTSIZE]; /**< link layer address filters */ @@ -401,12 +402,12 @@ void netdev_register_signal(struct netdev *dev, netdev_type_t type, uint8_t inde static inline void netdev_register(struct netdev *dev, netdev_type_t type, uint8_t index) { #ifdef MODULE_NETDEV_REGISTER - dev->type = type; + dev->type = type; dev->index = index; #else - (void) dev; - (void) type; - (void) index; + (void)dev; + (void)type; + (void)index; #endif if (IS_ACTIVE(CONFIG_NETDEV_REGISTER_SIGNAL)) { diff --git a/drivers/include/w5500.h b/drivers/include/w5500.h new file mode 100644 index 000000000000..b6fe73f23114 --- /dev/null +++ b/drivers/include/w5500.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_w5500 W5500 ethernet driver + * @ingroup drivers_netdev + * @brief Driver for W5500 ethernet devices + * + * This device driver only exposes the MACRAW mode of W5500 devices, so it does + * not offer any support for the on-chip IPv4, UDP, and TCP capabilities of + * these chips. In connection with RIOT we are only interested in the RAW + * Ethernet packets, which we can use through netdev with any software network + * stack provided by RIOT (e.g. GNRC). This enables W5500 devices to communicate + * via IPv6, enables unlimited connections, and more... + * + * @note This driver can be used in polling or interrupt mode. + * On some shields the interrupt line is not enabled by default, + * you have to close the corresponding solder bridge to make the + * interrupt mode work... + * + * @{ + * + * @file + * @brief Interface definition for the W5500 device driver + * + * @author Stefan Schmidt + */ + +#ifndef W5500_H +#define W5500_H + +#include + +#include "periph/spi.h" +#include "periph/gpio.h" +#include "net/netdev.h" +#include "kernel_defines.h" +#include "ztimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief W5500 device descriptor + */ +typedef struct { + spi_t spi; /**< SPI bus used */ + spi_clk_t clk; /**< clock speed used on the selected SPI bus */ + gpio_t cs; /**< pin connected to the chip select line */ + gpio_t irq; /**< pin connected to the INT line */ + uint32_t polling_interval_ms; /**< interval for polling, 0 if interrupt mode */ +} w5500_params_t; + +/** + * @brief Device descriptor for W5500 devices + */ +typedef struct w5500 { + netdev_t netdev; /**< extends the netdev structure */ + w5500_params_t p; /**< device configuration parameters */ + uint16_t frame_size; /**< size of the frame which has been send */ + bool link_up; /**< used to prevent sending the same LINK event twice */ + bool frame_sent; /**< indicates that the frame has been transmitted */ + ztimer_t timerInstance; /**< stores the polling interval timer in polling mode */ +} w5500_t; + +/** + * @brief So the initial device setup + * + * This function pre-initializes the netdev structure, saves the configuration + * parameters and finally initializes the SPI bus and the used GPIO pins. + * + * @param [out] dev the handle of the device to initialize + * @param [in] params parameters for device initialization + * @param [in] index Index of @p params in a global parameter struct array. + * If initialized manually, pass a unique identifier instead. + */ +void w5500_setup(w5500_t *dev, const w5500_params_t *params, uint8_t index); + +#ifdef __cplusplus +} +#endif + +#endif /* W5500_H */ +/** @} */ diff --git a/drivers/w5500/Kconfig b/drivers/w5500/Kconfig new file mode 100644 index 000000000000..da916efd4d85 --- /dev/null +++ b/drivers/w5500/Kconfig @@ -0,0 +1,52 @@ +# Copyright (C) 2023 Stefan Schmidt +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +menuconfig MODULE_W5500 + bool "W5500 Ethernet Adapter" + depends on HAS_PERIPH_GPIO + depends on HAS_PERIPH_GPIO_IRQ + depends on HAS_PERIPH_SPI + depends on TEST_KCONFIG + select MODULE_LUID + select MODULE_NETDEV_ETH + select MODULE_PERIPH_GPIO + select MODULE_PERIPH_GPIO_IRQ + select MODULE_PERIPH_SPI + +config HAVE_W5500 + bool + select MODULE_W5500 if MODULE_NETDEV_DEFAULT + help + Indicates that a w5500 ethernet adapter is present. + +if MODULE_W5500 + +config W5500_USE_POLLING + bool "Use driver in polling mode" + default y + +if W5500_USE_POLLING + +config W5500_POLLING_INTERVAL + int "Polling interval in ms" + default 100 + +#endif # W5500_USE_POLLING + +config W5500_MAC_FILTER + bool "Enable hardware MAC filter" + default n + +config W5500_BROADCAST_FILTER + bool "Enable hardware broadcast filter" + default n + +config W5500_MULTICAST_FILTER + bool "Enable hardware multicast filter" + default n + +endif # MODULE_W5500 diff --git a/drivers/w5500/Makefile b/drivers/w5500/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/drivers/w5500/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/w5500/Makefile.dep b/drivers/w5500/Makefile.dep new file mode 100644 index 000000000000..5233ef829a61 --- /dev/null +++ b/drivers/w5500/Makefile.dep @@ -0,0 +1,6 @@ +FEATURES_REQUIRED += periph_gpio_irq +FEATURES_REQUIRED += periph_spi +USEMODULE += luid +USEMODULE += iolist +USEMODULE += netdev_eth +USEMODULE += ztimer_msec diff --git a/drivers/w5500/Makefile.include b/drivers/w5500/Makefile.include new file mode 100644 index 000000000000..3e40a12d885d --- /dev/null +++ b/drivers/w5500/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_w5500 := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_w5500) diff --git a/drivers/w5500/include/w5500_params.h b/drivers/w5500/include/w5500_params.h new file mode 100644 index 000000000000..093187c68cd2 --- /dev/null +++ b/drivers/w5500/include/w5500_params.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_w5500 + * @{ + * + * @file + * @brief Default parameters for W5500 Ethernet devices + * + * @author Stefan Schmidt + */ + +#ifndef W5500_PARAMS_H +#define W5500_PARAMS_H + +#include "board.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Default configuration parameters for the W5500 driver + * @{ + */ +#ifndef W5500_PARAM_SPI +#define W5500_PARAM_SPI (SPI_DEV(0)) /**< Default SPI device */ +#endif +#ifndef W5500_PARAM_SPI_CLK +#define W5500_PARAM_SPI_CLK (SPI_CLK_10MHZ) /**< Default SPI speed */ +#endif +#ifndef W5500_PARAM_CS +#define W5500_PARAM_CS (GPIO_PIN(0, 27)) /**< Default SPI chip select pin */ +#endif +#ifndef W5500_PARAM_INT +#define W5500_PARAM_INT GPIO_UNDEF /**< set to invalid */ +#endif +#ifndef CONFIG_W5500_POLLING_INTERVAL +#define CONFIG_W5500_POLLING_INTERVAL 100u /**< default polling interval 100 ms */ +#endif + +#ifndef W5500_PARAMS +/** + * @brief W5500 initialization parameters + */ + +#define W5500_PARAMS { .spi = W5500_PARAM_SPI, \ + .clk = W5500_PARAM_SPI_CLK, \ + .cs = W5500_PARAM_CS, \ + .irq = W5500_PARAM_INT, \ + .polling_interval_ms = CONFIG_W5500_POLLING_INTERVAL } +#endif +/** @} */ + +/** + * @brief W5500 configuration + */ +static const w5500_params_t w5500_params[] = { + W5500_PARAMS +}; + +#ifdef __cplusplus +} +#endif + +#endif /* W5500_PARAMS_H */ +/** @} */ diff --git a/drivers/w5500/include/w5500_regs.h b/drivers/w5500/include/w5500_regs.h new file mode 100644 index 000000000000..b05b086497c9 --- /dev/null +++ b/drivers/w5500/include/w5500_regs.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_w5500 + * @{ + * + * @file + * @brief Register definitions for W5500 devices + * + * @author Stefan Schmidt + */ + +#ifndef W5500_REGS_H +#define W5500_REGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* The W5500 is accessed by sending a 16 Bit address first, then a 8 bit control byte + which determines to which register (common or one of the 8 Sockets) this address shall be + applied and finally the data. + + In order to simplify the functions to read and write to the W5500 via SPI the defined register + addresses contain the control byte in the upper 5 bits and the actual register address in the + lower 13 bits: + 0b00000 -> common register (0x0xxx) + 0b00001 -> Socket 0 register (0x08xx) + + The RX and TX buffers are accessed via separate functions in order to be able to access the full + 16 kB buffers. + */ + +/** + * @name Common registers (with BSB 00000 in the upper 5 bits). + * @{ + */ +#define REG_MODE (0x0000) /**< Mode. */ +#define REG_GAR0 (0x0001) /**< Gateway address 0. */ +#define REG_GAR1 (0x0002) /**< Gateway address 1. */ +#define REG_GAR2 (0x0003) /**< Gateway address 2. */ +#define REG_GAR3 (0x0004) /**< Gateway address 3. */ +#define REG_SUBR0 (0x0005) /**< Subnet mask 0. */ +#define REG_SUBR1 (0x0006) /**< Subnet mask 1. */ +#define REG_SUBR2 (0x0007) /**< Subnet mask 2. */ +#define REG_SUBR3 (0x0008) /**< Subnet mask 3. */ +#define REG_SHAR0 (0x0009) /**< Source hardware address 0. */ +#define REG_SHAR1 (0x000a) /**< Source hardware address 1. */ +#define REG_SHAR2 (0x000b) /**< Source hardware address 2. */ +#define REG_SHAR3 (0x000c) /**< Source hardware address 3. */ +#define REG_SHAR4 (0x000d) /**< Source hardware address 4. */ +#define REG_SHAR5 (0x000e) /**< Source hardware address 5. */ +#define REG_SIPR0 (0x000f) /**< Source IP address 0. */ +#define REG_SIPR1 (0x0010) /**< Source IP address 1. */ +#define REG_SIPR2 (0x0011) /**< Source IP address 2. */ +#define REG_SIPR3 (0x0012) /**< Source IP address 3. */ +#define REG_INTLEVEL0 (0x0013) /**< Interrupt low level timer 0. */ +#define REG_INTLEVEL1 (0x0014) /**< Interrupt low level timer 1. */ +#define REG_IR (0x0015) /**< Interrupt flags. */ +#define REG_IMR (0x0016) /**< Interrupt masks. */ +#define REG_SIR (0x0017) /**< Socket interrupt. */ +#define REG_SIMR (0x0018) /**< Socket interrupt mask. */ +#define REG_RTR0 (0x0019) /**< Retry time 0. */ +#define REG_RTR1 (0x001a) /**< Retry time 1. */ +#define REG_RCR (0x001b) /**< Retry count. */ +#define REG_PTIMER (0x001c) /**< Ppp lcp request timer. */ +#define REG_PMAGIC (0x001d) /**< Ppp lcp magic number. */ +#define REG_PHAR0 (0x001e) /**< Ppp destination mac address 0. */ +#define REG_PHAR1 (0x001f) /**< Ppp destination mac address 1. */ +#define REG_PHAR2 (0x0020) /**< Ppp destination mac address 2. */ +#define REG_PHAR3 (0x0021) /**< Ppp destination mac address 3. */ +#define REG_PHAR4 (0x0022) /**< Ppp destination mac address 4. */ +#define REG_PHAR5 (0x0023) /**< Ppp destination mac address 5. */ +#define REG_PSID0 (0x0024) /**< Ppp session identification 0. */ +#define REG_PSID1 (0x0025) /**< Ppp session identification 1. */ +#define REG_PMRU0 (0x0026) /**< Ppp maximum segment size 0. */ +#define REG_PMRU1 (0x0027) /**< Ppp maximum segment size 1. */ +#define REG_UIPR0 (0x0028) /**< Unreachable IP address 0. */ +#define REG_UIPR1 (0x0029) /**< Unreachable IP address 1. */ +#define REG_UIPR2 (0x002a) /**< Unreachable IP address 2. */ +#define REG_UIPR3 (0x002b) /**< Unreachable IP address 3. */ +#define REG_UPORT0 (0x002c) /**< Unreachable port 0. */ +#define REG_UPORT1 (0x002d) /**< Unreachable port 1. */ +#define REG_PHYCFGR (0x002e) /**< Phy configuration. */ +#define REG_VERSIONR (0x0039) /**< Chip version. */ +/** @} */ + +/** + * @name Socket 0 register block (with BSB 00001 in the upper 5 bits). + * @{ + */ +#define REG_S0_MR (0x0800) /**< Socket 0 mode */ +#define REG_S0_CR (0x0801) /**< Socket 0 command */ +#define REG_S0_IR (0x0802) /**< Socket 0 interrupt */ +#define REG_S0_SR (0x0803) /**< Socket 0 status */ +#define REG_S0_PORT0 (0x0804) /**< Socket 0 Source port 0 */ +#define REG_S0_PORT1 (0x0805) /**< Socket 0 Source port 1 */ +#define REG_S0_DHAR0 (0x0806) /**< Socket 0 destination hardware address 0 */ +#define REG_S0_DHAR1 (0x0807) /**< Socket 0 destination hardware address 1 */ +#define REG_S0_DHAR2 (0x0808) /**< Socket 0 destination hardware address 2 */ +#define REG_S0_DHAR3 (0x0809) /**< Socket 0 destination hardware address 3 */ +#define REG_S0_DHAR4 (0x080a) /**< Socket 0 destination hardware address 4 */ +#define REG_S0_DHAR5 (0x080b) /**< Socket 0 destination hardware address 5 */ +#define REG_S0_DIPR0 (0x080c) /**< Socket 0 destination IP address 0 */ +#define REG_S0_DIPR1 (0x080d) /**< Socket 0 destination IP address 1 */ +#define REG_S0_DIPR2 (0x080e) /**< Socket 0 destination IP address 2 */ +#define REG_S0_DIPR3 (0x080f) /**< Socket 0 destination IP address 3 */ +#define REG_S0_DPORT0 (0x0810) /**< Socket 0 destination port 0 */ +#define REG_S0_DPORT1 (0x0811) /**< Socket 0 destination port 1 */ +#define REG_S0_MSSR0 (0x0812) /**< Socket 0 maximum segment size 0 */ +#define REG_S0_MSSR1 (0x0813) /**< Socket 0 maximum segment size 1 */ +#define REG_S0_TOS (0x0815) /**< Socket 0 IP TOS */ +#define REG_S0_TTL (0x0816) /**< Socket 0 IP TTL */ +#define REG_S0_RXBUF_SIZE (0x081e) /**< Socket 0 receive buffer size */ +#define REG_S0_TXBUF_SIZE (0x081f) /**< Socket 0 transmit buffer size */ +#define REG_S0_TX_FSR0 (0x0820) /**< Socket 0 tx free size 0 */ +#define REG_S0_TX_FSR1 (0x0821) /**< Socket 0 tx free size 1 */ +#define REG_S0_TX_RD0 (0x0822) /**< Socket 0 tx read pointer 0 */ +#define REG_S0_TX_RD1 (0x0823) /**< Socket 0 tx read pointer 1 */ +#define REG_S0_TX_WR0 (0x0824) /**< Socket 0 tx write pointer 0 */ +#define REG_S0_TX_WR1 (0x0825) /**< Socket 0 tx write pointer 1 */ +#define REG_S0_RX_RSR0 (0x0826) /**< Socket 0 rx received size 0 */ +#define REG_S0_RX_RSR1 (0x0827) /**< Socket 0 rx received size 1 */ +#define REG_S0_RX_RD0 (0x0828) /**< Socket 0 rx read pointer 0 */ +#define REG_S0_RX_RD1 (0x0829) /**< Socket 0 rx read pointer 1 */ +#define REG_S0_RX_WR0 (0x082a) /**< Socket 0 rx write pointer 0 */ +#define REG_S0_RX_WR1 (0x082b) /**< Socket 0 rx write pointer 1 */ +#define REG_S0_IMR (0x082c) /**< Socket 0 interrupt mask */ +#define REG_S0_FRAG0 (0x082d) /**< Socket 0 fragment offset in IP header 0 */ +#define REG_S0_FRAG1 (0x082e) /**< Socket 0 fragment offset in IP header 1 */ +#define REG_S0_KPALVTR (0x082f) /**< Socket 0 keep alive timer */ +/** @} */ + +#define Sn_RXBUF_SIZE_BASE (0x001E) /**< Register to configure a sockets RX buffer size */ +#define Sn_TXBUF_SIZE_BASE (0x001F) /**< Register to configure a sockets TX buffer size */ + +/** + * @name Some selected bitfield definitions. + * @{ + */ +/* Common definitions. */ +#define MODE_RESET (0x80) /**< Device mode: reset. */ +#define PHY_LINK_UP (0x01) /**< Link up indication. */ +#define IMR_S0_INT (0x01) /**< Global Socket 0 interrupt mask. */ +#define SPI_CONF SPI_MODE_0 /**< Configure SPI MODE 0 */ +#define CHIP_VERSION (0x04) /**< Chip version we expect to read from the device */ + +#define ENABLE_MAC_FILTER (0x80) /**< Enable hardware MAC filter for raw mode */ +#define ENABLE_BROADCAST_FILTER (0x40) /**< Enable Broadcast blocking */ +#define ENABLE_MULTICAST_FILTER (0x20) /**< Enable Multicast blocking */ +#define MR_MACRAW (0x04) /**< Socket mode: raw Ethernet */ + +#define CR_OPEN (0x01) /**< Socket command: open */ +#define CR_SEND (0x20) /**< Socket command: send */ +#define CR_RECV (0x40) /**< Socket command: receive new data */ + +#define IR_RECV (0x04) /**< Socket interrupt: data received */ +#define IR_SEND_OK (0x10) /**< Socket interrupt: send ok */ + +#define CMD_READ (0x00) /**< Define for the read command */ +#define CMD_WRITE (0x04) /**< Define for the write command */ +#define SOCKET0_RX_BUFFER (0x18) /**< BSB for Socket 0 Receive Buffer. */ +#define SOCKET0_TX_BUFFER (0x10) /**< BSB for Socket 0 Transmit Buffer. */ +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* W5500_REGS_H */ +/** @} */ diff --git a/drivers/w5500/w5500.c b/drivers/w5500/w5500.c new file mode 100644 index 000000000000..0c99ddf994e5 --- /dev/null +++ b/drivers/w5500/w5500.c @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_w5500 + * @{ + * + * @file + * @brief Device driver implementation for w5500 Ethernet devices + * + * @author Stefan Schmidt + * + * @} + */ + +#include +#include +#include + +#include "assert.h" +#include "iolist.h" +#include "net/ethernet.h" +#include "net/eui_provider.h" +#include "net/netdev/eth.h" +#include "net/ethernet/hdr.h" +#include "ztimer.h" + +#include "w5500.h" +#include "w5500_regs.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" +#include "log.h" + +/* Socket definitions. */ +#define BSB_SOCKET_BASE (0x08) /**< Base for Socket n registers. */ +#define RXBUF_16KB_TO_S0 (0x10) /**< Receive memory size: 16 kB. */ +#define TXBUF_16KB_TO_S0 (0x10) /**< Transmit memory size: 16 kB. */ + +static const netdev_driver_t netdev_driver_w5500; + +static uint8_t read_register(w5500_t *dev, uint16_t reg) +{ + uint16_t address = reg & 0x07ff; + uint8_t command = (uint8_t)((reg & 0xf800) >> 8u) | CMD_READ; + + spi_transfer_u16_be(dev->p.spi, dev->p.cs, true, address); + spi_transfer_byte(dev->p.spi, dev->p.cs, true, command); + return spi_transfer_byte(dev->p.spi, dev->p.cs, false, 0u); +} + +static void write_register(w5500_t *dev, uint16_t reg, uint8_t data) +{ + uint16_t address = reg & 0x07ff; + uint8_t command = (uint8_t)((reg & 0xf800) >> 8u) | CMD_WRITE; + + spi_transfer_u16_be(dev->p.spi, dev->p.cs, true, address); + spi_transfer_byte(dev->p.spi, dev->p.cs, true, command); + spi_transfer_byte(dev->p.spi, dev->p.cs, false, data); +} + +static uint16_t read_addr(w5500_t *dev, uint16_t addr_high, uint16_t addr_low) +{ + uint16_t res = (read_register(dev, addr_high) << 8); + + res |= read_register(dev, addr_low); + return res; +} + +static void write_address(w5500_t *dev, + uint16_t addr_high, uint16_t addr_low, uint16_t val) +{ + write_register(dev, addr_high, (uint8_t)(val >> 8)); + write_register(dev, addr_low, (uint8_t)(val & 0xff)); +} + +static void read_chunk(w5500_t *dev, uint16_t addr, uint8_t *data, size_t len) +{ + uint16_t address = addr & 0x07ff; + uint8_t command = (uint8_t)((addr & 0xf800) >> 8u) | CMD_READ; + + spi_transfer_u16_be(dev->p.spi, dev->p.cs, true, address); + spi_transfer_byte(dev->p.spi, dev->p.cs, true, command); + spi_transfer_bytes(dev->p.spi, dev->p.cs, false, NULL, data, len); +} + +static void write_chunk(w5500_t *dev, uint16_t addr, uint8_t *data, size_t len) +{ + uint16_t address = addr & 0x07ff; + uint8_t command = (uint8_t)((addr & 0xf800) >> 8u) | CMD_WRITE; + + spi_transfer_u16_be(dev->p.spi, dev->p.cs, true, address); + spi_transfer_byte(dev->p.spi, dev->p.cs, true, command); + spi_transfer_bytes(dev->p.spi, dev->p.cs, false, data, NULL, len); +} + +static void extint(void *arg) +{ + w5500_t *dev = (w5500_t *)arg; + + netdev_trigger_event_isr(&dev->netdev); + if (!gpio_is_valid(dev->p.irq)) { + /* restart timer if we are polling */ + ztimer_set(ZTIMER_MSEC, &dev->timerInstance, dev->p.polling_interval_ms); + } +} + +void w5500_setup(w5500_t *dev, const w5500_params_t *params, uint8_t index) +{ + assert(dev); + assert(params); + + /* Initialize netdev structure. */ + dev->netdev.driver = &netdev_driver_w5500; + dev->netdev.event_callback = NULL; + dev->netdev.context = dev; + /* Initialize the device descriptor. */ + dev->p = *params; + + dev->frame_size = 0u; + dev->link_up = false; + dev->frame_sent = false; + + /* Initialize the chip select pin. */ + spi_init_cs(dev->p.spi, dev->p.cs); + if (gpio_is_valid(dev->p.irq)) { + /* Initialize the external interrupt pin. */ + gpio_init_int(dev->p.irq, GPIO_IN, GPIO_FALLING, extint, dev); + } + netdev_register(&dev->netdev, NETDEV_W5500, index); +} + +static int init(netdev_t *netdev) +{ + w5500_t *dev = (w5500_t *)netdev; + uint8_t tmp; + + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + + /* Reset the device. */ + write_register(dev, REG_MODE, MODE_RESET); + while (read_register(dev, REG_MODE) & MODE_RESET) {} + + /* Test the SPI connection by reading the value of the version register. */ + tmp = read_register(dev, REG_VERSIONR); + if (tmp != CHIP_VERSION) { + spi_release(dev->p.spi); + LOG_ERROR("[w5500] error: invalid chip ID %x\n", tmp); + return -ENODEV; + } + + /* Write the MAC address. */ + eui48_t address; + netdev_eui48_get(netdev, &address); + write_chunk(dev, REG_SHAR0, (uint8_t*)&address, sizeof(eui48_t)); + + /* Configure 16 kB memory to be used by socket 0. */ + write_register(dev, REG_S0_RXBUF_SIZE, RXBUF_16KB_TO_S0); + write_register(dev, REG_S0_TXBUF_SIZE, TXBUF_16KB_TO_S0); + + /* Configure remaining RX/TX buffers to 0 kB. */ + for (uint8_t socket = 1u; socket < 8u; socket++) { + uint16_t bsb = (socket << 5u) | 0x08 | CMD_WRITE; + uint16_t Sn_RXBUF_SIZE = Sn_RXBUF_SIZE_BASE | bsb; + uint16_t Sn_TXBUF_SIZE = Sn_TXBUF_SIZE_BASE | bsb; + write_register(dev, Sn_RXBUF_SIZE, 0u); + write_register(dev, Sn_TXBUF_SIZE, 0u); + } + /* Next we configure socket 0 to work in MACRAW mode with MAC filtering. */ + write_register(dev, REG_S0_MR, (MR_MACRAW | ENABLE_MAC_FILTER | ENABLE_MULTICAST_FILTER)); + + /* Set the source IP address to something random to prevent the device to do + * stupid thing (e.g. answering ICMP echo requests on its own). */ + write_register(dev, REG_SIPR0, 0x00); + write_register(dev, REG_SIPR1, 0x00); + write_register(dev, REG_SIPR2, 0x00); + write_register(dev, REG_SIPR3, 0x00); + + if (!gpio_is_valid(dev->p.irq)) { + dev->timerInstance.callback = extint; + dev->timerInstance.arg = dev; + ztimer_set(ZTIMER_MSEC, &dev->timerInstance, dev->p.polling_interval_ms); + } + else { + /* Configure interrupt pin to trigger on socket 0 events. */ + write_register(dev, REG_SIMR, IMR_S0_INT); + } + /* Open socket. */ + write_register(dev, REG_S0_CR, CR_OPEN); + + spi_release(dev->p.spi); + + return 0; +} + +static void write_tx0_buffer(w5500_t *dev, uint16_t address, uint8_t *data, uint16_t size) +{ + spi_transfer_u16_be(dev->p.spi, dev->p.cs, true, address); + spi_transfer_byte(dev->p.spi, dev->p.cs, true, SOCKET0_TX_BUFFER | CMD_WRITE); + + spi_transfer_bytes(dev->p.spi, dev->p.cs, false, data, NULL, size); +} + +static uint16_t get_free_size_in_tx_buffer(w5500_t *dev) +{ + uint16_t tx_free = read_addr(dev, REG_S0_TX_FSR0, REG_S0_TX_FSR1); + uint16_t tmp = read_addr(dev, REG_S0_TX_FSR0, REG_S0_TX_FSR1); + + /* See Note in the description of Sn_TX_FSR in the datasheet: This is a 16 bit value, + so we read it until we get the same value twice. The W5500 will update it + while transmitting data. */ + while (tx_free != tmp) { + tx_free = tmp; + tmp = read_addr(dev, REG_S0_TX_FSR0, REG_S0_TX_FSR1); + } + + return tx_free; +} + +static int send(netdev_t *netdev, const iolist_t *iolist) +{ + w5500_t *dev = (w5500_t *)netdev; + int result = -ENODEV; + + /* Get access to the SPI bus for the duration of this function. */ + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + + uint8_t tmp = read_register(dev, REG_PHYCFGR); + + if (tmp & PHY_LINK_UP) { + uint16_t tx_free = get_free_size_in_tx_buffer(dev); + uint16_t data_length = (uint16_t)iolist_size(iolist); + + /* Get the write pointer. */ + uint16_t socket0_write_pointer = read_addr(dev, REG_S0_TX_WR0, REG_S0_TX_WR1); + + /* Make sure we can write the full packet. */ + if (data_length <= tx_free) { + /* reset the frame size information */ + dev->frame_size = 0u; + + for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) { + size_t len = iol->iol_len; + write_tx0_buffer(dev, socket0_write_pointer + dev->frame_size, + iol->iol_base, len); + dev->frame_size += len; + } + /* Update the write pointer. */ + write_address(dev, REG_S0_TX_WR0, REG_S0_TX_WR1, + socket0_write_pointer + dev->frame_size); + /* Trigger the sending process. */ + write_register(dev, REG_S0_CR, CR_SEND); + + DEBUG("[w5500] send: transferred %i byte (at 0x%04x)\n", data_length, + (int)socket0_write_pointer); + result = 0; + } + } + else { + DEBUG("%s: link is down: PHYCFGR = 0x%02x\n", __func__, tmp); + result = -EIO; + } + + /* release the SPI bus again */ + spi_release(dev->p.spi); + + return result; +} + +static int confirm_send(netdev_t *netdev, void *info) +{ + w5500_t *dev = (w5500_t *)netdev; + + (void)info; + + if (dev->frame_sent == false) { + return -EAGAIN; + } + else { + return dev->frame_size; + } +} + +static void read_rx0_buffer(w5500_t *dev, uint16_t address, uint8_t *data, uint16_t size) +{ + spi_transfer_u16_be(dev->p.spi, dev->p.cs, true, address); + spi_transfer_byte(dev->p.spi, dev->p.cs, true, SOCKET0_RX_BUFFER); + + spi_transfer_bytes(dev->p.spi, dev->p.cs, false, NULL, data, size); +} + +static uint16_t get_size_in_rx_buffer(w5500_t *dev) +{ + uint16_t received = read_addr(dev, REG_S0_RX_RSR0, REG_S0_RX_RSR1); + uint16_t tmp = read_addr(dev, REG_S0_RX_RSR0, REG_S0_RX_RSR1); + + /* See Note in the description of Sn_RX_RSR in the datasheet: This is a 16 bit value, + so we read it until we get the same value twice. The W5500 will update it + while receiving data, when we read the same value again we can assume that + the current packet is fully received. */ + while (received != tmp) { + received = tmp; + tmp = read_addr(dev, REG_S0_RX_RSR0, REG_S0_RX_RSR1); + } + + return received; +} + +static int receive(netdev_t *netdev, void *buf, size_t max_len, void *info) +{ + (void)info; + w5500_t *dev = (w5500_t *)netdev; + uint16_t packet_size = 0u; + int ret_value = 0; + + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + + uint16_t available_bytes = get_size_in_rx_buffer(dev); + + if (available_bytes > 0u) { + uint16_t socket0_read_pointer = read_addr(dev, REG_S0_RX_RD0, REG_S0_RX_RD1); + /* I could not find a hint in the documentation of the W5500, but different implementations + pointed me to the fact that the W5500 stores the size of the packet right in front of the + packet data. */ + read_rx0_buffer(dev, socket0_read_pointer, (uint8_t*)&packet_size, 2u); + packet_size = ntohs(packet_size) - 2; + ret_value = packet_size; + + if (max_len > 0u) { /* max_len > 0u: Client wants to read or drop the packet. */ + if (packet_size > max_len) { + buf = NULL; /* Drop the packet. */ + ret_value = -ENOBUFS; + } + + socket0_read_pointer += 2u; /* Add the 2 bytes of the packet_size. */ + if (buf != NULL) { + read_rx0_buffer(dev, socket0_read_pointer, (uint8_t *)buf, packet_size); + DEBUG("[w5500] receive: got packet of %i byte (at 0x%04x)\n", packet_size, + (int)socket0_read_pointer); + } + else { + DEBUG("[w5500] receive: drop packet of %i byte (at 0x%04x)\n", packet_size, + (int)socket0_read_pointer); + } + socket0_read_pointer += packet_size; + } + + /* Update read pointer. */ + write_address(dev, REG_S0_RX_RD0, REG_S0_RX_RD1, socket0_read_pointer); + write_register(dev, REG_S0_CR, CR_RECV); + while (read_register(dev, REG_S0_CR)) {} + } + + spi_release(dev->p.spi); + + return ret_value; +} + +static void isr(netdev_t *netdev) +{ + w5500_t *dev = (w5500_t *)netdev; + + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + /* Get phy status register. */ + uint8_t phy_status = read_register(dev, REG_PHYCFGR); + /* Get socket 0 interrupt register. */ + uint8_t socket0_interrupt_status = read_register(dev, REG_S0_IR); + + /* Clear interrupts. */ + write_register(dev, REG_S0_IR, socket0_interrupt_status); + spi_release(dev->p.spi); + + if ((phy_status & PHY_LINK_UP) && (dev->link_up == false)) { + dev->link_up = true; + DEBUG("%s: link is up: PHYCFGR = 0x%02x\n", __func__, phy_status); + netdev->event_callback(&dev->netdev, NETDEV_EVENT_LINK_UP); + } + else if (!(phy_status & PHY_LINK_UP) && (dev->link_up == true)) { + dev->link_up = false; + netdev->event_callback(&dev->netdev, NETDEV_EVENT_LINK_DOWN); + DEBUG("%s: link is down: PHYCFGR = 0x%02x\n", __func__, phy_status); + } + + while (socket0_interrupt_status & (IR_RECV | IR_SEND_OK)) { + if (socket0_interrupt_status & IR_RECV) { + DEBUG("[w5500] netdev RX complete\n"); + netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE); + } + if (socket0_interrupt_status & IR_SEND_OK) { + DEBUG("[w5500] netdev TX complete\n"); + dev->frame_sent = true; + netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE); + } + + /* Check interrupt status again. */ + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + socket0_interrupt_status = read_register(dev, REG_S0_IR); + write_register(dev, REG_S0_IR, socket0_interrupt_status); + spi_release(dev->p.spi); + } +} + +static int get(netdev_t *netdev, netopt_t opt, void *value, size_t max_len) +{ + w5500_t *dev = (w5500_t *)netdev; + int res = 0; + + switch (opt) { + case NETOPT_ADDRESS: + assert(max_len >= ETHERNET_ADDR_LEN); + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + read_chunk(dev, REG_SHAR0, value, ETHERNET_ADDR_LEN); + spi_release(dev->p.spi); + res = ETHERNET_ADDR_LEN; + break; + case NETOPT_LINK: + spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk); + uint8_t tmp = read_register(dev, REG_PHYCFGR); + spi_release(dev->p.spi); + dev->link_up = ((tmp & PHY_LINK_UP) != 0u); + *((netopt_enable_t *)value) = dev->link_up ? NETOPT_ENABLE + : NETOPT_DISABLE; + res = sizeof(netopt_enable_t); + break; + default: + res = netdev_eth_get(netdev, opt, value, max_len); + break; + } + + return res; +} + +static const netdev_driver_t netdev_driver_w5500 = { + .send = send, + .recv = receive, + .confirm_send = confirm_send, + .init = init, + .isr = isr, + .get = get, + .set = netdev_eth_set, +}; diff --git a/sys/net/gnrc/netif/init_devs/auto_init_w5500.c b/sys/net/gnrc/netif/init_devs/auto_init_w5500.c new file mode 100644 index 000000000000..7995409caa3b --- /dev/null +++ b/sys/net/gnrc/netif/init_devs/auto_init_w5500.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_auto_init_gnrc_netif + * @{ + * + * @file + * @brief Auto initialization for W5500 Ethernet devices + * + * @author Stefan Schmidt + */ + +#include "log.h" +#include "w5500.h" +#include "w5500_params.h" +#include "net/gnrc/netif/ethernet.h" +#include "include/init_devs.h" + +/** + * @brief Find out how many of these devices we need to care for + */ +#define W5500_NUM ARRAY_SIZE(w5500_params) + +/** + * @brief Allocate memory for the device descriptors + * @{ + */ +static w5500_t dev[W5500_NUM]; +/** @} */ + +static gnrc_netif_t _netif[W5500_NUM]; + +/** + * @brief Stacks for the MAC layer threads + */ +static char stack[W5500_NUM][GNRC_NETIF_STACKSIZE_DEFAULT]; + +void auto_init_w5500(void) +{ + for (unsigned i = 0; i < W5500_NUM; i++) { + LOG_DEBUG("[auto_init_netif] initializing w5500 #%u\n", i); + + /* setup netdev device */ + w5500_setup(&dev[i], &w5500_params[i], i); + /* initialize netdev <-> gnrc adapter state */ + gnrc_netif_ethernet_create(&_netif[i], stack[i], GNRC_NETIF_STACKSIZE_DEFAULT, + GNRC_NETIF_PRIO, "w5500", &dev[i].netdev); + } +} +/** @} */ diff --git a/sys/net/gnrc/netif/init_devs/init.c b/sys/net/gnrc/netif/init_devs/init.c index 8360365cbf02..13800a25ed0f 100644 --- a/sys/net/gnrc/netif/init_devs/init.c +++ b/sys/net/gnrc/netif/init_devs/init.c @@ -177,4 +177,9 @@ void gnrc_netif_init_devs(void) auto_init_tinyusb_netdev(); } + if (IS_USED(MODULE_W5500)) { + extern void auto_init_w5500(void); + auto_init_w5500(); + } + } diff --git a/tests/drivers/w5500/Makefile b/tests/drivers/w5500/Makefile new file mode 100644 index 000000000000..9fb30f9d1a6b --- /dev/null +++ b/tests/drivers/w5500/Makefile @@ -0,0 +1,10 @@ +include ../Makefile.drivers_common + +USEMODULE += test_utils_netdev_eth_minimal + +# the driver to test +USEMODULE += w5500 + +INCLUDES += -I$(APPDIR) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/drivers/w5500/Makefile.ci b/tests/drivers/w5500/Makefile.ci new file mode 100644 index 000000000000..877613d9ab41 --- /dev/null +++ b/tests/drivers/w5500/Makefile.ci @@ -0,0 +1,29 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + atmega8 \ + bluepill-stm32f030c8 \ + i-nucleo-lrwan1 \ + msb-430 \ + msb-430h \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + waspmote-pro \ + # diff --git a/tests/drivers/w5500/app.config.test b/tests/drivers/w5500/app.config.test new file mode 100644 index 000000000000..545f8aebb7d4 --- /dev/null +++ b/tests/drivers/w5500/app.config.test @@ -0,0 +1,2 @@ +CONFIG_MODULE_TEST_UTILS_NETDEV_ETH_MINIMAL=y +CONFIG_MODULE_W5500=y diff --git a/tests/drivers/w5500/init_dev.h b/tests/drivers/w5500/init_dev.h new file mode 100644 index 000000000000..281f408b5668 --- /dev/null +++ b/tests/drivers/w5500/init_dev.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Device-specific test header file W5500 ethernet device driver + * + * @author Stefan Schmidt + */ +#ifndef INIT_DEV_H +#define INIT_DEV_H + +#include + +#include "kernel_defines.h" +#include "net/netdev.h" + +#include "w5500.h" +#include "w5500_params.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define W5500_NUM ARRAY_SIZE(w5500_params) +#define NETDEV_ETH_MINIMAL_NUMOF W5500_NUM + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* INIT_DEV_H */ +/** @} */ diff --git a/tests/drivers/w5500/main.c b/tests/drivers/w5500/main.c new file mode 100644 index 000000000000..906dab63892b --- /dev/null +++ b/tests/drivers/w5500/main.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 Stefan Schmidt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for W5500 ethernet device driver + * + * @author Stefan Schmidt + * + * @} + */ + +#include + +#include "shell.h" +#include "test_utils/netdev_eth_minimal.h" +#include "init_dev.h" +#include "assert.h" +#include "w5500.h" +#include "w5500_params.h" + +static w5500_t w5500[W5500_NUM]; + +int netdev_eth_minimal_init_devs(netdev_event_cb_t cb) { + for (unsigned i = 0; i < W5500_NUM; i++) { + netdev_t *device = &w5500[i].netdev; + + /* setup the specific driver */ + w5500_setup(&w5500[i], &w5500_params[i], i); + + /* set the application-provided callback */ + device->event_callback = cb; + + /* initialize the device driver */ + int res = device->driver->init(device); + assert(!res); + } + + return 0; +} + +int main(void) +{ + puts("Test application for W5500 ethernet device driver"); + + int res = netdev_eth_minimal_init(); + if (res) { + puts("Error initializing devices"); + return 1; + } + + /* start the shell */ + puts("Initialization successful - starting the shell now"); + + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +}