//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<