|
|
@@ -27,6 +27,8 @@ uint8_t SPIClass::interruptSave = 0; |
|
|
|
#ifdef SPI_TRANSACTION_MISMATCH_LED |
|
|
|
uint8_t SPIClass::inTransactionFlag = 0; |
|
|
|
#endif |
|
|
|
uint8_t SPIClass::_transferWriteFill = 0; |
|
|
|
|
|
|
|
|
|
|
|
void SPIClass::begin() |
|
|
|
{ |
|
|
@@ -138,16 +140,63 @@ void SPIClass::usingInterrupt(uint8_t interruptNumber) |
|
|
|
SREG = stmp; |
|
|
|
} |
|
|
|
|
|
|
|
void SPIClass::transfer(const void * buf, void * retbuf, uint32_t count) { |
|
|
|
if (count == 0) return; |
|
|
|
|
|
|
|
const uint8_t *p = (const uint8_t *)buf; |
|
|
|
uint8_t *pret = (uint8_t *)retbuf; |
|
|
|
uint8_t in; |
|
|
|
|
|
|
|
uint8_t out = p ? *p++ : _transferWriteFill; |
|
|
|
SPDR = out; |
|
|
|
while (--count > 0) { |
|
|
|
if (p) { |
|
|
|
out = *p++; |
|
|
|
} |
|
|
|
while (!(SPSR & _BV(SPIF))) ; |
|
|
|
in = SPDR; |
|
|
|
SPDR = out; |
|
|
|
if (pret)*pret++ = in; |
|
|
|
} |
|
|
|
while (!(SPSR & _BV(SPIF))) ; |
|
|
|
in = SPDR; |
|
|
|
if (pret)*pret = in; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************/ |
|
|
|
/* 32 bit Teensy 3.x */ |
|
|
|
/**********************************************************/ |
|
|
|
|
|
|
|
#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK) |
|
|
|
#if defined(KINETISK) && defined( SPI_HAS_TRANSFER_ASYNC) |
|
|
|
|
|
|
|
#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 |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#if defined(__MK20DX128__) || defined(__MK20DX256__) |
|
|
|
void _spi_dma_rxISR0(void) {/*SPI.dma_rxisr();*/} |
|
|
|
#ifdef SPI_HAS_TRANSFER_ASYNC |
|
|
|
void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} |
|
|
|
#else |
|
|
|
void _spi_dma_rxISR0(void) {;} |
|
|
|
#endif |
|
|
|
|
|
|
|
const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { |
|
|
|
SIM_SCGC6, SIM_SCGC6_SPI0, 4, IRQ_SPI0, |
|
|
|
32767, DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, |
|
|
@@ -165,9 +214,15 @@ const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { |
|
|
|
SPIClass SPI((uintptr_t)&KINETISK_SPI0, (uintptr_t)&SPIClass::spi0_hardware); |
|
|
|
|
|
|
|
#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) |
|
|
|
void _spi_dma_rxISR0(void) {/*SPI.dma_rxisr();*/} |
|
|
|
void _spi_dma_rxISR1(void) {/*SPI1.dma_rxisr();*/} |
|
|
|
void _spi_dma_rxISR2(void) {/*SPI2.dma_rxisr();*/} |
|
|
|
#ifdef SPI_HAS_TRANSFER_ASYNC |
|
|
|
void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} |
|
|
|
void _spi_dma_rxISR1(void) {SPI1.dma_rxisr();} |
|
|
|
void _spi_dma_rxISR2(void) {SPI2.dma_rxisr();} |
|
|
|
#else |
|
|
|
void _spi_dma_rxISR0(void) {;} |
|
|
|
void _spi_dma_rxISR1(void) {;} |
|
|
|
void _spi_dma_rxISR2(void) {;} |
|
|
|
#endif |
|
|
|
const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { |
|
|
|
SIM_SCGC6, SIM_SCGC6_SPI0, 4, IRQ_SPI0, |
|
|
|
32767, DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, |
|
|
@@ -473,73 +528,367 @@ void SPIClass::setSCK(uint8_t pin) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void SPIClass::transfer(void *buf, size_t count) |
|
|
|
void SPIClass::transfer(const void * buf, void * retbuf, size_t count) |
|
|
|
{ |
|
|
|
|
|
|
|
if (count == 0) return; |
|
|
|
uint8_t *p_write = (uint8_t *)buf; |
|
|
|
uint8_t *p_read = p_write; |
|
|
|
size_t count_read = count; |
|
|
|
bool lsbfirst = (port().CTAR0 & SPI_CTAR_LSBFE) ? true : false; |
|
|
|
uint32_t sr, full_mask; |
|
|
|
|
|
|
|
// Lets clear the reader queue |
|
|
|
port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); |
|
|
|
|
|
|
|
// Now lets loop while we still have data to output |
|
|
|
if (count & 1) { |
|
|
|
if (count > 1) |
|
|
|
port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); |
|
|
|
else |
|
|
|
port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); |
|
|
|
count--; |
|
|
|
} |
|
|
|
|
|
|
|
full_mask = (hardware().queue_size-1) << 12; |
|
|
|
while (count > 0) { |
|
|
|
// Push out the next byte |
|
|
|
uint16_t w = (*p_write++) << 8; |
|
|
|
w |= *p_write++; |
|
|
|
if (lsbfirst) w = __builtin_bswap16(w); |
|
|
|
if (count == 2) |
|
|
|
port().PUSHR = w | SPI_PUSHR_CTAS(1); |
|
|
|
else |
|
|
|
port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); |
|
|
|
count -= 2; // how many bytes to output. |
|
|
|
// Make sure queue is not full before pushing next byte out |
|
|
|
do { |
|
|
|
if (!(port().CTAR0 & SPI_CTAR_LSBFE)) { |
|
|
|
// We are doing the standard MSB order |
|
|
|
const uint8_t *p_write = (const uint8_t *)buf; |
|
|
|
uint8_t *p_read = (uint8_t *)retbuf; |
|
|
|
size_t count_read = count; |
|
|
|
|
|
|
|
// Lets clear the reader queue |
|
|
|
port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); |
|
|
|
|
|
|
|
uint32_t sr; |
|
|
|
|
|
|
|
// Now lets loop while we still have data to output |
|
|
|
if (count & 1) { |
|
|
|
if (p_write) { |
|
|
|
if (count > 1) |
|
|
|
port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); |
|
|
|
else |
|
|
|
port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); |
|
|
|
} else { |
|
|
|
if (count > 1) |
|
|
|
port().PUSHR = _transferWriteFill | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); |
|
|
|
else |
|
|
|
port().PUSHR = _transferWriteFill | SPI_PUSHR_CTAS(0); |
|
|
|
} |
|
|
|
count--; |
|
|
|
} |
|
|
|
|
|
|
|
uint16_t w = (uint16_t)(_transferWriteFill << 8) | _transferWriteFill; |
|
|
|
|
|
|
|
while (count > 0) { |
|
|
|
// Push out the next byte; |
|
|
|
if (p_write) { |
|
|
|
w = (*p_write++) << 8; |
|
|
|
w |= *p_write++; |
|
|
|
} |
|
|
|
uint16_t queue_full_status_mask = (hardware().queue_size-1) << 12; |
|
|
|
if (count == 2) |
|
|
|
port().PUSHR = w | SPI_PUSHR_CTAS(1); |
|
|
|
else |
|
|
|
port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); |
|
|
|
count -= 2; // how many bytes to output. |
|
|
|
// Make sure queue is not full before pushing next byte out |
|
|
|
do { |
|
|
|
sr = port().SR; |
|
|
|
if (sr & 0xF0) { |
|
|
|
uint16_t w = port().POPR; // Read any pending RX bytes in |
|
|
|
if (count_read & 1) { |
|
|
|
if (p_read) { |
|
|
|
*p_read++ = w; // Read any pending RX bytes in |
|
|
|
} |
|
|
|
count_read--; |
|
|
|
} else { |
|
|
|
if (p_read) { |
|
|
|
*p_read++ = w >> 8; |
|
|
|
*p_read++ = (w & 0xff); |
|
|
|
} |
|
|
|
count_read -= 2; |
|
|
|
} |
|
|
|
} |
|
|
|
} while ((sr & (15 << 12)) > queue_full_status_mask); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// now lets wait for all of the read bytes to be returned... |
|
|
|
while (count_read) { |
|
|
|
sr = port().SR; |
|
|
|
if (sr & 0xF0) { |
|
|
|
uint16_t w = port().POPR; // Read any pending RX bytes in |
|
|
|
if (count_read & 1) { |
|
|
|
*p_read++ = w; // Read any pending RX bytes in |
|
|
|
if (p_read) |
|
|
|
*p_read++ = w; // Read any pending RX bytes in |
|
|
|
count_read--; |
|
|
|
} else { |
|
|
|
if (lsbfirst) w = __builtin_bswap16(w); |
|
|
|
*p_read++ = w >> 8; |
|
|
|
*p_read++ = (w & 0xff); |
|
|
|
if (p_read) { |
|
|
|
*p_read++ = w >> 8; |
|
|
|
*p_read++ = (w & 0xff); |
|
|
|
} |
|
|
|
count_read -= 2; |
|
|
|
} |
|
|
|
} |
|
|
|
} while ((sr & (15 << 12)) > full_mask); |
|
|
|
} |
|
|
|
} else { |
|
|
|
// We are doing the less ofen LSB mode |
|
|
|
const uint8_t *p_write = (const uint8_t *)buf; |
|
|
|
uint8_t *p_read = (uint8_t *)retbuf; |
|
|
|
size_t count_read = count; |
|
|
|
|
|
|
|
// Lets clear the reader queue |
|
|
|
port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); |
|
|
|
|
|
|
|
uint32_t sr; |
|
|
|
|
|
|
|
// Now lets loop while we still have data to output |
|
|
|
if (count & 1) { |
|
|
|
if (p_write) { |
|
|
|
if (count > 1) |
|
|
|
port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); |
|
|
|
else |
|
|
|
port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); |
|
|
|
} else { |
|
|
|
if (count > 1) |
|
|
|
port().PUSHR = _transferWriteFill | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); |
|
|
|
else |
|
|
|
port().PUSHR = _transferWriteFill | SPI_PUSHR_CTAS(0); |
|
|
|
} |
|
|
|
count--; |
|
|
|
} |
|
|
|
|
|
|
|
uint16_t w = _transferWriteFill; |
|
|
|
|
|
|
|
while (count > 0) { |
|
|
|
// Push out the next byte; |
|
|
|
if (p_write) { |
|
|
|
w = *p_write++; |
|
|
|
w |= ((*p_write++) << 8); |
|
|
|
} |
|
|
|
uint16_t queue_full_status_mask = (hardware().queue_size-1) << 12; |
|
|
|
if (count == 2) |
|
|
|
port().PUSHR = w | SPI_PUSHR_CTAS(1); |
|
|
|
else |
|
|
|
port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); |
|
|
|
count -= 2; // how many bytes to output. |
|
|
|
// Make sure queue is not full before pushing next byte out |
|
|
|
do { |
|
|
|
sr = port().SR; |
|
|
|
if (sr & 0xF0) { |
|
|
|
uint16_t w = port().POPR; // Read any pending RX bytes in |
|
|
|
if (count_read & 1) { |
|
|
|
if (p_read) { |
|
|
|
*p_read++ = w; // Read any pending RX bytes in |
|
|
|
} |
|
|
|
count_read--; |
|
|
|
} else { |
|
|
|
if (p_read) { |
|
|
|
*p_read++ = (w & 0xff); |
|
|
|
*p_read++ = w >> 8; |
|
|
|
} |
|
|
|
count_read -= 2; |
|
|
|
} |
|
|
|
} |
|
|
|
} while ((sr & (15 << 12)) > queue_full_status_mask); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// now lets wait for all of the read bytes to be returned... |
|
|
|
while (count_read) { |
|
|
|
sr = port().SR; |
|
|
|
if (sr & 0xF0) { |
|
|
|
uint16_t w = port().POPR; // Read any pending RX bytes in |
|
|
|
if (count_read & 1) { |
|
|
|
if (p_read) |
|
|
|
*p_read++ = w; // Read any pending RX bytes in |
|
|
|
count_read--; |
|
|
|
} else { |
|
|
|
if (p_read) { |
|
|
|
*p_read++ = (w & 0xff); |
|
|
|
*p_read++ = w >> 8; |
|
|
|
} |
|
|
|
count_read -= 2; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
//============================================================================= |
|
|
|
// 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; |
|
|
|
} |
|
|
|
|
|
|
|
// now lets wait for all of the read bytes to be returned... |
|
|
|
while (count_read) { |
|
|
|
sr = port().SR; |
|
|
|
if (sr & 0xF0) { |
|
|
|
uint16_t w = port().POPR; // Read any pending RX bytes in |
|
|
|
if (count_read & 1) { |
|
|
|
*p_read++ = w; // Read any pending RX bytes in |
|
|
|
count_read--; |
|
|
|
} else { |
|
|
|
if (lsbfirst) w = __builtin_bswap16(w); |
|
|
|
*p_read++ = w >> 8; |
|
|
|
*p_read++ = (w & 0xff); |
|
|
|
count_read -= 2; |
|
|
|
_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().POPR); |
|
|
|
_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().PUSHR); |
|
|
|
_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 Aync Transfer function |
|
|
|
//========================================================================= |
|
|
|
|
|
|
|
bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { |
|
|
|
uint8_t dma_first_byte; |
|
|
|
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 > hardware().max_dma_count) { |
|
|
|
_dma_count_remaining = count - hardware().max_dma_count; |
|
|
|
count = hardware().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) { |
|
|
|
dma_first_byte = *write_data; |
|
|
|
_dmaTX->sourceBuffer((uint8_t*)write_data+1, count-1); |
|
|
|
_dmaTX->TCD->SLAST = 0; // Finish with it pointing to next location |
|
|
|
} else { |
|
|
|
dma_first_byte = _transferWriteFill; |
|
|
|
_dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value |
|
|
|
DMAChanneltransferCount(_dmaTX, count-1); |
|
|
|
} |
|
|
|
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 |
|
|
|
} 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(); |
|
|
|
port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS(0x1F); |
|
|
|
|
|
|
|
port().SR = 0xFF0F0000; |
|
|
|
|
|
|
|
// Lets try to output the first byte to make sure that we are in 8 bit mode... |
|
|
|
port().PUSHR = dma_first_byte | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; |
|
|
|
|
|
|
|
if (hardware().tx_dma_channel) { |
|
|
|
port().RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; |
|
|
|
_dmaRX->enable(); |
|
|
|
// Get the initial settings. |
|
|
|
_dmaTX->enable(); |
|
|
|
} else { |
|
|
|
//T3.5 SP1 and SPI2 - TX is not triggered by SPI but by RX... |
|
|
|
port().RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS ; |
|
|
|
_dmaTX->triggerAtTransfersOf(*_dmaRX); |
|
|
|
_dmaTX->enable(); |
|
|
|
_dmaRX->enable(); |
|
|
|
} |
|
|
|
|
|
|
|
_dma_state = DMAState::active; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------- |
|
|
|
// DMA RX ISR |
|
|
|
//------------------------------------------------------------------------- |
|
|
|
void SPIClass::dma_rxisr(void) { |
|
|
|
_dmaRX->clearInterrupt(); |
|
|
|
_dmaTX->clearComplete(); |
|
|
|
_dmaRX->clearComplete(); |
|
|
|
|
|
|
|
uint8_t should_reenable_tx = true; // should we re-enable TX maybe not if count will be 0... |
|
|
|
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 > hardware().max_dma_count) { |
|
|
|
_dma_count_remaining -= hardware().max_dma_count; |
|
|
|
} else { |
|
|
|
DMAChanneltransferCount(_dmaTX, _dma_count_remaining-1); |
|
|
|
DMAChanneltransferCount(_dmaRX, _dma_count_remaining); |
|
|
|
if (_dma_count_remaining == 1) should_reenable_tx = false; |
|
|
|
|
|
|
|
_dma_count_remaining = 0; |
|
|
|
} |
|
|
|
// In some cases we need to again start the TX manually to get it to work... |
|
|
|
if (_dmaTX->TCD->SADDR == &_transferWriteFill) { |
|
|
|
if (port().CTAR0 & SPI_CTAR_FMSZ(8)) { |
|
|
|
port().PUSHR = (_transferWriteFill | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); |
|
|
|
} else { |
|
|
|
port().PUSHR = (_transferWriteFill | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); |
|
|
|
} |
|
|
|
} else { |
|
|
|
if (port().CTAR0 & SPI_CTAR_FMSZ(8)) { |
|
|
|
// 16 bit mode |
|
|
|
uint16_t w = *((uint16_t*)_dmaTX->TCD->SADDR); |
|
|
|
_dmaTX->TCD->SADDR = (volatile uint8_t*)(_dmaTX->TCD->SADDR) + 2; |
|
|
|
port().PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); |
|
|
|
} else { |
|
|
|
uint8_t w = *((uint8_t*)_dmaTX->TCD->SADDR); |
|
|
|
_dmaTX->TCD->SADDR = (volatile uint8_t*)(_dmaTX->TCD->SADDR) + 1; |
|
|
|
port().PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); |
|
|
|
} |
|
|
|
} |
|
|
|
_dmaRX->enable(); |
|
|
|
if (should_reenable_tx) |
|
|
|
_dmaTX->enable(); |
|
|
|
} else { |
|
|
|
|
|
|
|
port().RSER = 0; |
|
|
|
//port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); // clear out the queue |
|
|
|
port().SR = 0xFF0F0000; |
|
|
|
port().CTAR0 &= ~(SPI_CTAR_FMSZ(8)); // Hack restore back to 8 bits |
|
|
|
|
|
|
|
_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 |
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************/ |
|
|
@@ -548,7 +897,14 @@ void SPIClass::transfer(void *buf, size_t count) |
|
|
|
|
|
|
|
#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL) |
|
|
|
|
|
|
|
void _spi_dma_rxISR0(void) {/*SPI.dma_rxisr();*/} |
|
|
|
#ifdef SPI_HAS_TRANSFER_ASYNC |
|
|
|
void _spi_dma_rxISR0(void) {SPI.dma_isr();} |
|
|
|
void _spi_dma_rxISR1(void) {SPI1.dma_isr();} |
|
|
|
#else |
|
|
|
void _spi_dma_rxISR0(void) {;} |
|
|
|
void _spi_dma_rxISR1(void) {;} |
|
|
|
#endif |
|
|
|
|
|
|
|
const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { |
|
|
|
SIM_SCGC4, SIM_SCGC4_SPI0, |
|
|
|
0, // BR index 0 |
|
|
@@ -565,7 +921,6 @@ const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { |
|
|
|
}; |
|
|
|
SPIClass SPI((uintptr_t)&KINETISL_SPI0, (uintptr_t)&SPIClass::spi0_hardware); |
|
|
|
|
|
|
|
void _spi_dma_rxISR1(void) {/*SPI1.dma_rxisr();*/} |
|
|
|
const SPIClass::SPI_Hardware_t SPIClass::spi1_hardware = { |
|
|
|
SIM_SCGC4, SIM_SCGC4_SPI1, |
|
|
|
1, // BR index 1 in SPI Settings |
|
|
@@ -752,8 +1107,145 @@ uint8_t SPIClass::setCS(uint8_t pin) |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
void SPIClass::transfer(const void * buf, void * retbuf, size_t count) { |
|
|
|
if (count == 0) return; |
|
|
|
const uint8_t *p = (const uint8_t *)buf; |
|
|
|
uint8_t *pret = (uint8_t *)retbuf; |
|
|
|
uint8_t in; |
|
|
|
|
|
|
|
while (!(port().S & SPI_S_SPTEF)) ; // wait |
|
|
|
uint8_t out = p ? *p++ : _transferWriteFill; |
|
|
|
port().DL = out; |
|
|
|
while (--count > 0) { |
|
|
|
if (p) { |
|
|
|
out = *p++; |
|
|
|
} |
|
|
|
while (!(port().S & SPI_S_SPTEF)) ; // wait |
|
|
|
__disable_irq(); |
|
|
|
port().DL = out; |
|
|
|
while (!(port().S & SPI_S_SPRF)) ; // wait |
|
|
|
in = port().DL; |
|
|
|
__enable_irq(); |
|
|
|
if (pret)*pret++ = in; |
|
|
|
} |
|
|
|
while (!(port().S & SPI_S_SPRF)) ; // wait |
|
|
|
in = port().DL; |
|
|
|
if (pret)*pret = in; |
|
|
|
} |
|
|
|
//============================================================================= |
|
|
|
// ASYNCH Support |
|
|
|
//============================================================================= |
|
|
|
//========================================================================= |
|
|
|
// Try Transfer using DMA. |
|
|
|
//========================================================================= |
|
|
|
#ifdef SPI_HAS_TRANSFER_ASYNC |
|
|
|
static uint8_t _dma_dummy_rx; |
|
|
|
|
|
|
|
void SPIClass::dma_isr(void) { |
|
|
|
// Serial.println("_spi_dma_rxISR"); |
|
|
|
_dmaRX->clearInterrupt(); |
|
|
|
port().C2 = 0; |
|
|
|
uint8_t tmp __attribute__((unused)) = port().S; |
|
|
|
_dmaTX->clearComplete(); |
|
|
|
_dmaRX->clearComplete(); |
|
|
|
|
|
|
|
_dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again |
|
|
|
_dma_event_responder->triggerEvent(); |
|
|
|
} |
|
|
|
|
|
|
|
bool SPIClass::initDMAChannels() { |
|
|
|
//Serial.println("First dma call"); Serial.flush(); |
|
|
|
_dmaTX = new DMAChannel(); |
|
|
|
if (_dmaTX == nullptr) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
_dmaTX->disable(); |
|
|
|
_dmaTX->destination((volatile uint8_t&)port().DL); |
|
|
|
_dmaTX->disableOnCompletion(); |
|
|
|
_dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
_dmaRX = new DMAChannel(); |
|
|
|
if (_dmaRX == NULL) { |
|
|
|
delete _dmaTX; |
|
|
|
_dmaRX = nullptr; |
|
|
|
return false; |
|
|
|
} |
|
|
|
_dmaRX->disable(); |
|
|
|
_dmaRX->source((volatile uint8_t&)port().DL); |
|
|
|
_dmaRX->disableOnCompletion(); |
|
|
|
_dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); |
|
|
|
_dmaRX->attachInterrupt(hardware().dma_isr); |
|
|
|
_dmaRX->interruptAtCompletion(); |
|
|
|
|
|
|
|
_dma_state = DMAState::idle; // Should be first thing set! |
|
|
|
//Serial.println("end First dma call"); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
//========================================================================= |
|
|
|
// Main Aync Transfer function |
|
|
|
//========================================================================= |
|
|
|
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; |
|
|
|
} |
|
|
|
//_dmaTX->destination((volatile uint8_t&)port().DL); |
|
|
|
//_dmaRX->source((volatile uint8_t&)port().DL); |
|
|
|
_dmaTX->CFG->DCR = (_dmaTX->CFG->DCR & ~DMA_DCR_DSIZE(3)) | DMA_DCR_DSIZE(1); |
|
|
|
_dmaRX->CFG->DCR = (_dmaRX->CFG->DCR & ~DMA_DCR_SSIZE(3)) | DMA_DCR_SSIZE(1); // 8 bit transfer |
|
|
|
|
|
|
|
// Now see if the user passed in TX buffer to send. |
|
|
|
uint8_t first_char; |
|
|
|
if (buf) { |
|
|
|
uint8_t *data_out = (uint8_t*)buf; |
|
|
|
first_char = *data_out++; |
|
|
|
_dmaTX->sourceBuffer(data_out, count-1); |
|
|
|
} else { |
|
|
|
first_char = (_transferWriteFill & 0xff); |
|
|
|
_dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value |
|
|
|
_dmaTX->transferCount(count-1); |
|
|
|
} |
|
|
|
|
|
|
|
if (retbuf) { |
|
|
|
_dmaRX->destinationBuffer((uint8_t*)retbuf, count); |
|
|
|
} else { |
|
|
|
_dmaRX->destination(_dma_dummy_rx); // NULL ? |
|
|
|
_dmaRX->transferCount(count); |
|
|
|
} |
|
|
|
|
|
|
|
_dma_event_responder = &event_responder; |
|
|
|
|
|
|
|
//Serial.println("Before DMA C2"); |
|
|
|
// Try pushing the first character |
|
|
|
while (!(port().S & SPI_S_SPTEF)); |
|
|
|
port().DL = first_char; |
|
|
|
|
|
|
|
port().C2 |= SPI_C2_TXDMAE | SPI_C2_RXDMAE; |
|
|
|
|
|
|
|
// Now make sure SPI is enabled. |
|
|
|
port().C1 |= SPI_C1_SPE; |
|
|
|
|
|
|
|
_dmaRX->enable(); |
|
|
|
_dmaTX->enable(); |
|
|
|
_dma_state = DMAState::active; |
|
|
|
return true; |
|
|
|
} |
|
|
|
#endif //SPI_HAS_TRANSFER_ASYNC |
|
|
|
|
|
|
|
#endif |