summaryrefslogtreecommitdiff
path: root/libhw_cr/w5500_ll.h
diff options
context:
space:
mode:
Diffstat (limited to 'libhw_cr/w5500_ll.h')
-rw-r--r--libhw_cr/w5500_ll.h426
1 files changed, 426 insertions, 0 deletions
diff --git a/libhw_cr/w5500_ll.h b/libhw_cr/w5500_ll.h
new file mode 100644
index 0000000..2506cd2
--- /dev/null
+++ b/libhw_cr/w5500_ll.h
@@ -0,0 +1,426 @@
+/* libhw_cr/w5500_ll.h - Low-level header library for the WIZnet W5500 chip
+ *
+ * Based entirely on the W5500 datasheet, v1.1.0.
+ * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_CR_W5500_LL_H_
+#define _LIBHW_CR_W5500_LL_H_
+
+#include <alloca.h> /* for alloca() */
+#include <stdint.h> /* for uint{n}_t */
+#include <string.h> /* for memcmp() */
+
+#include <libmisc/assert.h> /* for assert(), static_assert() */
+#include <libmisc/endian.h> /* for uint16be_t */
+
+#include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
+#include <libhw/generic/spi.h> /* for lo_interface spi */
+
+/* Config *********************************************************************/
+
+#include "config.h"
+
+#ifndef CONFIG_W5500_LL_DEBUG
+ #error config.h must define CONFIG_W5500_LL_DEBUG
+#endif
+
+/* Low-level protocol built on SPI frames. ***********************************/
+
+/* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */
+
+/* Part 1: Block ID. */
+#define CTL_MASK_BLOCK 0b11111000
+#define _CTL_BLOCK_RES 0b00000 /* chip-wide registers on socknum=0, REServed on socknum>=1 */
+#define _CTL_BLOCK_REG 0b01000 /* socknum-specific registers */
+#define _CTL_BLOCK_TX 0b10000 /* socknum-specific transmit buffer */
+#define _CTL_BLOCK_RX 0b11000 /* socknum-specific receive buffer */
+#define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part))
+#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES)
+
+/* Part 2: R/W. */
+#define CTL_MASK_RW 0b100
+#define CTL_R 0b000
+#define CTL_W 0b100
+
+/* Part 3: Operating mode. */
+#define CTL_MASK_OM 0b11
+#define CTL_OM_VDM 0b00 /* variable-length data mode */
+#define CTL_OM_FDM1 0b01 /* fixed-length data mode: 1 byte data length */
+#define CTL_OM_FDM2 0b10 /* fixed-length data mode: 2 byte data length */
+#define CTL_OM_FDM4 0b11 /* fixed-length data mode: 4 byte data length */
+
+#if CONFIG_W5500_LL_DEBUG
+static char *_ctl_block_part_strs[] = {
+ "RES",
+ "REG",
+ "TX",
+ "RX",
+};
+#define PRI_ctl_block "CTL_BLOCK_SOCK(%d, %s)"
+#define ARG_ctl_block(b) (((b)>>5) & 0b111), _ctl_block_part_strs[((b)>>3)&0b11]
+#endif
+
+/* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex.
+ * Lame. */
+
+static inline void
+#if CONFIG_W5500_LL_DEBUG
+#define w5500ll_writev(...) _w5500ll_writev(__func__, __VA_ARGS__)
+_w5500ll_writev(const char *func,
+#else
+w5500ll_writev(
+#endif
+ lo_interface spi spidev, uint16_t addr, uint8_t block,
+ const struct iovec *iov, int iovcnt,
+ size_t skip, size_t max)
+{
+ assert(!LO_IS_NULL(spidev));
+ assert((block & ~CTL_MASK_BLOCK) == 0);
+ assert(iov);
+ assert(iovcnt > 0);
+#if CONFIG_W5500_LL_DEBUG
+ n_debugf(W5500_LL,
+ "%s(): w5500ll_write(spidev, addr=%#04x, block="PRI_ctl_block", iov, iovcnt=%d)",
+ func, addr, ARG_ctl_block(block), iovcnt);
+#endif
+
+ uint8_t header[] = {
+ (uint8_t)((addr >> 8) & 0xFF),
+ (uint8_t)(addr & 0xFF),
+ (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM,
+ };
+ int inner_cnt = 1+io_slice_cnt(iov, iovcnt, skip, max);
+ struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt);
+ inner[0] = (struct duplex_iovec){
+ .iov_read_to = IOVEC_DISCARD,
+ .iov_write_from = header,
+ .iov_len = sizeof(header),
+ };
+ io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max);
+ LO_CALL(spidev, readwritev, inner, inner_cnt);
+}
+
+static inline void
+#if CONFIG_W5500_LL_DEBUG
+#define w5500ll_readv(...) _w5500ll_read(__func__, __VA_ARGS__)
+_w5500ll_readv(const char *func,
+#else
+w5500ll_readv(
+#endif
+ lo_interface spi spidev, uint16_t addr, uint8_t block,
+ const struct iovec *iov, int iovcnt,
+ size_t max)
+{
+ assert(!LO_IS_NULL(spidev));
+ assert((block & ~CTL_MASK_BLOCK) == 0);
+ assert(iov);
+ assert(iovcnt > 0);
+#if CONFIG_W5500_LL_DEBUG
+ n_debugf(W5500_LL,
+ "%s(): w5500ll_read(spidev, addr=%#04x, block="PRI_ctl_block", iov, iovcnt=%d)",
+ func, addr, ARG_ctl_block(block), iovcnt);
+#endif
+
+ uint8_t header[] = {
+ (uint8_t)((addr >> 8) & 0xFF),
+ (uint8_t)(addr & 0xFF),
+ (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM,
+ };
+ int inner_cnt = 1+io_slice_cnt(iov, iovcnt, 0, max);
+ struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt);
+ inner[0] = (struct duplex_iovec){
+ .iov_read_to = IOVEC_DISCARD,
+ .iov_write_from = header,
+ .iov_len = sizeof(header),
+ };
+ io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max);
+ LO_CALL(spidev, readwritev, inner, inner_cnt);
+}
+
+/* Common chip-wide registers. ***********************************************/
+
+struct w5500ll_block_common_reg {
+ uint8_t mode; /* MR; bitfield, see CHIPMODE_{x} below */
+ struct net_ip4_addr ip_gateway_addr; /* GAR0 ... GAR3 */
+ struct net_ip4_addr ip_subnet_mask; /* SUBR0 ... SUBR3 */
+ struct net_eth_addr eth_addr; /* SHAR0 ... SHAR5 */
+ struct net_ip4_addr ip_addr; /* SIPR0 ... SIPR3 */
+
+ uint16be_t intlevel; /* INTLEVEL0, INTLEVEL1; if non-zero,
+ * hysteresis between pin_intr being pulled
+ * low (hysteresis=(intlevel+1)*4/(150MHz)) */
+ uint8_t chip_interrupt; /* IR; bitfield, see CHIPINTR_{x} below */
+ uint8_t chip_interrupt_mask; /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */
+ uint8_t sock_interrupt; /* SIR; bitfield of which sockets have their .interrupt set */
+ uint8_t sock_interrupt_mask; /* SIMR; bitfield of sockets, 0=disable, 1=enable */
+ uint16be_t retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */
+ uint8_t retry_count; /* RCR; configures max re-transmission count */
+
+ uint8_t ppp_lcp_request_timer; /* PTIMER */
+ uint8_t ppp_lcp_magic_bumber; /* PMAGIC */
+ struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */
+ uint16be_t ppp_sess_id; /* PSID0 ... PSID1 */
+ uint16be_t ppp_max_seg_size; /* PMRU0 ... PMRU1 */
+
+ struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */
+ uint16be_t unreachable_port; /* UPORTR0, UPORTR1 */
+
+ uint8_t phy_cfg; /* PHYCFGR */
+
+ uint8_t _reserved[10];
+
+ uint8_t chip_version; /* VERSIONR */
+};
+static_assert(sizeof(struct w5500ll_block_common_reg) == 0x3A);
+
+/* bitfield */
+#define CHIPMODE_RST ((uint8_t)(1<<7)) /* software reset */
+#define _CHIPMODE_UNUSED6 ((uint8_t)(1<<6))
+#define CHIPMODE_WOL ((uint8_t)(1<<5)) /* wake-on-lan */
+#define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4))
+#define CHIPMODE_PPP ((uint8_t)(1<<3))
+#define _CHIPMODE_UNUSED2 ((uint8_t)(1<<2))
+#define CHIPMODE_FORCE_ARP ((uint8_t)(1<<1))
+#define _CHIPMODE_UNUSED0 ((uint8_t)(1<<0))
+
+#define CHIPINTR_CONFLICT ((uint8_t)(1<<7)) /* ARP says remote IP is self */
+#define CHIPINTR_UNREACH ((uint8_t)(1<<6))
+#define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6))
+#define CHIPINTR_WOL ((uint8_t)(1<<4)) /* wake-on-LAN */
+#define _CHIPINTR_UNUSED3 ((uint8_t)(1<<3))
+#define _CHIPINTR_UNUSED2 ((uint8_t)(1<<2))
+#define _CHIPINTR_UNUSED1 ((uint8_t)(1<<1))
+#define _CHIPINTR_UNUSED0 ((uint8_t)(1<<0))
+
+#define w5500ll_write_common_reg(spidev, field, val) \
+ w5500ll_write_reg(spidev, \
+ CTL_BLOCK_COMMON_REG, \
+ struct w5500ll_block_common_reg, \
+ field, val)
+
+
+#define w5500ll_read_common_reg(spidev, field) \
+ w5500ll_read_reg(spidev, \
+ CTL_BLOCK_COMMON_REG, \
+ struct w5500ll_block_common_reg, \
+ field)
+
+/* Per-socket registers. *****************************************************/
+
+struct w5500ll_block_sock_reg {
+ uint8_t mode; /* Sn_MR; see SOCKMODE_{x} below */
+ uint8_t command; /* Sn_CR; see CMD_{x} below */
+ uint8_t interrupt; /* Sn_IR; bitfield, see SOCKINTR_{x} below */
+ uint8_t state; /* Sn_SR; see STATE_{x} below */
+ uint16be_t local_port; /* Sn_PORT0, Sn_PORT1 */
+ struct net_eth_addr remote_eth_addr; /* Sn_DHAR0 ... SnDHAR5 */
+ struct net_ip4_addr remote_ip_addr; /* Sn_DIPR0 ... Sn_DIP3 */
+ uint16be_t remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */
+
+ uint16be_t max_seg_size; /* Sn_MSSR0, Sn_MSSR1 */
+ uint8_t _reserved0[1];
+ uint8_t ip_tos; /* Sn_TOS */
+ uint8_t ip_ttl; /* Sn_TTL */
+ uint8_t _reserved1[7];
+
+ uint8_t rx_buf_size; /* Sn_RXBUF_SIZE; in KiB, power of 2, <= 16 */
+ uint8_t tx_buf_size; /* Sn_TXBUF_SIZE; in KiB, power of 2, <= 16 */
+ uint16be_t tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */
+ uint16be_t tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */
+ uint16be_t tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */
+ uint16be_t rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */
+ uint16be_t rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */
+ uint16be_t rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */
+
+ uint8_t interrupt_mask; /* Sn_IMR */
+ uint16be_t fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */
+ uint8_t keepalive_timer; /* Sn_KPALVTR */
+};
+static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30);
+
+/* low 4 bits are the main enum, high 4 bits are flags */
+#define SOCKMODE_CLOSED ((uint8_t)0b0000)
+#define SOCKMODE_TCP ((uint8_t)0b0001)
+#define SOCKMODE_UDP ((uint8_t)0b0010)
+#define SOCKMODE_MACRAW ((uint8_t)0b0100)
+
+#define SOCKMODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5))
+
+#define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7))
+#define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6))
+#define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) /* Use IGMPv1 instead of v2 */
+#define SOCKMODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4))
+
+#define SOCKMODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7))
+#define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6))
+#define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5))
+#define SOCKMODE_FLAG_MACRAW_BLOCK_IPV6 ((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 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 _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7)
+#define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6)
+#define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5)
+#define SOCKINTR_SEND_OK ((uint8_t)1<<4) /* TODO: determine precise meaning */
+#define SOCKINTR_SEND_TIMEOUT ((uint8_t)1<<3) /* ARP or TCP */
+#define SOCKINTR_RECV_DAT ((uint8_t)1<<2) /* received data */
+#define SOCKINTR_RECV_FIN ((uint8_t)1<<1) /* received FIN */
+#define SOCKINTR_CONN ((uint8_t)1<<0) /* first for SYN, then when SOCKMODE_ESTABLISHED */
+
+#define STATE_CLOSED ((uint8_t)0x00)
+
+/**
+ * The TCP state diagram is as follows.
+ * - Reading the W5500's "state" register does not distinguish between FIN_WAIT_1 and FIN_WAIT_2;
+ * it just has a single FIN_WAIT.
+ * - At any point the state can jump to "CLOSED" either by CMD_CLOSE or by a timeout.
+ * - Writing data is valid in ESTABLISHED and CLOSE_WAIT.
+ * - Reading data is valid in ESTABLISHED and FIN_WAIT.
+ *
+ * TCP state diagram, showing the flow of │ CLOSED │ ━━ role separator ┌───────┐
+ * SYN, FIN, and their assocaited ACKs. └────────┘ ══ state transition │ state │
+ * V ┈┈ packet flow └───────┘
+ * (CMD_OPEN) ║
+ * V (action/event)
+ * ┌────────┐
+ * ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━│ INIT │━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
+ * ┃ server ┃ └────────┘ ┃ client ┃
+ * ┣━━━━━━━━━━━━━━━━┛ V ┃┃ V ┗━━━━━━━━━━━━━━━━┫
+ * ┃ ╔═══════════╝ ┃┃ ╚═══════════╗ ┃
+ * ┃ (CMD_LISTEN) ┃┃ (CMD_CONNECT) ┃
+ * ┃ V ┌┃┃┈┈┈┈┈┈┈┈<(send SYN) ┃
+ * ┃ ┌────────┐ ┊┃┃ V ┃
+ * ┃ │ LISTEN │ ┊┃┃ ┌─────────┐ ┃
+ * ┃ └────────┘ ┊┃┃ │ SYNSENT │ ┃
+ * ┃ V ┊┃┃ └─────────┘ ┃
+ * ┃ (recv SYN)<┈┈┈┈┈┈┘┃┃ V ┃
+ * ┃ (send SYN+ACK)>┈┈┈┈┐┃┃ ║ ┃
+ * ┃ V └┃┃┈┈┈┈┈┈>(recv SYN+ACK) ┃
+ * ┃ ┌─────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ack) ┃
+ * ┃ │ SYNRECV │ ┊┃┃ ║ ┃
+ * ┃ └─────────┘ ┊┃┃ ║ ┃
+ * ┃ V V ┊┃┃ ║ ┃
+ * ┃ ║ (recv ACK)<┈┈┈┘┃┃ ║ ┃
+ * ┃ ║ ╚═════════╗ ┃┃ ╔═══════════╝ ┃
+ * ┃ ╚═══╗ V ┃┃ V ┃
+ * ┃ ║ ┌─────────────┐ ┃
+ * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━│ ESTABLISHED │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━└─────────────┘━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ * ┃ ║ V ┃┃ V ┃
+ * ┃ ╠═══════════╝ ┃┃ ╚═══════════╗ ┃
+ * ┃ (CMD_DISCON) ┃┃ ║ ┃
+ * ┃ Both sides sent ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┐┃┃ ║ ┃
+ * ┃ FIN at the "same" ┊ V └┃┃┈┈┈┈┈┈┈┈>(recv FIN) ┃
+ * ┃ time; both are ┊ ┌────────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ACK) ┃
+ * ┃ active closers ┊ │ FIN_WAIT_1 │ ┊┃┃ V ┃
+ * ┃ / \ ┊ └────────────┘ ┊┃┃ ┌────────────┐ ┃
+ * ┃ ,------' '------, ┊ V V ┊┃┃ │ CLOSE_WAIT │ ┃
+ * ┃ ╔════════════════╝ ║ ┊┃┃ └────────────┘ ┃
+ * ┃ (recv FIN)<┈┈┈┈┤ ╔══╝ ┊┃┃ V ┃
+ * ┃ ┌┈┈<(send ACK)>┈┈┐ ┊ ║ ┊┃┃ ║ ┃
+ * ┃ ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┘┃┃ ║ ┃
+ * ┃ ┊ V ┊ V ┃┃ ║ ┃
+ * ┃ ┊ ┌─────────┐ ┊ ┌────────────┐ ┃┃ ║ ┃
+ * ┃ ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ┃┃ ║ ┃
+ * ┃ ┊ └─────────┘ ┊ └────────────┘ ┃┃ (CMD_DISCON) ┃
+ * ┃ ┊ V ┊ V ┌┃┃┈┈┈┈┈┈┈┈<(send FIN) ┃
+ * ┃ ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┘┃┃ ║ ┃
+ * ┃ ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┐┃┃ V ┃
+ * ┃ └┈┈>(recv ACK)<┈┈┘ ╚═╗ ┊┃┃ ┌──────────┐ ┃
+ * ┃ ╚════════════════╗ ║ ┊┃┃ │ LAST_ACK │ ┃
+ * ┃ V V ┊┃┃ └──────────┘ ┃
+ * ┃ ┌───────────┐ ┊┃┃ V ┃
+ * ┃ │ TIME_WAIT │ ┊┃┃ ║ ┃
+ * ┃ └───────────┘ └┃┃┈┈┈┈┈┈┈┈>(recv ACK) ┃
+ * ┃ V ┃┃ ║ ┃
+ * ┣━━━━━━━━━━━━━━━━┓ (2*MSL has elapsed) ┃┃ ║ ┏━━━━━━━━━━━━━━━━┫
+ * ┃ active closer ┃ ╚═══════════╗ ┃┃ ╔═══════════╝ ┃ passive closer ┃
+ * ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━V━┛┗━V━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┛
+ * ┌────────┐
+ * │ CLOSED │
+ */
+#define STATE_TCP_INIT ((uint8_t)0x13)
+#define STATE_TCP_LISTEN ((uint8_t)0x14) /* server */
+#define STATE_TCP_SYNSENT ((uint8_t)0x15) /* client; during dial */
+#define STATE_TCP_SYNRECV ((uint8_t)0x16) /* server; during accept */
+#define STATE_TCP_ESTABLISHED ((uint8_t)0x17)
+#define STATE_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */
+#define STATE_TCP_CLOSING ((uint8_t)0x1a) /* during active close */
+#define STATE_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */
+#define STATE_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */
+#define STATE_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */
+
+#define STATE_UDP ((uint8_t)0x22)
+
+#define STATE_MACRAW ((uint8_t)0x42)
+
+#define w5500ll_write_sock_reg(spidev, socknum, field, val) \
+ w5500ll_write_reg(spidev, \
+ CTL_BLOCK_SOCK(socknum, REG), \
+ struct w5500ll_block_sock_reg, \
+ field, val)
+
+#define w5500ll_read_sock_reg(spidev, socknum, field) \
+ w5500ll_read_reg(spidev, \
+ CTL_BLOCK_SOCK(socknum, REG), \
+ struct w5500ll_block_sock_reg, \
+ field)
+
+/******************************************************************************/
+
+#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \
+ typeof((blocktyp){}.field) lval = val; \
+ w5500ll_writev(spidev, \
+ offsetof(blocktyp, field), \
+ blockid, \
+ &((struct iovec){ \
+ &lval, \
+ sizeof(lval), \
+ }), \
+ 1, 0, 0); \
+ } while (0)
+
+/* The datasheet tells us that multi-byte reads are non-atomic and
+ * that "it is recommended that you read all 16-bits twice or more
+ * until getting the same value". */
+#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \
+ typeof((blocktyp){}.field) val; \
+ w5500ll_readv(spidev, \
+ offsetof(blocktyp, field), \
+ blockid, \
+ &((struct iovec){ \
+ .iov_base = &val, \
+ .iov_len = sizeof(val), \
+ }), \
+ 1, 0); \
+ if (sizeof(val) > 1) \
+ for (;;) { \
+ typeof(val) val2; \
+ w5500ll_readv(spidev, \
+ offsetof(blocktyp, field), \
+ blockid, \
+ &((struct iovec){ \
+ .iov_base = &val2, \
+ .iov_len = sizeof(val), \
+ }), \
+ 1, 0); \
+ if (memcmp(&val2, &val, sizeof(val)) == 0) \
+ break; \
+ val = val2; \
+ } \
+ val; \
+ })
+
+#endif /* _LIBHW_CR_W5500_LL_H_ */