/* libhw/rp2040_hwspi.c - implementation for the RP2040's ARM Primecell SSP (PL022) * * Copyright (C) 2024-2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include /* for PRIu{n} */ #include /* for clock_get_hz() and clk_peri */ #include #include #include #define LOG_NAME RP2040_SPI #include #define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES #include #include #include "config.h" #ifndef CONFIG_RP2040_SPI_DEBUG #error config.h must define CONFIG_RP2040_SPI_DEBUG (bool) #endif LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi, static) LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi, static) #define assert_4distinct(a, b, c, d) \ assert(a != b); \ assert(a != c); \ assert(a != d); \ assert(b != c); \ assert(b != d); \ assert(c != d); void _rp2040_hwspi_init(struct rp2040_hwspi *self, enum rp2040_hwspi_instance inst_num, enum spi_mode mode, uint baudrate_hz, uint64_t min_delay_ns, uint8_t bogus_data, 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; uint actual_baudrate_hz; assert(self); assert(baudrate_hz); uint32_t clk_peri_hz = clock_get_hz(clk_peri); debugf("clk_peri = %"PRIu32"Hz", clk_peri_hz); assert(baudrate_hz*2 <= clk_peri_hz); assert_4distinct(pin_miso, pin_mosi, pin_clk, pin_cs); /* 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_notreached("invalid hwspi instance number"); } actual_baudrate_hz = spi_init(inst, baudrate_hz); debugf("baudrate = %uHz", actual_baudrate_hz); assert(actual_baudrate_hz == 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); /* Initialize self. */ self->inst = inst; self->min_delay_ns = min_delay_ns; self->bogus_data = bogus_data; self->pin_cs = pin_cs; self->dead_until_ns = 0; } static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) { assert(self); spi_inst_t *inst = self->inst; assert(inst); assert(iov); assert(iovcnt); uint64_t now = LO_CALL(bootclock, get_time_ns); if (now < self->dead_until_ns) sleep_until_ns(self->dead_until_ns); 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, self->bogus_data, iov[i].iov_read_dst, iov[i].iov_len); else assert_notreached("duplex_iovec is neither read nor write"); } gpio_put(self->pin_cs, 1); self->dead_until_ns = LO_CALL(bootclock, get_time_ns) + self->min_delay_ns; }