1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 05:12:57 +01:00

Merge pull request #20301 from elektrozaun/driver_w5500

driver/w5500: driver for the W5500 ethernet chip
This commit is contained in:
Marian Buschsieweke 2024-05-03 06:15:21 +00:00 committed by GitHub
commit e2449184ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1070 additions and 8 deletions

View File

@ -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)) {

90
drivers/include/w5500.h Normal file
View File

@ -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 <stemschmidt@gmail.com>
*/
#ifndef W5500_H
#define W5500_H
#include <stdint.h>
#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 */
/** @} */

52
drivers/w5500/Kconfig Normal file
View File

@ -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

1
drivers/w5500/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,6 @@
FEATURES_REQUIRED += periph_gpio_irq
FEATURES_REQUIRED += periph_spi
USEMODULE += luid
USEMODULE += iolist
USEMODULE += netdev_eth
USEMODULE += ztimer_msec

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_w5500 := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_w5500)

View File

@ -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 <stemschmidt@gmail.com>
*/
#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 */
/** @} */

View File

@ -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 <stemschmidt@gmail.com>
*/
#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 */
/** @} */

446
drivers/w5500/w5500.c Normal file
View File

@ -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 <stemschmidt@gmail.com>
*
* @}
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#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,
};

View File

@ -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 <stemschmidt@gmail.com>
*/
#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);
}
}
/** @} */

View File

@ -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();
}
}

View File

@ -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

View File

@ -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 \
#

View File

@ -0,0 +1,2 @@
CONFIG_MODULE_TEST_UTILS_NETDEV_ETH_MINIMAL=y
CONFIG_MODULE_W5500=y

View File

@ -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 <stemschmidt@gmail.com>
*/
#ifndef INIT_DEV_H
#define INIT_DEV_H
#include <stdint.h>
#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 */
/** @} */

View File

@ -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 <stemschmidt@gmail.com>
*
* @}
*/
#include <stdio.h>
#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;
}