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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
//go:build rp2040 || rp2350
package machine
import (
"device/rp"
"errors"
"unsafe"
)
// SPI on the RP2040
var (
SPI0 = &_SPI0
_SPI0 = SPI{
Bus: rp.SPI0,
}
SPI1 = &_SPI1
_SPI1 = SPI{
Bus: rp.SPI1,
}
)
// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
Frequency uint32
// LSB not supported on rp2040.
LSBFirst bool
// Mode's two most LSB are CPOL and CPHA. i.e. Mode==2 (0b10) is CPOL=1, CPHA=0
Mode uint8
// Serial clock pin
SCK Pin
// TX or Serial Data Out (MOSI if rp2040 is master)
SDO Pin
// RX or Serial Data In (MISO if rp2040 is master)
SDI Pin
}
var (
ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022")
ErrSPITimeout = errors.New("SPI timeout")
ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz")
errSPIInvalidSDI = errors.New("invalid SPI SDI pin")
errSPIInvalidSDO = errors.New("invalid SPI SDO pin")
errSPIInvalidSCK = errors.New("invalid SPI SCK pin")
)
type SPI struct {
Bus *rp.SPI0_Type
}
// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read
// interface, there must always be the same number of bytes written as bytes read.
// The Tx method knows about this, and offers a few different ways of calling it.
//
// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer.
// Note that the tx and rx buffers must be the same size:
//
// spi.Tx(tx, rx)
//
// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros
// until all the bytes in the command packet have been received:
//
// spi.Tx(tx, nil)
//
// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet":
//
// spi.Tx(nil, rx)
//
// Remark: This implementation (RP2040) allows reading into buffer with a custom repeated
// value on tx.
//
// spi.Tx([]byte{0xff}, rx) // may cause unwanted heap allocations.
//
// This form sends 0xff and puts the result into rx buffer. Useful for reading from SD cards
// which require 0xff input on SI.
func (spi SPI) Tx(w, r []byte) (err error) {
switch {
case w == nil:
// read only, so write zero and read a result.
err = spi.rx(r, 0)
case r == nil:
// write only
err = spi.tx(w)
case len(w) == 1 && len(r) > 1:
// Read with custom repeated value.
err = spi.rx(r, w[0])
default:
// write/read
err = spi.txrx(w, r)
}
return err
}
// Write a single byte and read a single byte from TX/RX FIFO.
func (spi SPI) Transfer(w byte) (byte, error) {
for !spi.isWritable() {
}
spi.Bus.SSPDR.Set(uint32(w))
for !spi.isReadable() {
}
return uint8(spi.Bus.SSPDR.Get()), nil
}
func (spi SPI) SetBaudRate(br uint32) error {
const freqin uint32 = 125 * MHz
const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527.
// Find smallest prescale value which puts output frequency in range of
// post-divide. Prescale is an even number from 2 to 254 inclusive.
var prescale, postdiv uint32
for prescale = 2; prescale < 255; prescale += 2 {
if freqin < (prescale+2)*256*br {
break
}
}
if prescale > 254 || br > maxBaud {
return ErrSPIBaud
}
// Find largest post-divide which makes output <= baudrate. Post-divide is
// an integer in the range 1 to 256 inclusive.
for postdiv = 256; postdiv > 1; postdiv-- {
if freqin/(prescale*(postdiv-1)) > br {
break
}
}
spi.Bus.SSPCPSR.Set(prescale)
spi.Bus.SSPCR0.ReplaceBits((postdiv-1)<<rp.SPI0_SSPCR0_SCR_Pos, rp.SPI0_SSPCR0_SCR_Msk, 0)
return nil
}
func (spi SPI) GetBaudRate() uint32 {
const freqin uint32 = 125 * MHz
prescale := spi.Bus.SSPCPSR.Get()
postdiv := ((spi.Bus.SSPCR0.Get() & rp.SPI0_SSPCR0_SCR_Msk) >> rp.SPI0_SSPCR0_SCR_Pos) + 1
return freqin / (prescale * postdiv)
}
// Configure is intended to setup/initialize the SPI interface.
// Default baudrate of 4MHz is used if Frequency == 0. Default
// word length (data bits) is 8.
// Below is a list of GPIO pins corresponding to SPI0 bus on the rp2040:
//
// SI : 0, 4, 17 a.k.a RX and MISO (if rp2040 is master)
// SO : 3, 7, 19 a.k.a TX and MOSI (if rp2040 is master)
// SCK: 2, 6, 18
//
// SPI1 bus GPIO pins:
//
// SI : 8, 12
// SO : 11, 15
// SCK: 10, 14
//
// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure.
func (spi SPI) Configure(config SPIConfig) error {
const defaultBaud uint32 = 4 * MHz
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
// set default pins if config zero valued or invalid clock pin supplied.
switch spi.Bus {
case rp.SPI0:
config.SCK = SPI0_SCK_PIN
config.SDO = SPI0_SDO_PIN
config.SDI = SPI0_SDI_PIN
case rp.SPI1:
config.SCK = SPI1_SCK_PIN
config.SDO = SPI1_SDO_PIN
config.SDI = SPI1_SDI_PIN
}
}
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
if config.Frequency == 0 {
config.Frequency = defaultBaud
}
// SPI pin configuration
config.SCK.setFunc(fnSPI)
config.SDO.setFunc(fnSPI)
config.SDI.setFunc(fnSPI)
return spi.initSPI(config)
}
func (spi SPI) initSPI(config SPIConfig) (err error) {
spi.reset()
// LSB-first not supported on PL022:
if config.LSBFirst {
return ErrLSBNotSupported
}
err = spi.SetBaudRate(config.Frequency)
// Set SPI Format (CPHA and CPOL) and frame format (default is Motorola)
spi.setFormat(config.Mode)
// Always enable DREQ signals -- harmless if DMA is not listening
spi.Bus.SSPDMACR.SetBits(rp.SPI0_SSPDMACR_TXDMAE | rp.SPI0_SSPDMACR_RXDMAE)
// Finally enable the SPI
spi.Bus.SSPCR1.SetBits(rp.SPI0_SSPCR1_SSE)
return err
}
//go:inline
func (spi SPI) setFormat(mode uint8) {
cpha := uint32(mode) & 1
cpol := uint32(mode>>1) & 1
spi.Bus.SSPCR0.ReplaceBits(
(cpha<<rp.SPI0_SSPCR0_SPH_Pos)|
(cpol<<rp.SPI0_SSPCR0_SPO_Pos)|
(uint32(7)<<rp.SPI0_SSPCR0_DSS_Pos), // Set databits (SPI word length) to 8 bits.
rp.SPI0_SSPCR0_SPH_Msk|rp.SPI0_SSPCR0_SPO_Msk|rp.SPI0_SSPCR0_DSS_Msk|rp.SPI0_SSPCR0_FRF_Msk, 0)
}
// reset resets SPI and waits until reset is done.
//
//go:inline
func (spi SPI) reset() {
resetVal := spi.deinit()
rp.RESETS.RESET.ClearBits(resetVal)
// Wait until reset is done.
for !rp.RESETS.RESET_DONE.HasBits(resetVal) {
}
}
//go:inline
func (spi SPI) deinit() (resetVal uint32) {
switch spi.Bus {
case rp.SPI0:
resetVal = rp.RESETS_RESET_SPI0
case rp.SPI1:
resetVal = rp.RESETS_RESET_SPI1
}
// Perform SPI reset.
rp.RESETS.RESET.SetBits(resetVal)
return resetVal
}
// isWritable returns false if no space is available to write. True if a write is possible
//
//go:inline
func (spi SPI) isWritable() bool {
return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_TNF)
}
// isReadable returns true if a read is possible i.e. data is present
//
//go:inline
func (spi SPI) isReadable() bool {
return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_RNE)
}
// PrintRegs prints SPI's peripheral common registries current values
func (spi SPI) PrintRegs() {
cr0 := spi.Bus.SSPCR0.Get()
cr1 := spi.Bus.SSPCR1.Get()
dmacr := spi.Bus.SSPDMACR.Get()
cpsr := spi.Bus.SSPCPSR.Get()
dr := spi.Bus.SSPDR.Get()
ris := spi.Bus.SSPRIS.Get()
println("CR0:", cr0)
println("CR1:", cr1)
println("DMACR:", dmacr)
println("CPSR:", cpsr)
println("DR:", dr)
println("RIS:", ris)
}
//go:inline
func (spi SPI) isBusy() bool {
return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_BSY)
}
// tx writes buffer to SPI ignoring Rx.
func (spi SPI) tx(tx []byte) error {
if len(tx) == 0 {
// We don't have to do anything.
// This avoids a panic in &tx[0] when len(tx) == 0.
return nil
}
// Pick the DMA channel reserved for this SPI peripheral.
var ch *dmaChannel
var dreq uint32
if spi.Bus == rp.SPI0 {
ch = &dmaChannels[spi0DMAChannel]
dreq = 16 // DREQ_SPI0_TX
} else { // SPI1
ch = &dmaChannels[spi1DMAChannel]
dreq = 18 // DREQ_SPI1_TX
}
// Configure the DMA peripheral as follows:
// - set read address, write address, and number of transfer units (bytes)
// - increment read address (in memory), don't increment write address (SSPDR)
// - set data size to single bytes
// - set the DREQ so that the DMA will fill the SPI FIFO as needed
// - start the transfer
ch.READ_ADDR.Set(uint32(uintptr(unsafe.Pointer(&tx[0]))))
ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR))))
ch.TRANS_COUNT.Set(uint32(len(tx)))
ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ |
rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<<rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |
dreq<<rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos |
rp.DMA_CH0_CTRL_TRIG_EN)
// Wait until the transfer is complete.
// TODO: do this more efficiently:
// - Add a new API to start the transfer, without waiting for it to
// complete. This way, the CPU can do something useful while the
// transfer is in progress.
// - If we have to wait, do so by waiting for an interrupt and blocking
// this goroutine until finished (so that other goroutines can run or
// the CPU can go to sleep).
for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 {
}
// We didn't read any result values, which means the RX FIFO has likely
// overflown. We have to clean up this mess now.
// Drain RX FIFO, then wait for shifting to finish (which may be *after*
// TX FIFO drains), then drain RX FIFO again
for spi.isReadable() {
spi.Bus.SSPDR.Get()
}
for spi.isBusy() {
}
for spi.isReadable() {
spi.Bus.SSPDR.Get()
}
// Don't leave overrun flag set
spi.Bus.SSPICR.Set(rp.SPI0_SSPICR_RORIC)
return nil
}
// rx reads buffer to SPI ignoring x.
// txrepeat is output repeatedly on SO as data is read in from SI.
// Generally this can be 0, but some devices require a specific value here,
// e.g. SD cards expect 0xff
func (spi SPI) rx(rx []byte, txrepeat byte) error {
plen := len(rx)
const fifoDepth = 8 // see txrx
var rxleft, txleft = plen, plen
for txleft != 0 || rxleft != 0 {
if txleft != 0 && spi.isWritable() && rxleft < txleft+fifoDepth {
spi.Bus.SSPDR.Set(uint32(txrepeat))
txleft--
}
if rxleft != 0 && spi.isReadable() {
rx[plen-rxleft] = uint8(spi.Bus.SSPDR.Get())
rxleft--
continue
}
}
for spi.isBusy() {
gosched()
}
return nil
}
// Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst.
// Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit)
func (spi SPI) txrx(tx, rx []byte) error {
plen := len(tx)
if plen != len(rx) {
return ErrTxInvalidSliceSize
}
// Never have more transfers in flight than will fit into the RX FIFO,
// else FIFO will overflow if this code is heavily interrupted.
const fifoDepth = 8
var rxleft, txleft = plen, plen
for txleft != 0 || rxleft != 0 {
if txleft != 0 && spi.isWritable() && rxleft < txleft+fifoDepth {
spi.Bus.SSPDR.Set(uint32(tx[plen-txleft]))
txleft--
}
if rxleft != 0 && spi.isReadable() {
rx[plen-rxleft] = uint8(spi.Bus.SSPDR.Get())
rxleft--
}
}
if txleft != 0 || rxleft != 0 {
// Transaction ended early due to timeout
return ErrSPITimeout
}
for spi.isBusy() {
gosched()
}
return nil
}
|