summaryrefslogtreecommitdiff
path: root/cmd/sbc_harness/hw/w5500.c
blob: 993c2b523a9953a8cf2e3e542920e4f40e5f1571 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 */
};