diff options
Diffstat (limited to 'libhw_cr/rp2040_include/libhw/rp2040_hwspi.h')
-rw-r--r-- | libhw_cr/rp2040_include/libhw/rp2040_hwspi.h | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h new file mode 100644 index 0000000..9d99f7b --- /dev/null +++ b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h @@ -0,0 +1,118 @@ +/* libhw/rp2040_hwspi.h - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022) + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_RP2040_HWSPI_H_ +#define _LIBHW_RP2040_HWSPI_H_ + +#include <pico/binary_info.h> /* for bi_* */ + +#include <libcr_ipc/sema.h> +#include <libmisc/private.h> + +#include <libhw/generic/spi.h> + +enum rp2040_hwspi_instance { + RP2040_HWSPI_0 = 0, + RP2040_HWSPI_1 = 1, +}; + +struct rp2040_hwspi { + BEGIN_PRIVATE(LIBHW_RP2040_HWSPI_H) + /* const */ + LM_IF(IS_IMPLEMENTATION_FOR(LIBHW_RP2040_HWSPI_H))(spi_inst_t)(void) *inst; + uint64_t min_delay_ns; + uint8_t bogus_data; + uint pin_cs; + uint dma_tx_data; + uint dma_tx_ctrl; + uint dma_rx_data; + uint dma_rx_ctrl; + + /* mutable */ + uint64_t dead_until_ns; + cr_sema_t sema; + END_PRIVATE(LIBHW_RP2040_HWSPI_H) +}; +LO_IMPLEMENTATION_H(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi) +LO_IMPLEMENTATION_H(spi, struct rp2040_hwspi, rp2040_hwspi) + +/** + * 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 baudrate_hz : uint : baudrate in Hz + * @param min_delay_ns: uint64_t : minimum time for pin_cs to be high between messages + * @param bogus_data : uint8_t : bogus data to write when .iov_write_from is IOVEC_DISCARD + * @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 + * @param dma{1-4} : uint : DMA channel; any unused channel + * + * There is no bit-order argument; the RP2040's hardware SPI always + * uses MSB-first bit order. + * + * I know we called this "hwspi", but we're actually going to + * disconnect the CS pin from the PL022 SSP and manually GPIO it from + * the CPU. This is because the PL022 has a maximum of 16-bit frames, + * but 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. + * + * Restrictions on baudrate: + * + * - The PL022 requires that the baudrate is an even-number fraction + * of clk_peri. + * + This implies that the maximum baudrate is clk_peri/2. + * + Pico-SDK' default clk_peri is 125MHz, max is 200MHz. + * - The CS-from-GPIO hack above means that that we can't go so fast + * that the CPU can't do things in time. + * + Experimentally: + * | clk_sys=125MHz | baud=31.25MHz | works OK | + * | clk_sys=160MHz | baud=40 MHz | works OK | + * | clk_sys=170MHz | baud=42.5 MHz | works OK | + * | clk_sys=180MHz | baud=45 MHz | mangled in funny ways? | + * | clk_sys=200MHz | baud=50 MHz | messages get shifted right a bit | + * | clk_sys=125MHz | baud=62.5 MHz | messages get shifted right a bit | + * + * Both of these restrictions aught to be avoidable by using a + * PIO-based SPI driver instead of this PLL02-based driver. + */ +#define rp2040_hwspi_init(self, name, \ + inst_num, mode, baudrate_hz, \ + min_delay_ns, bogus_data, \ + pin_miso, pin_mosi, pin_clk, pin_cs, \ + dma1, dma2, dma3, dma4) \ + do { \ + bi_decl(bi_4pins_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, \ + min_delay_ns, bogus_data, \ + pin_miso, pin_mosi, pin_clk, pin_cs, \ + dma1, dma2, dma3, dma4); \ + } while(0) +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, + uint dma1, + uint dma2, + uint dma3, + uint dma4); + +#endif /* _LIBHW_RP2040_HWSPI_H_ */ |