diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/sbc_harness/hw/rp2040_hwspi.c | 115 | ||||
-rw-r--r-- | cmd/sbc_harness/hw/rp2040_hwspi.h | 50 | ||||
-rw-r--r-- | cmd/sbc_harness/hw/spi.h | 44 |
3 files changed, 209 insertions, 0 deletions
diff --git a/cmd/sbc_harness/hw/rp2040_hwspi.c b/cmd/sbc_harness/hw/rp2040_hwspi.c new file mode 100644 index 0000000..2f0bec4 --- /dev/null +++ b/cmd/sbc_harness/hw/rp2040_hwspi.c @@ -0,0 +1,115 @@ +/* rp2040_hwspi.c - `struct spi` implementation for the RP2040's ARM Primecell SSP (PL022) + */ + +#include <assert.h> + +#include <hardware/spi.h> /* pico-sdk:hardware_spi */ + +#include "hw/rp2040_hwspi.h" + +static void rp2040_hwspi_readwritev(struct spi *_self, const struct bidi_iovec *iov, int iovcnt); + +struct spi_vtable rp2040_hwspi_vtable = { + .readwritev = rp2040_hwspi_readwritev, +}; + +static inline void _rp2040_hwspi_init(struct rp2040_hwspi *self, + enum rp2040_hwspi_instance inst_num, + uint baudrate_hz; + uint pin_miso, + uint pin_mosi, + uint pin_clk, + uint pin_cs) { + /* Be not weary: This is but 12 lines of actual code; and many + * lines of comments and assert()s. */ + spi_inst_t *inst; + + assert(self); + assert(baudrate_hz); + assert(pin_miso != pin_mosi); + assert(pin_miso != pin_clk); + assert(pin_miso != pin_cs); + assert(pin_mosi != pin_clk); + assert(pin_mosi != pin_cs); + assert(pin_clk != pin_cs); + + /* I know we called this "hwspi", but we're actually going to + * disconnect the CS pin from the PL022 SSP and manually drive + * it from software. This is because the PL022 has a maximum + * of 16-bit frames, while we need to be able to do *at least* + * 32-bit frames (and ideally, much larger). By managing it + * ourselves, we can just keep CS pulled low extra-long, + * making the frame extra-long. */ + + /* Regarding the constraints on pin assignments: see the + * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */ + switch (inst_num) { + case RP2040_HWSPI_0: + inst = spi0; + assert(pin_miso == 0 || pin_miso == 4 || pin_miso == 16 || pin_miso == 20); + /*assert(pin_cs == 1 || pin_cs == 5 || pin_cs == 17 || pin_cs == 21);*/ + assert(pin_clk == 2 || pin_clk == 6 || pin_clk == 18 || pin_clk == 22); + assert(pin_mosi == 3 || pin_mosi == 7 || pin_mosi == 19 || pin_mosi == 23); + break; + case RP2040_HWSPI_1: + inst = spi1; + assert(pin_miso == 8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28); + /*assert(pin_cs == 9 || pin_cs == 13 || pin_cs == 25 || pin_cs == 29);*/ + assert(pin_clk == 10 || pin_clk == 14 || pin_clk == 26); + assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27); + break; + default: + assert(false); + } + + spi_init(inst, baudrate_hz); + spi_set_format(inst, 8, + (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0, + (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0, + SPI_MSB_FIRST); + + /* Connect the pins to the PL022; set them each to "function + * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3 + * "GPIO Functions"). + * + * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1", + * since on the RP2040 all of the "function 1" functions are + * some part of SPI.) */ + gpio_set_function(pin_clk, GPIO_FUNC_SPI); + gpio_set_function(pin_mosi, GPIO_FUNC_SPI); + gpio_set_function(pin_miso, GPIO_FUNC_SPI); + + /* Initialize the CS pin for software control. */ + gpio_init(pin_cs); + gpio_set_dir(pin_cs, GPIO_OUT); + gpio_put(pin_cs, 1); + + /* Return. */ + self->vtable = &rp2040_hwspi_vtable; + self->inst = inst; + self->pin_cs = pin_cs; +} + +static void rp2040_hwspi_readwritev(struct spi *_self, const struct bidi_iovec *iov, int iovcnt); { + struct rp2040_hwspi* self = _self; + assert(self); + spi_inst_t *inst = self->inst; + + assert(inst); + assert(iov); + assert(iovcnt); + + gpio_put(self->pin_cs, 0); + /* TODO: Replace blocking reads+writes with DMA. */ + for (int i = 0; i < iovcnt; i++) { + if (iov[i].iov_write_src && iov[i].iov_read_dst) + spi_write_read_blocking(inst, iov[i].iov_write_src, iov[i].iov_read_dst, iov[i].iov_len); + else if (iov[i].iov_write_src) + spi_write_blocking(inst, iov[i].iov_write_src, iov[i].iov_len); + else if (iov[i].iov_read_dst) + spi_read_blocking(inst, iov[i].iov_read_dst, iov[i].iov_len); + else + assert(false); + } + gpio_put(self->pin_cs, 1); +} diff --git a/cmd/sbc_harness/hw/rp2040_hwspi.h b/cmd/sbc_harness/hw/rp2040_hwspi.h new file mode 100644 index 0000000..9f4a551 --- /dev/null +++ b/cmd/sbc_harness/hw/rp2040_hwspi.h @@ -0,0 +1,50 @@ +#ifndef _RP2040_HWSPI_H_ +#define _RP2040_HWSPI_H_ + +#include "hw/spi.h" + +enum rp2040_hwspi_instance { + RP2040_HWSPI_0 = 0, + RP2040_HWSPI_1 = 1, +}; + +struct rp2040_hwspi { + struct spi_vtable *vtable; + + void /*spi_inst_t*/ *inst; + uint pin_cs; +}; + +/** + * Initialize an instance of `struct rp2040_hwspi`. + * + * @param self : struct rp2040_hwspi : the structure to initialize + * @param name : char * : a name for the SPI port; to include in the bininfo + * @param inst_num : enum rp2040_hwspi_instance : the PL220 instance number; RP2040_HWSPI_{0,1} + * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3} + * @param pin_miso : uint : pin number; 0, 4, 16, or 20 for _HWSPI_0; 8, 12, 24, or 28 for _HWSPI_1 + * @param pin_mosi : uint : pin number; 3, 7, 19, or 23 for _HWSPI_0; 11, 15, or 27 for _HWSPI_1 + * @param pin_clk : uint : pin number; 2, 6, 18, or 22 for _HWSPI_0; 10, 14, or 26 for _HWSPI_1 + * @param pin_cs : uint : pin number; any unused GPIO pin + * + * There is no bit-order argument; the RP2040's hardware SPI always + * uses MSB-first bit order. + */ +#define rp2040_hwspi_init(self, name, inst_num, mode, baudrate_hz, pin_miso, pin_mosi, pin_clk, pin_cs) do { \ + bi_decl(bi_4_pins_with_names(pin_miso, name" SPI MISO", \ + pin_mosi, name" SPI MOSI", \ + pin_mosi, name" SPI CLK", \ + pin_mosi, name" SPI CS")); \ + _rp2040_hwspi_init(self, inst_num, mode, baudrate_hz, pin_miso, pin_mosi, pin_c;k, pin_cs); \ + } while(0) + +void _rp2040_hwspi_init(struct rp2040_hwspi *self, + enum rp2040_hwspi_instance inst_num, + enum spi_mode mode, + uint baudrate_hz; + uint pin_miso, + uint pin_mosi, + uint pin_clk, + uint pin_cs); + +#endif /* _RP2040_HWSPI_H_ */ diff --git a/cmd/sbc_harness/hw/spi.h b/cmd/sbc_harness/hw/spi.h new file mode 100644 index 0000000..4a160ca --- /dev/null +++ b/cmd/sbc_harness/hw/spi.h @@ -0,0 +1,44 @@ +#ifndef _HW_SPI_H_ +#define _HW_SPI_H_ + +#include <stddef.h> /* for size_t */ + +enum spi_mode { + SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on rise) */ + SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on fall) */ + SPI_MODE_2 = 2, /* clk_polarity=1 (idle high), clk_phase=0 (sample on rise) */ + SPI_MODE_3 = 3, /* clk_polarity=1 (idle high), clk_phase=1 (sample on fall) */ +}; + +struct bidi_iovec { + void *iov_read_dst; + void *iov_write_src; + size_t iov_len; +}; + +struct spi; + +/* This API assumes that an SPI frame is a multiple of 8-bits. + * + * It is my understanding that this is a common constraint of SPI + * hardware, and that the RP2040 is somewhat unusual in that it allows + * frames of any length 4-16 bits (we disconnect the CS pin from the + * PL022 SSP and manually GPIO it from the CPU in order to achieve + * longer frames). + * + * But, more relevantly: The W5500's protocol uses frames that are 4-N + * octets; so we have no need for an API that allows a + * non-multiple-of-8 number of bits. + */ +struct spi_vtable { + void (*readwritev)(struct spi *, const struct bidi_iovec *iov, int iovcnt) +}; + +struct spi { + struct spi_vtable *vtable; + + /* This is where your implementation data goes. */ + char data[0]; +}; + +#endif /* _HW_SPI_H_ */ |