aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/machine/machine_rp2040_spi.go
blob: bf97403a706f73ae2bbc77566ae5ef3835dba970 (plain)
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
//go:build rp2040
// +build rp2040

package machine

import (
	"device/rp"
	"errors"
)

// 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
	// Number of data bits per transfer. Valid values 4..16. Default and recommended is 8.
	DataBits 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")
	ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
	ErrSPITimeout         = errors.New("SPI timeout")
	ErrSPIBaud            = errors.New("SPI baud too low or above 66.5Mhz")
)

type SPI struct {
	Bus *rp.SPI0_Type
}

// time to wait on a transaction before dropping. Unit in Microseconds for compatibility with ticks().
const _SPITimeout = 10 * 1000 // 10 ms

// Tx handles read/write operation for SPI interface. Since SPI is a syncronous 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) {
	var deadline = ticks() + _SPITimeout
	for !spi.isWritable() {
		if ticks() > deadline {
			return 0, ErrSPITimeout
		}
	}

	spi.Bus.SSPDR.Set(uint32(w))

	for !spi.isReadable() {
		if ticks() > deadline {
			return 0, ErrSPITimeout
		}
	}
	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 115200 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 = 115200
	if config.SCK == 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
		}
	}
	if config.DataBits < 4 || config.DataBits > 16 {
		config.DataBits = 8
	}
	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.DataBits, config.Mode, rp.XIP_SSI_CTRLR0_SPI_FRF_STD)

	// 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(databits, mode uint8, frameFormat uint32) {
	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(databits-1)<<rp.SPI0_SSPCR0_DSS_Pos)| // Set databits (SPI word length). Valid inputs are 4-16.
			(frameFormat&0b11)<<rp.SPI0_SSPCR0_FRF_Pos, // Frame format bits 4:5
		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 {
	var deadline = ticks() + _SPITimeout
	// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
	// is full, PL022 inhibits RX pushes, and sets a sticky flag on
	// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
	for i := range tx {
		for !spi.isWritable() {
			if ticks() > deadline {
				return ErrSPITimeout
			}
		}
		spi.Bus.SSPDR.Set(uint32(tx[i]))
	}
	// 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() {
		if ticks() > deadline {
			return ErrSPITimeout
		}
	}
	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 {
	var deadline = ticks() + _SPITimeout
	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 // if reading succesfully in rx there is no need to check deadline.
		}
		if ticks() > deadline {
			return ErrSPITimeout
		}
	}
	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 {
	var deadline = ticks() + _SPITimeout
	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) && ticks() <= deadline {
		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
	}

	return nil
}