From c5de68622edca9f9c518521321fca0a46af7db95 Mon Sep 17 00:00:00 2001 From: ardnew Date: Sat, 16 Apr 2022 08:43:52 -0500 Subject: [PATCH] board/teensy40: Add SPI support (#1455) machine/teensy40: add SPI support --- src/machine/board_teensy40.go | 66 +++++++ src/machine/machine_mimxrt1062_spi.go | 251 ++++++++++++++++++++++++++ src/machine/spi.go | 4 +- 3 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/machine/machine_mimxrt1062_spi.go diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 9f6f8617..070add23 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -237,6 +237,8 @@ var ( } ) +// #==================================================================# +// | SPI | // #===========#==========#===============#===========================# // | Interface | Hardware | Clock(Freq) | SDI/SDO/SCK/CS : Alt | // #===========#==========#===============#=================-=========# @@ -261,6 +263,70 @@ const ( SPI3_CS_PIN = D36 ) +var ( + SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) + SPI1 = &_SPI1 + _SPI1 = SPI{ + Bus: nxp.LPSPI4, + muxSDI: muxSelect{ // D12 (PB1 [B0_01]) + mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, + sel: &nxp.IOMUXC.LPSPI4_SDI_SELECT_INPUT, + }, + muxSDO: muxSelect{ // D11 (PB2 [B0_02]) + mux: nxp.IOMUXC_LPSPI4_SDO_SELECT_INPUT_DAISY_GPIO_B0_02_ALT3, + sel: &nxp.IOMUXC.LPSPI4_SDO_SELECT_INPUT, + }, + muxSCK: muxSelect{ // D13 (PB3 [B0_03]) + mux: nxp.IOMUXC_LPSPI4_SCK_SELECT_INPUT_DAISY_GPIO_B0_03_ALT3, + sel: &nxp.IOMUXC.LPSPI4_SCK_SELECT_INPUT, + }, + muxCS: muxSelect{ // D10 (PB0 [B0_00]) + mux: nxp.IOMUXC_LPSPI4_PCS0_SELECT_INPUT_DAISY_GPIO_B0_00_ALT3, + sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, + }, + } + SPI2 = &_SPI2 + _SPI2 = SPI{ + Bus: nxp.LPSPI3, + muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) + mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, + sel: &nxp.IOMUXC.LPSPI3_SDI_SELECT_INPUT, + }, + muxSDO: muxSelect{ // D26 (PA30 [AD_B1_14]) + mux: nxp.IOMUXC_LPSPI3_SDO_SELECT_INPUT_DAISY_GPIO_AD_B1_14_ALT2, + sel: &nxp.IOMUXC.LPSPI3_SDO_SELECT_INPUT, + }, + muxSCK: muxSelect{ // D27 (PA31 [AD_B1_15]) + mux: nxp.IOMUXC_LPSPI3_SCK_SELECT_INPUT_DAISY_GPIO_AD_B1_15, + sel: &nxp.IOMUXC.LPSPI3_SCK_SELECT_INPUT, + }, + muxCS: muxSelect{ // D0 (PA3 [AD_B0_03]) + mux: nxp.IOMUXC_LPSPI3_PCS0_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT7, + sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, + }, + } + SPI3 = &_SPI3 + _SPI3 = SPI{ + Bus: nxp.LPSPI1, + muxSDI: muxSelect{ // D34 (PC15 [SD_B0_03]) + mux: nxp.IOMUXC_LPSPI1_SDI_SELECT_INPUT_DAISY_GPIO_SD_B0_03_ALT4, + sel: &nxp.IOMUXC.LPSPI1_SDI_SELECT_INPUT, + }, + muxSDO: muxSelect{ // D35 (PC14 [SD_B0_02]) + mux: nxp.IOMUXC_LPSPI1_SDO_SELECT_INPUT_DAISY_GPIO_SD_B0_02_ALT4, + sel: &nxp.IOMUXC.LPSPI1_SDO_SELECT_INPUT, + }, + muxSCK: muxSelect{ // D37 (PC12 [SD_B0_00]) + mux: nxp.IOMUXC_LPSPI1_SCK_SELECT_INPUT_DAISY_GPIO_SD_B0_00_ALT4, + sel: &nxp.IOMUXC.LPSPI1_SCK_SELECT_INPUT, + }, + muxCS: muxSelect{ // D36 (PC13 [SD_B0_01]) + mux: nxp.IOMUXC_LPSPI1_PCS0_SELECT_INPUT_DAISY_GPIO_SD_B0_01_ALT4, + sel: &nxp.IOMUXC.LPSPI1_PCS0_SELECT_INPUT, + }, + } +) + // #====================================================# // | I2C | // #===========#==========#=============#===============# diff --git a/src/machine/machine_mimxrt1062_spi.go b/src/machine/machine_mimxrt1062_spi.go new file mode 100644 index 00000000..58440a89 --- /dev/null +++ b/src/machine/machine_mimxrt1062_spi.go @@ -0,0 +1,251 @@ +// +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) { + + 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 +} + +// 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)) +} diff --git a/src/machine/spi.go b/src/machine/spi.go index 0541f5b5..a8d4d329 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -1,5 +1,5 @@ -//go:build !baremetal || (stm32 && !stm32f7x2 && !stm32l5x2) || fe310 || k210 || atmega -// +build !baremetal stm32,!stm32f7x2,!stm32l5x2 fe310 k210 atmega +//go:build !baremetal || (stm32 && !stm32f7x2 && !stm32l5x2) || fe310 || k210 || (nxp && !mk66f18) || atmega +// +build !baremetal stm32,!stm32f7x2,!stm32l5x2 fe310 k210 nxp,!mk66f18 atmega package machine