summaryrefslogtreecommitdiff
path: root/cmd/sbc_harness
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/sbc_harness')
-rw-r--r--cmd/sbc_harness/hw/rp2040_hwspi.c115
-rw-r--r--cmd/sbc_harness/hw/rp2040_hwspi.h50
-rw-r--r--cmd/sbc_harness/hw/spi.h44
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_ */