summaryrefslogtreecommitdiff
path: root/libhw/rp2040_hwspi.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhw/rp2040_hwspi.c')
-rw-r--r--libhw/rp2040_hwspi.c123
1 files changed, 123 insertions, 0 deletions
diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c
new file mode 100644
index 0000000..b041bc9
--- /dev/null
+++ b/libhw/rp2040_hwspi.c
@@ -0,0 +1,123 @@
+/* libhw/rp2040_hwspi.c - `implements_spi` implementation for the RP2040's
+ * ARM Primecell SSP (PL022) (implementation file)
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#include <assert.h>
+
+#include <hardware/spi.h> /* pico-sdk:hardware_spi */
+#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
+
+#include <libmisc/vcall.h>
+
+#include <libhw/rp2040_hwspi.h>
+
+static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt);
+
+struct spi_vtable rp2040_hwspi_vtable = {
+ .readwritev = rp2040_hwspi_readwritev,
+};
+
+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) {
+ /* 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(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) {
+ struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _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, 0, iov[i].iov_read_dst, iov[i].iov_len);
+ else
+ assert(false);
+ }
+ gpio_put(self->pin_cs, 1);
+}