/* libhw/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  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#ifndef _LIBHW_W5500_LL_H_
#define _LIBHW_W5500_LL_H_

#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 <libmisc/vcall.h>  /* for VCALL() */

#include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
#include <libhw/generic/spi.h> /* for implements_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_write(...) _w5500ll_write(__func__, __VA_ARGS__)
_w5500ll_write(const char *func,
#else
w5500ll_write(
#endif
              implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
	assert(spidev);
	assert((block & ~CTL_MASK_BLOCK) == 0);
	assert(data);
	assert(data_len);
#if CONFIG_W5500_LL_DEBUG
	n_debugf(W5500_LL,
	         "%s(): w5500ll_write(spidev, addr=%#04x, block="PRI_ctl_block", data, data_len=%zu)",
	         func, addr, ARG_ctl_block(block), data_len);
#endif

	uint8_t header[3] = {
		(uint8_t)((addr >> 8) & 0xFF),
		(uint8_t)(addr & 0xFF),
		(block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM,
	};
	struct bidi_iovec iov[] = {
		{.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
		{.iov_read_dst = NULL, .iov_write_src = data,   .iov_len = data_len},
	};
	VCALL(spidev, readwritev, iov, 2);
}

static inline void
#if CONFIG_W5500_LL_DEBUG
#define w5500ll_read(...) _w5500ll_read(__func__, __VA_ARGS__)
_w5500ll_read(const char *func,
#else
w5500ll_read(
#endif
              implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
	assert(spidev);
	assert((block & ~CTL_MASK_BLOCK) == 0);
	assert(data);
	assert(data_len);
#if CONFIG_W5500_LL_DEBUG
	n_debugf(W5500_LL,
	         "%s(): w5500ll_read(spidev, addr=%#04x, block="PRI_ctl_block", data, data_len=%zu)",
	         func, addr, ARG_ctl_block(block), data_len);
#endif

	uint8_t header[3] = {
		(uint8_t)((addr >> 8) & 0xFF),
		(uint8_t)(addr & 0xFF),
		(block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM,
	};
	struct bidi_iovec iov[] = {
		{.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
		{.iov_read_dst = data, .iov_write_src = NULL,   .iov_len = data_len},
	};
	VCALL(spidev, readwritev, iov, 2);
}

/* 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_write(spidev,                                 \
		                     offsetof(blocktyp, field),       \
		                     blockid,                         \
		                     &lval,                           \
		                     sizeof(lval));                   \
	} 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_read(spidev,                                       \
		             offsetof(blocktyp, field),                    \
		             blockid,                                      \
		             &val,                                         \
		             sizeof(val));                                 \
		if (sizeof(val) > 1)                                       \
			for (;;) {                                         \
				typeof(val) val2;                          \
				w5500ll_read(spidev,                       \
				             offsetof(blocktyp, field),    \
				             blockid,                      \
				             &val2,                        \
				             sizeof(val));                 \
				if (memcmp(&val2, &val, sizeof(val)) == 0) \
					break;                             \
				val = val2;                                \
			}                                                  \
		val;                                                       \
	})

#endif /* _LIBHW_W5500_LL_H_ */