//go:build mimxrt1062 package machine // SPI peripheral abstraction layer for the MIMXRT1062 import ( "device/nxp" "errors" "unsafe" ) // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SDI Pin SDO Pin SCK Pin CS Pin LSBFirst bool Mode uint8 } func (c SPIConfig) getPins() (di, do, ck, cs Pin) { if 0 == c.SDI && 0 == c.SDO && 0 == c.SCK && 0 == c.CS { // default pins if none specified return SPI_SDI_PIN, SPI_SDO_PIN, SPI_SCK_PIN, SPI_CS_PIN } return c.SDI, c.SDO, c.SCK, c.CS } type SPI struct { Bus *nxp.LPSPI_Type // these hold the input selector ("daisy chain") values that select which pins // are connected to the LPSPI device, and should be defined where the SPI // instance is declared (e.g., in the board definition). see the godoc // comments on type muxSelect for more details. muxSDI, muxSDO, muxSCK, muxCS muxSelect // these are copied from SPIConfig, during (*SPI).Configure(SPIConfig), and // should be considered read-only for internal reference (i.e., modifying them // will have no desirable effect). sdi, sdo, sck, cs Pin frequency uint32 // auxiliary state data used internally configured bool } const ( statusTxDataRequest = nxp.LPSPI_SR_TDF // Transmit data flag statusRxDataReady = nxp.LPSPI_SR_RDF // Receive data flag statusWordComplete = nxp.LPSPI_SR_WCF // Word Complete flag statusFrameComplete = nxp.LPSPI_SR_FCF // Frame Complete flag statusTransferComplete = nxp.LPSPI_SR_TCF // Transfer Complete flag statusTransmitError = nxp.LPSPI_SR_TEF // Transmit Error flag (FIFO underrun) statusReceiveError = nxp.LPSPI_SR_REF // Receive Error flag (FIFO overrun) statusDataMatch = nxp.LPSPI_SR_DMF // Data Match flag statusModuleBusy = nxp.LPSPI_SR_MBF // Module Busy flag statusAll = nxp.LPSPI_SR_TDF | nxp.LPSPI_SR_RDF | nxp.LPSPI_SR_WCF | nxp.LPSPI_SR_FCF | nxp.LPSPI_SR_TCF | nxp.LPSPI_SR_TEF | nxp.LPSPI_SR_REF | nxp.LPSPI_SR_DMF | nxp.LPSPI_SR_MBF ) var ( errSPINotConfigured = errors.New("SPI interface is not yet configured") ) // Configure is intended to setup an SPI interface for transmit/receive. func (spi *SPI) Configure(config SPIConfig) error { const defaultSpiFreq = 4000000 // 4 MHz // init pins spi.sdi, spi.sdo, spi.sck, spi.cs = config.getPins() // configure the mux and pad control registers spi.sdi.Configure(PinConfig{Mode: PinModeSPISDI}) spi.sdo.Configure(PinConfig{Mode: PinModeSPISDO}) spi.sck.Configure(PinConfig{Mode: PinModeSPICLK}) spi.cs.Configure(PinConfig{Mode: PinModeSPICS}) // configure the mux input selector spi.muxSDI.connect() spi.muxSDO.connect() spi.muxSCK.connect() spi.muxCS.connect() // software reset of LPSPI state registers spi.Bus.CR.SetBits(nxp.LPSPI_CR_RST) // also reset FIFOs (not performed by software reset above) spi.Bus.CR.SetBits(nxp.LPSPI_CR_RRF | nxp.LPSPI_CR_RTF) spi.Bus.CR.Set(0) // set controller mode, and input data is sampled on delayed SCK edge spi.Bus.CFGR1.Set(nxp.LPSPI_CFGR1_MASTER | nxp.LPSPI_CFGR1_SAMPLE) spi.frequency = config.Frequency if 0 == spi.frequency { spi.frequency = defaultSpiFreq } // configure LPSPI clock divisor and CS assertion delays div := spi.getClockDivisor(config.Frequency) ccr := (div << nxp.LPSPI_CCR_SCKDIV_Pos) & nxp.LPSPI_CCR_SCKDIV_Msk ccr |= ((div / 2) << nxp.LPSPI_CCR_DBT_Pos) & nxp.LPSPI_CCR_DBT_Msk ccr |= ((div / 2) << nxp.LPSPI_CCR_PCSSCK_Pos) & nxp.LPSPI_CCR_PCSSCK_Msk spi.Bus.CCR.Set(ccr) // 8-bit frame size (words) tcr := uint32(7) if config.LSBFirst { tcr |= nxp.LPSPI_TCR_LSBF } // set polarity and phase switch config.Mode { case Mode1: tcr |= nxp.LPSPI_TCR_CPHA case Mode2: tcr |= nxp.LPSPI_TCR_CPOL case Mode3: tcr |= nxp.LPSPI_TCR_CPOL tcr |= nxp.LPSPI_TCR_CPHA } spi.Bus.TCR.Set(tcr) // clear FIFO water marks spi.setWatermark(0, 0) // enable LPSPI module spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) spi.configured = true return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { if !spi.configured { return 0, errSPINotConfigured } const readTryMax = 10000 for spi.Bus.SR.HasBits(statusModuleBusy) { } // wait for SPI busy bit to clear _, txFIFOSize := spi.getFIFOSize() spi.flushFIFO(true, true) spi.Bus.SR.Set(statusAll) // clear all status flags (W1C) // enable LPSPI module spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) // TODO: unnecessary since we just flushed the FIFO? for { // wait for TX FIFO to not be full if _, txFIFO := spi.getFIFOCount(); txFIFO < txFIFOSize { break } } // write out byte to TX FIFO spi.Bus.TDR.Set(uint32(w)) // try to read from RX FIFO if anything exists didRead := false data := byte(0) for i := 0; !didRead && (i < readTryMax); i++ { rxFIFO, _ := spi.getFIFOCount() didRead = rxFIFO > 0 if didRead { data = byte(spi.Bus.RDR.Get()) } } // if nothing was read, then wait for transfer complete flag to decide when // we are finished if !didRead { for !spi.Bus.SR.HasBits(nxp.LPSPI_SR_TCF) { } // wait for all transfers complete flag to set } return data, nil } func (spi *SPI) isHardwareCSPin(pin Pin) bool { switch unsafe.Pointer(spi.Bus) { case unsafe.Pointer(nxp.LPSPI1): return SPI1_CS_PIN == pin case unsafe.Pointer(nxp.LPSPI2): return SPI2_CS_PIN == pin case unsafe.Pointer(nxp.LPSPI3): return SPI3_CS_PIN == pin } return false } func (spi *SPI) hasHardwareCSPin() bool { return spi.isHardwareCSPin(spi.cs) } // getClockDivisor finds the SPI prescalar that minimizes the error between // requested frequency and possible frequencies available with the LPSPI clock. // this routine is based on Teensyduino (libraries/SPI/SPI.cpp): // // void SPIClass::setClockDivider_noInline(uint32_t clk) func (spi *SPI) getClockDivisor(freq uint32) uint32 { const clock = 132000000 // LPSPI root clock frequency (PLL2) d := uint32(clock) if freq > 0 { d /= freq } if d > 0 && clock/d > freq { d++ } if d > 257 { return 255 } if d > 2 { return d - 2 } return 0 } func (spi *SPI) getFIFOSize() (rx, tx uint32) { param := spi.Bus.PARAM.Get() return uint32(1) << ((param & nxp.LPSPI_PARAM_RXFIFO_Msk) >> nxp.LPSPI_PARAM_RXFIFO_Pos), uint32(1) << ((param & nxp.LPSPI_PARAM_TXFIFO_Msk) >> nxp.LPSPI_PARAM_TXFIFO_Pos) } func (spi *SPI) getFIFOCount() (rx, tx uint32) { fsr := spi.Bus.FSR.Get() return (fsr & nxp.LPSPI_FSR_RXCOUNT_Msk) >> nxp.LPSPI_FSR_RXCOUNT_Pos, (fsr & nxp.LPSPI_FSR_TXCOUNT_Msk) >> nxp.LPSPI_FSR_TXCOUNT_Pos } func (spi *SPI) flushFIFO(rx, tx bool) { var flush uint32 if rx { flush |= nxp.LPSPI_CR_RRF } if tx { flush |= nxp.LPSPI_CR_RTF } spi.Bus.CR.SetBits(flush) } func (spi *SPI) setWatermark(rx, tx uint32) { spi.Bus.FCR.Set(((rx << nxp.LPSPI_FCR_RXWATER_Pos) & nxp.LPSPI_FCR_RXWATER_Msk) | ((tx << nxp.LPSPI_FCR_TXWATER_Pos) & nxp.LPSPI_FCR_TXWATER_Msk)) }