@@ -235,6 +235,170 @@ void SPIClass::transfer(void *buf, size_t count)
235235 }
236236}
237237
238+ // Pointer to SPIClass object, one per DMA channel.
239+ static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Inits list to NULL
240+
241+ void SPIClass::dmaCallback (Adafruit_ZeroDMA *dma) {
242+ // dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get
243+ // a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object),
244+ // then locate the originating SPIClass object using array lookup, setting
245+ // the dma_busy element 'false' to indicate end of transfer.
246+ spiPtr[dma->getChannel ()]->dma_busy = false ;
247+ }
248+
249+ void SPIClass::transfer (const void * txbuf, void * rxbuf, size_t count,
250+ bool background) {
251+
252+ // If receiving data and the RX DMA channel is not yet allocated...
253+ if (rxbuf && (readChannel.getChannel () >= DMAC_CH_NUM)) {
254+ if (readChannel.allocate () == DMA_STATUS_OK) {
255+ readDescriptor =
256+ readChannel.addDescriptor (
257+ (void *)getDataRegister (), // Source address (SPI data reg)
258+ NULL , // Dest address (set later)
259+ 0 , // Count (set later)
260+ DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words
261+ false , // Don't increment source address
262+ true ); // Increment dest address
263+ readChannel.setTrigger (getDMAC_ID_RX ());
264+ readChannel.setAction (DMA_TRIGGER_ACTON_BEAT);
265+ readChannel.setCallback (dmaCallback);
266+ spiPtr[readChannel.getChannel ()] = this ;
267+ }
268+ }
269+
270+ // Unlike the rxbuf check above, where a RX DMA channel is allocated
271+ // only if receiving data (and channel not previously alloc'd), the
272+ // TX DMA channel is always needed, because even RX-only SPI requires
273+ // writing dummy bytes to the peripheral.
274+ if (writeChannel.getChannel () >= DMAC_CH_NUM) {
275+ if (writeChannel.allocate () == DMA_STATUS_OK) {
276+ writeDescriptor =
277+ writeChannel.addDescriptor (
278+ (void *)NULL , // Source address (set later)
279+ (void *)getDataRegister (), // Dest (SPI data register)
280+ 0 , // Count (set later)
281+ DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words
282+ false , // Don't increment source address
283+ true ); // Increment dest address
284+ writeChannel.setTrigger (getDMAC_ID_TX ());
285+ writeChannel.setAction (DMA_TRIGGER_ACTON_BEAT);
286+ writeChannel.setCallback (dmaCallback);
287+ spiPtr[writeChannel.getChannel ()] = this ;
288+ }
289+ }
290+
291+ if (writeDescriptor) { // If this allocated, then use DMA
292+ static uint8_t dum = 0xFF ; // Dummy byte for read-only transfers
293+
294+ // Initialize read descriptor dest address to rxbuf (even if unused)
295+ readDescriptor->DSTADDR .reg = (uint32_t )rxbuf;
296+
297+ // If reading only, set up writeDescriptor to issue dummy bytes
298+ // (set SRCADDR to &dum and SRCINC to 0). Otherwise, set SRCADDR
299+ // to txbuf and SRCINC to 1. Only needed once at start.
300+ if (rxbuf && !txbuf) {
301+ writeDescriptor->SRCADDR .reg = (uint32_t )&dum;
302+ writeDescriptor->BTCTRL .bit .SRCINC = 0 ;
303+ } else {
304+ writeDescriptor->SRCADDR .reg = (uint32_t )txbuf;
305+ writeDescriptor->BTCTRL .bit .SRCINC = 1 ;
306+ }
307+
308+ while (count > 0 ) {
309+ // Maximum bytes per DMA descriptor is 65,535 (NOT 65,536).
310+ // We could set up a descriptor chain, but that gets more
311+ // complex. For now, instead, break up long transfers into
312+ // chunks of 65,535 bytes max...these transfers are all
313+ // blocking, regardless of the "background" argument, except
314+ // for the last one which will observe the background request.
315+ // The fractional part is done first, so for any "partially
316+ // backgrounded" transfers like these at least it's the
317+ // largest single-descriptor transfer possible that occurs
318+ // in the background, rather than the tail end.
319+ int bytesThisPass;
320+ bool block;
321+ if (count > 65535 ) { // Too big for 1 descriptor
322+ block = true ;
323+ bytesThisPass = count % 65535 ; // Fractional part
324+ if (!bytesThisPass) bytesThisPass = 65535 ;
325+ } else {
326+ block = !background;
327+ bytesThisPass = count;
328+ }
329+
330+ // Issue 'bytesThisPass' bytes...
331+ dma_busy = true ;
332+ if (rxbuf) {
333+ // Reading, or reading + writing.
334+ // Set up read descriptor for reading.
335+ // Src address doesn't change, only dest & count.
336+ // DMA needs address set to END of buffer, so
337+ // increment the address now, before the transfer.
338+ readDescriptor->DSTADDR .reg += bytesThisPass;
339+ readDescriptor->BTCNT .reg = bytesThisPass;
340+ if (txbuf) {
341+ // Writing and reading simultaneously.
342+ // Set up write descriptor for writing real data.
343+ // Src address and count both change.
344+ // DMA needs address set to END of buffer, so
345+ // increment the address now, before the transfer.
346+ writeDescriptor->SRCADDR .reg += bytesThisPass;
347+ writeDescriptor->BTCNT .reg = bytesThisPass;
348+ } else {
349+ // Reading only.
350+ // Write descriptor was already set up for dummy
351+ // writes outside loop, only BTCNT needs set.
352+ writeDescriptor->SRCADDR .reg = (uint32_t )&dum;
353+ writeDescriptor->BTCNT .reg = bytesThisPass;
354+ }
355+ // Start the RX job BEFORE the TX job!
356+ // That's the whole secret sauce to the two-channel transfer.
357+ readChannel.startJob ();
358+ } else if (txbuf) {
359+ // Writing only.
360+ // Set up write descriptor for writing real data.
361+ // DMA needs address set to END of buffer, so
362+ // increment the address now, before the transfer.
363+ writeDescriptor->SRCADDR .reg += bytesThisPass;
364+ writeDescriptor->BTCNT .reg = bytesThisPass;
365+ }
366+ writeChannel.startJob ();
367+ count -= bytesThisPass;
368+ if (block) {
369+ while (dma_busy);
370+ }
371+ }
372+ } else {
373+ // Non-DMA fallback.
374+ uint8_t *txbuf8 = (uint8_t *)txbuf,
375+ *rxbuf8 = (uint8_t *)rxbuf;
376+ if (rxbuf8) {
377+ if (txbuf8) {
378+ // Writing and reading simultaneously
379+ while (count--) {
380+ *rxbuf8++ = _p_sercom->transferDataSPI (*txbuf8++);
381+ }
382+ } else {
383+ // Reading only
384+ while (count--) {
385+ *rxbuf8++ = _p_sercom->transferDataSPI (0xFF );
386+ }
387+ }
388+ } else if (txbuf) {
389+ // Writing only
390+ while (count--) {
391+ (void )_p_sercom->transferDataSPI (*txbuf8++);
392+ }
393+ }
394+ }
395+ }
396+
397+ // Waits for a prior in-background DMA transfer to complete.
398+ void SPIClass::waitForTransfer (void ) {
399+ while (dma_busy);
400+ }
401+
238402void SPIClass::attachInterrupt () {
239403 // Should be enableInterrupt()
240404}
0 commit comments