@@ -235,6 +235,170 @@ void SPIClass::transfer(void *buf, size_t count)
235
235
}
236
236
}
237
237
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
+
238
402
void SPIClass::attachInterrupt () {
239
403
// Should be enableInterrupt()
240
404
}
0 commit comments