diff options
Diffstat (limited to 'cmd/sbc_harness/hw/w5500.c')
-rw-r--r-- | cmd/sbc_harness/hw/w5500.c | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c new file mode 100644 index 0000000..993c2b5 --- /dev/null +++ b/cmd/sbc_harness/hw/w5500.c @@ -0,0 +1,151 @@ +#include "hw/w5500.h" + +/* A u8 control byte has 3 parts: block-ID, R/W, and + * operating-mode. */ + +/* Part 1: Block ID. */ +#define CTL_MASK_BLOCK 0b11111'000 +#define _CTL_BLOCK_RES (0b00'000) +#define _CTL_BLOCK_REG (0b01'000) +#define _CTL_BLOCK_TX (0b10'000) +#define _CTL_BLOCK_RX (0b11'000) +#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 0b1'00 +#define CTL_R 0b0'00 +#define CTL_W 0b1'00 + +/* Part 3: Operating mode. */ +#define CTL_MASK_OM 0b11 +#define CTL_OM_VDM 0b00 +#define CTL_OM_FDM1 0b01 +#define CTL_OM_FDM2 0b10 +#define CTL_OM_FDM4 0b11 + +/* The W5500 has 2 channels of communication with the MCU: + * + * - An SPI-based RPC protocol: + * + mode: mode 0 or mode 3 + * + bit-order: MSB-first + * + clock frequency: 33.3MHz - 80MHz + * - An interrupt pin that it pulls low when an event happens (to let + * the MCU know that it should do an SPI RPC "get" to see what + * happened.) + * + * Even though SPI is a full-duplex protocol, the W5500's RPC protocol + * on top of it is only half-duplex. Lame. + */ + +void w5500_spiframe_write(struct 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); + + 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}, + }; + spidev->vtable->readwritev(spidev, iov, 2); +} + +void w5500_spiframe_read(uint16_t addr, uint8_t ctl, void *data, size_t data_len) { + assert(spidev); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(data); + assert(data_len); + + 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}, + }; + spidev->vtable->readwritev(spidev, iov, 2); +} + +void _w5500_init(struct w5500 *self, struct spi* spi, uint pin_intr) { + self->spidev = spi; + gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, cbfn); +} + +struct w5500_block_common_reg { + uint8_t mode; /* MR */ + uint8_t ip_gateway_addr[4]; /* GAR0 ... GAR3 */ + uint8_t ip_subnet_mask[4]; /* SUBR0 ... SUBR3 */ + uint8_t eth_addr[6]; /* SHAR0 ... SHAR5 */ + uint8_t ip_addr[4]; /* SIPR0 ... SIPR3 */ + + uint8_t intlevel_0; /* INTLEVEL0 */ + uint8_t intlevel_1; /* INTLEVEL1 */ + uint8_t interrupt; /* IR */ + uint8_t interrupt_mask; /* IMR */ + uint8_t sock_interrupt; /* SIR */ + uint8_t sock_interrupt_mask; /* SIMR */ + uint8_t retry_time_0; /* RTR0 */ + uint8_t retry_time_1; /* RTR0 */ + uint8_t retry_count; /* RCR */ + + uint8_t ppp_lcp_request_timer; /* PTIMER */ + uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ + uint8_t ppp_dst_eth_addr[6]; /* PHAR0 ... PHAR5 */ + uint8_t ppp_sess_id[2]; /* PSID0 ... PSID1 */ + uint8_t ppp_max_seg_size[2]; /* PMRU0 ... PMRU1 */ + + uint8_t unreachable_ip_addr[4]; /* UIPR0 ... UIPR3 */ + uint8_t unreachable_port[2]; /* UPORTR0, UPROTR1 */ + + uint8_t phy_cfg; /* PHYCFGR */ + + uint8_t _reserved[10]; + + uint8_t chip_version; /* VERSIONR */ +}; +static_assert(sizeof(struct w5500_block_common_reg) == 0x3A); + +struct w5500_block_sock_reg { + uint8_t mode; /* Sn_MR */ + uint8_t command; /* Sn_CR */ + uint8_t interrupt; /* Sn_IR */ + uint8_t status; /* Sn_SR */ + uint8_t src_port[2]; /* Sn_PORT0, Sn_PORT1 */ + uint8_t dst_eth_addr[6]; /* Sn_DHAR0 ... SnDHAR5 */ + uint8_t dst_ip_addr[4]; /* Sn_DIPR0 ... Sn_DIP3 */ + uint8_t dst_port[2]; /* Sn_DPORT0 ... Sn_DPORT1 */ + + uint8_t max_seg_size[2]; /* 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 */ + uint8_t tx_buf_size; /* Sn_TXBUF_SIZE */ + uint8_t tx_free_size[2]; /* Sn_TX_FSR0, Sn_TX_FSR1 */ + uint8_t tx_read_pointer[2]; /* Sn_TX_RD0, Sn_TX_RD1 */ + uint8_t tx_write_pointer[2]; /* Sn_TX_WR0, Sn_TX_WR1 */ + uint8_t rx_size[2]; /* Sn_RX_RSR0, Sn_RX_RSR1 */ + uint8_t rx_read_pointer[2]; /* Sn_RX_RD0, Sn_RX_RD1 */ + uint8_t rx_write_pointer[2]; /* Sn_RX_WR0, Sn_RX_WR1 */ + + uint8_t interrupt_mask; /* Sn_IMR */ + uint8_t fragment_offset[2]; /* Sn_FRAG0, Sn_FRAG1 */ + uint8_t keepalive_timer; /* Sn_KPALVTR */ +}; +static_assert(sizeof(struct w5500_block_sock_reg) == 0x30); + + +struct w5500_socket { + struct spi *spidev; + uint8_t socknum; /* 0-7 */ +}; |