github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/machine/machine_mimxrt1062_spi.go (about) 1 //go:build mimxrt1062 2 3 package machine 4 5 // SPI peripheral abstraction layer for the MIMXRT1062 6 7 import ( 8 "device/nxp" 9 "errors" 10 "unsafe" 11 ) 12 13 // SPIConfig is used to store config info for SPI. 14 type SPIConfig struct { 15 Frequency uint32 16 SDI Pin 17 SDO Pin 18 SCK Pin 19 CS Pin 20 LSBFirst bool 21 Mode uint8 22 } 23 24 func (c SPIConfig) getPins() (di, do, ck, cs Pin) { 25 if 0 == c.SDI && 0 == c.SDO && 0 == c.SCK && 0 == c.CS { 26 // default pins if none specified 27 return SPI_SDI_PIN, SPI_SDO_PIN, SPI_SCK_PIN, SPI_CS_PIN 28 } 29 return c.SDI, c.SDO, c.SCK, c.CS 30 } 31 32 type SPI struct { 33 Bus *nxp.LPSPI_Type 34 35 // these hold the input selector ("daisy chain") values that select which pins 36 // are connected to the LPSPI device, and should be defined where the SPI 37 // instance is declared (e.g., in the board definition). see the godoc 38 // comments on type muxSelect for more details. 39 muxSDI, muxSDO, muxSCK, muxCS muxSelect 40 41 // these are copied from SPIConfig, during (*SPI).Configure(SPIConfig), and 42 // should be considered read-only for internal reference (i.e., modifying them 43 // will have no desirable effect). 44 sdi, sdo, sck, cs Pin 45 frequency uint32 46 47 // auxiliary state data used internally 48 configured bool 49 } 50 51 const ( 52 statusTxDataRequest = nxp.LPSPI_SR_TDF // Transmit data flag 53 statusRxDataReady = nxp.LPSPI_SR_RDF // Receive data flag 54 statusWordComplete = nxp.LPSPI_SR_WCF // Word Complete flag 55 statusFrameComplete = nxp.LPSPI_SR_FCF // Frame Complete flag 56 statusTransferComplete = nxp.LPSPI_SR_TCF // Transfer Complete flag 57 statusTransmitError = nxp.LPSPI_SR_TEF // Transmit Error flag (FIFO underrun) 58 statusReceiveError = nxp.LPSPI_SR_REF // Receive Error flag (FIFO overrun) 59 statusDataMatch = nxp.LPSPI_SR_DMF // Data Match flag 60 statusModuleBusy = nxp.LPSPI_SR_MBF // Module Busy flag 61 statusAll = nxp.LPSPI_SR_TDF | nxp.LPSPI_SR_RDF | 62 nxp.LPSPI_SR_WCF | nxp.LPSPI_SR_FCF | nxp.LPSPI_SR_TCF | nxp.LPSPI_SR_TEF | 63 nxp.LPSPI_SR_REF | nxp.LPSPI_SR_DMF | nxp.LPSPI_SR_MBF 64 ) 65 66 var ( 67 errSPINotConfigured = errors.New("SPI interface is not yet configured") 68 ) 69 70 // Configure is intended to setup an SPI interface for transmit/receive. 71 func (spi *SPI) Configure(config SPIConfig) error { 72 73 const defaultSpiFreq = 4000000 // 4 MHz 74 75 // init pins 76 spi.sdi, spi.sdo, spi.sck, spi.cs = config.getPins() 77 78 // configure the mux and pad control registers 79 spi.sdi.Configure(PinConfig{Mode: PinModeSPISDI}) 80 spi.sdo.Configure(PinConfig{Mode: PinModeSPISDO}) 81 spi.sck.Configure(PinConfig{Mode: PinModeSPICLK}) 82 spi.cs.Configure(PinConfig{Mode: PinModeSPICS}) 83 84 // configure the mux input selector 85 spi.muxSDI.connect() 86 spi.muxSDO.connect() 87 spi.muxSCK.connect() 88 spi.muxCS.connect() 89 90 // software reset of LPSPI state registers 91 spi.Bus.CR.SetBits(nxp.LPSPI_CR_RST) 92 // also reset FIFOs (not performed by software reset above) 93 spi.Bus.CR.SetBits(nxp.LPSPI_CR_RRF | nxp.LPSPI_CR_RTF) 94 spi.Bus.CR.Set(0) 95 96 // set controller mode, and input data is sampled on delayed SCK edge 97 spi.Bus.CFGR1.Set(nxp.LPSPI_CFGR1_MASTER | nxp.LPSPI_CFGR1_SAMPLE) 98 99 spi.frequency = config.Frequency 100 if 0 == spi.frequency { 101 spi.frequency = defaultSpiFreq 102 } 103 104 // configure LPSPI clock divisor and CS assertion delays 105 div := spi.getClockDivisor(config.Frequency) 106 ccr := (div << nxp.LPSPI_CCR_SCKDIV_Pos) & nxp.LPSPI_CCR_SCKDIV_Msk 107 ccr |= ((div / 2) << nxp.LPSPI_CCR_DBT_Pos) & nxp.LPSPI_CCR_DBT_Msk 108 ccr |= ((div / 2) << nxp.LPSPI_CCR_PCSSCK_Pos) & nxp.LPSPI_CCR_PCSSCK_Msk 109 spi.Bus.CCR.Set(ccr) 110 111 // 8-bit frame size (words) 112 tcr := uint32(7) 113 if config.LSBFirst { 114 tcr |= nxp.LPSPI_TCR_LSBF 115 } 116 // set polarity and phase 117 switch config.Mode { 118 case Mode1: 119 tcr |= nxp.LPSPI_TCR_CPHA 120 case Mode2: 121 tcr |= nxp.LPSPI_TCR_CPOL 122 case Mode3: 123 tcr |= nxp.LPSPI_TCR_CPOL 124 tcr |= nxp.LPSPI_TCR_CPHA 125 } 126 spi.Bus.TCR.Set(tcr) 127 128 // clear FIFO water marks 129 spi.setWatermark(0, 0) 130 131 // enable LPSPI module 132 spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) 133 134 spi.configured = true 135 136 return nil 137 } 138 139 // Transfer writes/reads a single byte using the SPI interface. 140 func (spi *SPI) Transfer(w byte) (byte, error) { 141 if !spi.configured { 142 return 0, errSPINotConfigured 143 } 144 145 const readTryMax = 10000 146 147 for spi.Bus.SR.HasBits(statusModuleBusy) { 148 } // wait for SPI busy bit to clear 149 150 _, txFIFOSize := spi.getFIFOSize() 151 152 spi.flushFIFO(true, true) 153 spi.Bus.SR.Set(statusAll) // clear all status flags (W1C) 154 155 // enable LPSPI module 156 spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) 157 158 // TODO: unnecessary since we just flushed the FIFO? 159 for { // wait for TX FIFO to not be full 160 if _, txFIFO := spi.getFIFOCount(); txFIFO < txFIFOSize { 161 break 162 } 163 } 164 165 // write out byte to TX FIFO 166 spi.Bus.TDR.Set(uint32(w)) 167 168 // try to read from RX FIFO if anything exists 169 didRead := false 170 data := byte(0) 171 for i := 0; !didRead && (i < readTryMax); i++ { 172 rxFIFO, _ := spi.getFIFOCount() 173 didRead = rxFIFO > 0 174 if didRead { 175 data = byte(spi.Bus.RDR.Get()) 176 } 177 } 178 179 // if nothing was read, then wait for transfer complete flag to decide when 180 // we are finished 181 if !didRead { 182 for !spi.Bus.SR.HasBits(nxp.LPSPI_SR_TCF) { 183 } // wait for all transfers complete flag to set 184 } 185 186 return data, nil 187 } 188 189 func (spi *SPI) isHardwareCSPin(pin Pin) bool { 190 switch unsafe.Pointer(spi.Bus) { 191 case unsafe.Pointer(nxp.LPSPI1): 192 return SPI1_CS_PIN == pin 193 case unsafe.Pointer(nxp.LPSPI2): 194 return SPI2_CS_PIN == pin 195 case unsafe.Pointer(nxp.LPSPI3): 196 return SPI3_CS_PIN == pin 197 } 198 return false 199 } 200 201 func (spi *SPI) hasHardwareCSPin() bool { 202 return spi.isHardwareCSPin(spi.cs) 203 } 204 205 // getClockDivisor finds the SPI prescalar that minimizes the error between 206 // requested frequency and possible frequencies available with the LPSPI clock. 207 // this routine is based on Teensyduino (libraries/SPI/SPI.cpp): 208 // 209 // void SPIClass::setClockDivider_noInline(uint32_t clk) 210 func (spi *SPI) getClockDivisor(freq uint32) uint32 { 211 const clock = 132000000 // LPSPI root clock frequency (PLL2) 212 d := uint32(clock) 213 if freq > 0 { 214 d /= freq 215 } 216 if d > 0 && clock/d > freq { 217 d++ 218 } 219 if d > 257 { 220 return 255 221 } 222 if d > 2 { 223 return d - 2 224 } 225 return 0 226 } 227 228 func (spi *SPI) getFIFOSize() (rx, tx uint32) { 229 param := spi.Bus.PARAM.Get() 230 return uint32(1) << ((param & nxp.LPSPI_PARAM_RXFIFO_Msk) >> nxp.LPSPI_PARAM_RXFIFO_Pos), 231 uint32(1) << ((param & nxp.LPSPI_PARAM_TXFIFO_Msk) >> nxp.LPSPI_PARAM_TXFIFO_Pos) 232 } 233 234 func (spi *SPI) getFIFOCount() (rx, tx uint32) { 235 fsr := spi.Bus.FSR.Get() 236 return (fsr & nxp.LPSPI_FSR_RXCOUNT_Msk) >> nxp.LPSPI_FSR_RXCOUNT_Pos, 237 (fsr & nxp.LPSPI_FSR_TXCOUNT_Msk) >> nxp.LPSPI_FSR_TXCOUNT_Pos 238 } 239 240 func (spi *SPI) flushFIFO(rx, tx bool) { 241 var flush uint32 242 if rx { 243 flush |= nxp.LPSPI_CR_RRF 244 } 245 if tx { 246 flush |= nxp.LPSPI_CR_RTF 247 } 248 spi.Bus.CR.SetBits(flush) 249 } 250 251 func (spi *SPI) setWatermark(rx, tx uint32) { 252 spi.Bus.FCR.Set(((rx << nxp.LPSPI_FCR_RXWATER_Pos) & nxp.LPSPI_FCR_RXWATER_Msk) | 253 ((tx << nxp.LPSPI_FCR_TXWATER_Pos) & nxp.LPSPI_FCR_TXWATER_Msk)) 254 }