|
|
|
|
|
|
|
|
#include "SPI.h" |
|
|
#include "SPI.h" |
|
|
#include "pins_arduino.h" |
|
|
#include "pins_arduino.h" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//#define DEBUG_DMA_TRANSFERS |
|
|
|
|
|
|
|
|
/**********************************************************/ |
|
|
/**********************************************************/ |
|
|
/* 8 bit AVR-based boards */ |
|
|
/* 8 bit AVR-based boards */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t fastio = IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3); |
|
|
uint32_t fastio = IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3); |
|
|
//uint32_t fastio = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3); |
|
|
//uint32_t fastio = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3); |
|
|
Serial.printf("SPI MISO: %d MOSI: %d, SCK: %d\n", hardware().miso_pin[miso_pin_index], hardware().mosi_pin[mosi_pin_index], hardware().sck_pin[sck_pin_index]); |
|
|
|
|
|
|
|
|
//Serial.printf("SPI MISO: %d MOSI: %d, SCK: %d\n", hardware().miso_pin[miso_pin_index], hardware().mosi_pin[mosi_pin_index], hardware().sck_pin[sck_pin_index]); |
|
|
*(portControlRegister(hardware().miso_pin[miso_pin_index])) = fastio; |
|
|
*(portControlRegister(hardware().miso_pin[miso_pin_index])) = fastio; |
|
|
*(portControlRegister(hardware().mosi_pin[mosi_pin_index])) = fastio; |
|
|
*(portControlRegister(hardware().mosi_pin[mosi_pin_index])) = fastio; |
|
|
*(portControlRegister(hardware().sck_pin[sck_pin_index])) = fastio; |
|
|
*(portControlRegister(hardware().sck_pin[sck_pin_index])) = fastio; |
|
|
|
|
|
|
|
|
//SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; |
|
|
//SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} |
|
|
|
|
|
|
|
|
const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi4_hardware = { |
|
|
const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi4_hardware = { |
|
|
CCM_CCGR1, CCM_CCGR1_LPSPI4(CCM_CCGR_ON), |
|
|
CCM_CCGR1, CCM_CCGR1_LPSPI4(CCM_CCGR_ON), |
|
|
|
|
|
DMAMUX_SOURCE_LPSPI4_TX, DMAMUX_SOURCE_LPSPI4_RX, _spi_dma_rxISR0, |
|
|
12, |
|
|
12, |
|
|
3 | 0x10, |
|
|
3 | 0x10, |
|
|
11, |
|
|
11, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Pass 1 keep it simple and don't try packing 8 bits into 16 yet.. |
|
|
// Pass 1 keep it simple and don't try packing 8 bits into 16 yet.. |
|
|
// Lets clear the reader queue |
|
|
// Lets clear the reader queue |
|
|
//port().CR = LPSPI_CR_RRF; |
|
|
|
|
|
|
|
|
port().CR = LPSPI_CR_RRF | LPSPI_CR_MEN; // clear the queue and make sure still enabled. |
|
|
|
|
|
|
|
|
while (count > 0) { |
|
|
while (count > 0) { |
|
|
// Push out the next byte; |
|
|
// Push out the next byte; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SPIClass::end(){} |
|
|
void SPIClass::end(){} |
|
|
|
|
|
|
|
|
|
|
|
//============================================================================= |
|
|
|
|
|
// ASYNCH Support |
|
|
|
|
|
//============================================================================= |
|
|
|
|
|
//========================================================================= |
|
|
|
|
|
// Try Transfer using DMA. |
|
|
|
|
|
//========================================================================= |
|
|
|
|
|
#ifdef SPI_HAS_TRANSFER_ASYNC |
|
|
|
|
|
static uint8_t bit_bucket; |
|
|
|
|
|
#define dontInterruptAtCompletion(dmac) (dmac)->TCD->CSR &= ~DMA_TCD_CSR_INTMAJOR |
|
|
|
|
|
|
|
|
|
|
|
//========================================================================= |
|
|
|
|
|
// Init the DMA channels |
|
|
|
|
|
//========================================================================= |
|
|
|
|
|
bool SPIClass::initDMAChannels() { |
|
|
|
|
|
// Allocate our channels. |
|
|
|
|
|
_dmaTX = new DMAChannel(); |
|
|
|
|
|
if (_dmaTX == nullptr) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_dmaRX = new DMAChannel(); |
|
|
|
|
|
if (_dmaRX == nullptr) { |
|
|
|
|
|
delete _dmaTX; // release it |
|
|
|
|
|
_dmaTX = nullptr; |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Let's setup the RX chain |
|
|
|
|
|
_dmaRX->disable(); |
|
|
|
|
|
_dmaRX->source((volatile uint8_t&)port().RDR); |
|
|
|
|
|
_dmaRX->disableOnCompletion(); |
|
|
|
|
|
_dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); |
|
|
|
|
|
_dmaRX->attachInterrupt(hardware().dma_rxisr); |
|
|
|
|
|
_dmaRX->interruptAtCompletion(); |
|
|
|
|
|
|
|
|
|
|
|
// We may be using settings chain here so lets set it up. |
|
|
|
|
|
// Now lets setup TX chain. Note if trigger TX is not set |
|
|
|
|
|
// we need to have the RX do it for us. |
|
|
|
|
|
_dmaTX->disable(); |
|
|
|
|
|
_dmaTX->destination((volatile uint8_t&)port().TDR); |
|
|
|
|
|
_dmaTX->disableOnCompletion(); |
|
|
|
|
|
|
|
|
|
|
|
if (hardware().tx_dma_channel) { |
|
|
|
|
|
_dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); |
|
|
|
|
|
} else { |
|
|
|
|
|
// Serial.printf("SPI InitDMA tx triger by RX: %x\n", (uint32_t)_dmaRX); |
|
|
|
|
|
_dmaTX->triggerAtTransfersOf(*_dmaRX); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_dma_state = DMAState::idle; // Should be first thing set! |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//========================================================================= |
|
|
|
|
|
// Main Async Transfer function |
|
|
|
|
|
//========================================================================= |
|
|
|
|
|
#ifndef TRANSFER_COUNT_FIXED |
|
|
|
|
|
inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { |
|
|
|
|
|
// note does no validation of length... |
|
|
|
|
|
DMABaseClass::TCD_t *tcd = dmac->TCD; |
|
|
|
|
|
if (!(tcd->BITER & DMA_TCD_BITER_ELINK)) { |
|
|
|
|
|
tcd->BITER = len & 0x7fff; |
|
|
|
|
|
} else { |
|
|
|
|
|
tcd->BITER = (tcd->BITER & 0xFE00) | (len & 0x1ff); |
|
|
|
|
|
} |
|
|
|
|
|
tcd->CITER = tcd->BITER; |
|
|
|
|
|
} |
|
|
|
|
|
#else |
|
|
|
|
|
inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { |
|
|
|
|
|
dmac->transferCount(len); |
|
|
|
|
|
} |
|
|
|
|
|
#endif |
|
|
|
|
|
#ifdef DEBUG_DMA_TRANSFERS |
|
|
|
|
|
void dumpDMA_TCD(DMABaseClass *dmabc) |
|
|
|
|
|
{ |
|
|
|
|
|
Serial4.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD); |
|
|
|
|
|
|
|
|
|
|
|
Serial4.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR, |
|
|
|
|
|
dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR, |
|
|
|
|
|
dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER); |
|
|
|
|
|
} |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { |
|
|
|
|
|
if (_dma_state == DMAState::notAllocated) { |
|
|
|
|
|
if (!initDMAChannels()) |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (_dma_state == DMAState::active) |
|
|
|
|
|
return false; // already active |
|
|
|
|
|
|
|
|
|
|
|
event_responder.clearEvent(); // Make sure it is not set yet |
|
|
|
|
|
if (count < 2) { |
|
|
|
|
|
// Use non-async version to simplify cases... |
|
|
|
|
|
transfer(buf, retbuf, count); |
|
|
|
|
|
event_responder.triggerEvent(); |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Now handle the cases where the count > then how many we can output in one DMA request |
|
|
|
|
|
if (count > MAX_DMA_COUNT) { |
|
|
|
|
|
_dma_count_remaining = count - MAX_DMA_COUNT; |
|
|
|
|
|
count = MAX_DMA_COUNT; |
|
|
|
|
|
} else { |
|
|
|
|
|
_dma_count_remaining = 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Now See if caller passed in a source buffer. |
|
|
|
|
|
_dmaTX->TCD->ATTR_DST = 0; // Make sure set for 8 bit mode |
|
|
|
|
|
uint8_t *write_data = (uint8_t*) buf; |
|
|
|
|
|
if (buf) { |
|
|
|
|
|
_dmaTX->sourceBuffer((uint8_t*)write_data, count); |
|
|
|
|
|
_dmaTX->TCD->SLAST = 0; // Finish with it pointing to next location |
|
|
|
|
|
if ((uint32_t)write_data >= 0x20200000u) arm_dcache_flush(write_data, count); |
|
|
|
|
|
} else { |
|
|
|
|
|
_dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value |
|
|
|
|
|
DMAChanneltransferCount(_dmaTX, count); |
|
|
|
|
|
} |
|
|
|
|
|
if (retbuf) { |
|
|
|
|
|
// On T3.5 must handle SPI1/2 differently as only one DMA channel |
|
|
|
|
|
_dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... |
|
|
|
|
|
_dmaRX->destinationBuffer((uint8_t*)retbuf, count); |
|
|
|
|
|
_dmaRX->TCD->DLASTSGA = 0; // At end point after our bufffer |
|
|
|
|
|
if ((uint32_t)retbuf >= 0x20200000u) arm_dcache_delete(retbuf, count); |
|
|
|
|
|
} else { |
|
|
|
|
|
// Write only mode |
|
|
|
|
|
_dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... |
|
|
|
|
|
_dmaRX->destination((uint8_t&)bit_bucket); |
|
|
|
|
|
DMAChanneltransferCount(_dmaRX, count); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_dma_event_responder = &event_responder; |
|
|
|
|
|
// Now try to start it? |
|
|
|
|
|
// Setup DMA main object |
|
|
|
|
|
yield(); |
|
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG_DMA_TRANSFERS |
|
|
|
|
|
// Lets dump TX, RX |
|
|
|
|
|
dumpDMA_TCD(_dmaTX); |
|
|
|
|
|
dumpDMA_TCD(_dmaRX); |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
// Make sure port is in 8 bit mode and clear watermark |
|
|
|
|
|
port().TCR = (port().TCR & ~(LPSPI_TCR_FRAMESZ(31))) | LPSPI_TCR_FRAMESZ(7); |
|
|
|
|
|
port().FCR = 0; |
|
|
|
|
|
|
|
|
|
|
|
// Lets try to output the first byte to make sure that we are in 8 bit mode... |
|
|
|
|
|
port().DER = LPSPI_DER_TDDE | LPSPI_DER_RDDE; //enable DMA on both TX and RX |
|
|
|
|
|
port().SR = 0x3f00; // clear out all of the other status... |
|
|
|
|
|
|
|
|
|
|
|
_dmaRX->enable(); |
|
|
|
|
|
_dmaTX->enable(); |
|
|
|
|
|
|
|
|
|
|
|
_dma_state = DMAState::active; |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------- |
|
|
|
|
|
// DMA RX ISR |
|
|
|
|
|
//------------------------------------------------------------------------- |
|
|
|
|
|
void SPIClass::dma_rxisr(void) { |
|
|
|
|
|
_dmaRX->clearInterrupt(); |
|
|
|
|
|
_dmaTX->clearComplete(); |
|
|
|
|
|
_dmaRX->clearComplete(); |
|
|
|
|
|
|
|
|
|
|
|
if (_dma_count_remaining) { |
|
|
|
|
|
// What do I need to do to start it back up again... |
|
|
|
|
|
// We will use the BITR/CITR from RX as TX may have prefed some stuff |
|
|
|
|
|
if (_dma_count_remaining > MAX_DMA_COUNT) { |
|
|
|
|
|
_dma_count_remaining -= MAX_DMA_COUNT; |
|
|
|
|
|
} else { |
|
|
|
|
|
DMAChanneltransferCount(_dmaTX, _dma_count_remaining); |
|
|
|
|
|
DMAChanneltransferCount(_dmaRX, _dma_count_remaining); |
|
|
|
|
|
|
|
|
|
|
|
_dma_count_remaining = 0; |
|
|
|
|
|
} |
|
|
|
|
|
_dmaRX->enable(); |
|
|
|
|
|
_dmaTX->enable(); |
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
port().FCR = LPSPI_FCR_TXWATER(15); // _spi_fcr_save; // restore the FSR status... |
|
|
|
|
|
port().DER = 0; // DMA no longer doing TX (or RX) |
|
|
|
|
|
|
|
|
|
|
|
port().CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF; // actually clear both... |
|
|
|
|
|
port().SR = 0x3f00; // clear out all of the other status... |
|
|
|
|
|
|
|
|
|
|
|
_dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again |
|
|
|
|
|
_dma_event_responder->triggerEvent(); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
#endif // SPI_HAS_TRANSFER_ASYNC |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endif |
|
|
#endif |