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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
/* libhw/rp2040_hwspi.c - <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
*/
#include <inttypes.h> /* for PRIu{n} */
#include <hardware/clocks.h> /* for clock_get_hz() and clk_peri */
#include <hardware/gpio.h>
#include <hardware/spi.h>
#include <libmisc/assert.h>
#define LOG_NAME RP2040_SPI
#include <libmisc/log.h>
#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES
#include <libhw/rp2040_hwspi.h>
#include <libhw/generic/alarmclock.h>
#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;
}
|