From 443aaa7991a611946e91c601598971158f070f12 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Dec 2024 11:21:51 +0100 Subject: feature: make SPI implementation shared for rp2040/rp2350 Signed-off-by: deadprogram --- src/machine/machine_rp2040_spi.go | 407 -------------------------------------- src/machine/machine_rp2_spi.go | 406 +++++++++++++++++++++++++++++++++++++ src/machine/spi.go | 2 +- 3 files changed, 407 insertions(+), 408 deletions(-) delete mode 100644 src/machine/machine_rp2040_spi.go create mode 100644 src/machine/machine_rp2_spi.go (limited to 'src') diff --git a/src/machine/machine_rp2040_spi.go b/src/machine/machine_rp2040_spi.go deleted file mode 100644 index dbb063cb3..000000000 --- a/src/machine/machine_rp2040_spi.go +++ /dev/null @@ -1,407 +0,0 @@ -//go:build rp2040 - -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) + 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, 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(mode uint8, frameFormat uint32) { - cpha := uint32(mode) & 1 - cpol := uint32(mode>>1) & 1 - spi.Bus.SSPCR0.ReplaceBits( - (cpha< 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) + 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<