1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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_ */
|