/* libhw_cr/w5500_ll.c - Low-level library for the WIZnet W5500 chip * * Based entirely on the W5500 datasheet (v1.1.0), not on reference code. * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf * * Copyright (C) 2024-2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include /* for memcmp() and mempy() */ #include /* for stack_alloc() */ #include #include "w5500_ll.h" #if CONFIG_W5500_LL_DEBUG static void fmt_print_ctl_block(lo_interface fmt_dest, uint8_t b) { static char *strs[] = { "RES", "REG", "TX", "RX", }; fmt_print("CTL_BLOCK_SOCK(", (base10, (((b)>>5) & 0b111)), ", ", strs[((b)>>3)&0b11], ")"); } #endif void _w5500ll_n_writev(const char *func, 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 log_n_debugln(W5500_LL, func, "(): w5500ll_write(spidev", ", addr=", (base16_u16_, addr), ", block=", (ctl_block, block), ", iov", ", iovcnt=", 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 = stack_alloc(inner_cnt, struct duplex_iovec); 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); } void _w5500ll_n_readv(const char *func, 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 log_n_debugln(W5500_LL, func, "(): w5500ll_read(spidev", ", addr=", (base16_u16_, addr), ", block=", (ctl_block, block), ", iov", ", iovcnt=", 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 = stack_alloc(inner_cnt, struct duplex_iovec); 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); } void _w5500ll_n_read_repeated(const char *func, lo_interface spi spidev, uint16_t addr, uint8_t block, void *buf, void *buf_scratch, size_t len) { _w5500ll_n_readv(func, spidev, addr, block, &((struct iovec){.iov_base=buf, .iov_len=len}), 1, 0); for (;;) { _w5500ll_n_readv(func, spidev, addr, block, &((struct iovec){.iov_base=buf_scratch, .iov_len=len}), 1, 0); if (memcmp(buf, buf_scratch, len) == 0) break; memcpy(buf, buf_scratch, len); } }