board/teensy40: Add SPI support (#1455)

machine/teensy40: add SPI support
Этот коммит содержится в:
ardnew 2022-04-16 08:43:52 -05:00 коммит произвёл GitHub
родитель 8fb93fbac4
коммит c5de68622e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 319 добавлений и 2 удалений

Просмотреть файл

@ -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 |
// #===========#==========#=============#===============#

251
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))
}

Просмотреть файл

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