Time for a rather lengthy post…
After doing some digging it looks like enabling true DMA for DPI may only be possible for DSPI 1. First things first, though. It looks like the sim5441x.h definitions for the memory map are different than the user manual specifies. I rebuilt system files after changing them to:
Code: Select all
/*
* EDMA TRANSFER CONTROL DESCRIPTOR (tcd[16] = 0xFC04_5000 -> 0xFC04_51FF)
*/
typedef struct {
vudword saddr; /* 0x0000 -> 0x0003 - Source Address */
vuword attr; /* 0x0004 -> 0x0005 - Transfer Attributes */
vuword soff; /* 0x0006 -> 0x0007 - Signed Source Address Offset */
vudword nbytes; /* 0x0008 -> 0x000B - Signed Minor Loop Offset/Minor Byte Count */
vudword slast; /* 0x000C -> 0x000F - Last Source Address Adjustment */
vudword daddr; /* 0x0010 -> 0x0013 - Destination Address */
vuword citer; /* 0x0014 -> 0x0015 - Current Minor Loop Link/Major Loop Count */
vuword doff; /* 0x0016 -> 0x0017 - Signed Destination Address Offset */
vudword dlast_sga; /* 0x0018 -> 0x001B - Last Destination Addr. Adjustment/Scatter Gather Addr. */
vuword biter; /* 0x001C -> 0x001D - Beginning Minor Loop Link/Major Loop Count */
vuword csr; /* 0x001E -> 0x001F - Control and Status */
} edma_tcdstruct;
/*
* ENHANCED DIRECT MEMORY ACCESS (EDMA) CONTROLLER
*/
typedef struct {
vudword cr; /* 0xFC04_4000 -> 0xFC04_4003 - eDMA Control Register */
vudword es; /* 0xFC04_4004 -> 0xFC04_4007 - eDMA Error Status Register */
vubyte pack00[6]; /* 0xFC04_4008 -> 0xFC04_400D - RESERVED */
vuword erq; /* 0xFC04_400E -> 0xFC04_400F - eDMA Enable Request Register */
vubyte pack01[6]; /* 0xFC04_4010 -> 0xFC04_4015 - RESERVED */
vuword eei; /* 0xFC04_4016 -> 0xFC04_4017 - eDMA Enable Error Interrupt Register */
vubyte serq; /* 0xFC04_4018 -> 0xFC04_4018 - eDMA Set Enable Request */
vubyte cerq; /* 0xFC04_4019 -> 0xFC04_4019 - eDMA Clear Enable Request */
vubyte seei; /* 0xFC04_401A -> 0xFC04_401A - eDMA Set Enable Error Interrupt Register */
vubyte ceei; /* 0xFC04_401B -> 0xFC04_401B - eDMA Clear Enable Error Interrupt Register */
vubyte cint; /* 0xFC04_401C -> 0xFC04_401C - eDMA Clear Interrupt Request Register */
vubyte cerr; /* 0xFC04_401D -> 0xFC04_401D - eDMA Clear Error Register */
vubyte ssrt; /* 0xFC04_401E -> 0xFC04_401E - eDMA Set START Bit Register */
vubyte cdne; /* 0xFC04_401F -> 0xFC04_401F - eDMA Clear DONE Status Bit Register */
vubyte pack02[6]; /* 0xFC04_4020 -> 0xFC04_4025 - RESERVED */
vudword intr; /* 0xFC04_4026 -> 0xFC04_4029 - eDMA Interrupt Request Register */
vubyte pack03[4]; /* 0xFC04_402A -> 0xFC04_402D - RESERVED */
vudword err; /* 0xFC04_402E -> 0xFC04_4031 - eDMA Error Register */
vubyte pack04[206]; /* 0xFC04_4032 -> 0xFC04_40FF - RESERVED */
vubyte dchpri[16]; /* 0xFC04_4100 -> 0xFC04_410F - eDMA Channel 0-15 Priority Registers */
vubyte pack05[3824]; /* 0xFC04_4110 -> 0xFC04_4FFF - RESERVED */
edma_tcdstruct tcd[16]; /* 0xFC04_5000 -> 0xFC04_51FF - Transfer Control Descriptor 0-15 */
} edmastruct;
When looking at the user manual I also noticed that there are only 16 DMA channels but there are 64 defined in table 19-6. I assume these are leftover from some earlier manual as I don't see any register configuration for these extra DMA channels and all channel configuration registers are 16 bits wide (one bit per channel). Assuming the table is still accurate for channels 0-15, it looks like only DSPI 0 and DSPI 1 are assigned DMA channels and since only DSPI 1 is brought out to the development board, it looks to me like that is the only channel truly capable of DMA SPI.
Continuing with the assumption that table 19-6 is accurate for DMA channels 0-15, I proceeded with some configuration code for enabling DMA for DSPI, passing in DSPI channel 1 (note that I’m transferring 16bits of data at a time and anything that follows assumes the same):
Code: Select all
#define DSPI0_BASE 0xFC05C000 // base address for DSPI 0
#define DSPI1_BASE 0xFC03C000 // base address for DSPI 1
#define DSPI2_BASE 0xEC038000 // base address for DSPI 2
#define DSPI3_BASE 0xEC03C000 // base address for DSPI 3
#define PUSH_REG_OFFSET 0x34 // address offset of the push transmit FIFO register
#define POP_REG_OFFSET 0x38 // address offset of the pop receive FIFO register
/*------------------------------------------------------------------
* Initialize DMA transfers for a DSPI channel
*-----------------------------------------------------------------*/
void init_DSPI_DMA(void* txbuf, void* rxbuf, int dspi_chan)
{
int dspi_tx_chan;
int dspi_rx_chan;
int i, push_reg, pop_reg;
// Get the correct DMA channels and push/pop addresses for selected DSPI channel
switch (dspi_chan)
{
case 0:
push_reg = DSPI0_BASE + PUSH_REG_OFFSET;
pop_reg = DSPI0_BASE + POP_REG_OFFSET;
dspi_tx_chan = 13;
dspi_rx_chan = 12;
break;
case 2:
push_reg = DSPI2_BASE + PUSH_REG_OFFSET;
pop_reg = DSPI2_BASE + POP_REG_OFFSET;
dspi_tx_chan = 29;
dspi_rx_chan = 28;
break;
case 3:
push_reg = DSPI3_BASE + PUSH_REG_OFFSET;
pop_reg = DSPI3_BASE + POP_REG_OFFSET;
dspi_tx_chan = 45;
dspi_rx_chan = 44;
break;
default: // case 1 and any other bogus case
push_reg = DSPI1_BASE + PUSH_REG_OFFSET;
pop_reg = DSPI1_BASE + POP_REG_OFFSET;
dspi_tx_chan = 15;
dspi_rx_chan = 14;
}
// Configure fixed priorities and disable debug mode
sim2.edma.cr = 0;
// Clear enable request register before initializing tcd
// Otherwise errors could be triggered before configuration is complete
sim2.edma.erq = 0;
// Clear error interrupt enable register
sim2.edma.eei = 0;
// Set the priorities of the channels, higher channels have lower priorities
for (i=0; i<16; i++)
sim2.edma.dchpri[i] = 15-i;
// Transfer Control Descriptor for DSPI TFFF (transmit FIFO fill flag)
sim2.edma.tcd[dspi_tx_chan].saddr = (int)txbuf; // 32b - transmit buffer address
sim2.edma.tcd[dspi_tx_chan].attr = 0x0202; // 16b - addresses modulo = 0, 32 bit transfers
sim2.edma.tcd[dspi_tx_chan].soff = 4; // 16b - offset to next transfer address
sim2.edma.tcd[dspi_tx_chan].nbytes = 4; // 32b - bytes to transfer per service request
sim2.edma.tcd[dspi_tx_chan].slast = 0; // 32b - no source offset for last transfer
sim2.edma.tcd[dspi_tx_chan].daddr = push_reg; // 32b - DSPI push transmit FIFO register
sim2.edma.tcd[dspi_tx_chan].citer = 1; // 16b - current iteration (disabled)
sim2.edma.tcd[dspi_tx_chan].doff = 0; // 16b - writing to same register each time, so no offset
sim2.edma.tcd[dspi_tx_chan].dlast_sga = 0; // 32b - no destination offset for last transfer
sim2.edma.tcd[dspi_tx_chan].biter = 1; // 16b - beginning major iteration count
sim2.edma.tcd[dspi_tx_chan].csr = 0; // 16b - no stalls, disable random junk
// Transfer Control Descriptor for DSPI RFDF (receive FIFO drain flag)
sim2.edma.tcd[dspi_rx_chan].saddr = pop_reg; // 32b - transmit buffer address
sim2.edma.tcd[dspi_rx_chan].attr = 0x0202; // 16b - addresses modulo = 0, 32 bit transfers
sim2.edma.tcd[dspi_rx_chan].soff = 0; // 16b - reading same register each time, so no offset
sim2.edma.tcd[dspi_rx_chan].nbytes = 4; // 32b - bytes to transfer per service request
sim2.edma.tcd[dspi_rx_chan].slast = 0; // 32b - no source offset for last transfer
sim2.edma.tcd[dspi_rx_chan].daddr = (int)rxbuf; // 32b - DSPI push transmit FIFO register
sim2.edma.tcd[dspi_rx_chan].citer = 1; // 16b - current iteration (disabled)
sim2.edma.tcd[dspi_rx_chan].doff = 4; // 16b - offset to next transfer address
sim2.edma.tcd[dspi_rx_chan].dlast_sga = 0; // 32b - no destination offset for last transfer
sim2.edma.tcd[dspi_rx_chan].biter = 1; // 16b - beginning major iteration count
sim2.edma.tcd[dspi_rx_chan].csr = 0; // 16b - no stalls, disable random junk
// Enable interrupts on channels dspi_tx_chan and dspi_rx_chan
sim2.edma.erq = (1 << dspi_tx_chan) | (1 << dspi_rx_chan);
//iprintf("ERQ:%04x\n",sim2.edma.erq);
//iprintf("ES:%08x\n",sim2.edma.es);
}
Now when DMA transfers are triggered, data should be moved four bytes at a time into the push register, which loads the transmit FIFO. One consideration is that the FIFO expects a word of command information in each dword, so the command data must be loaded into the buffer ahead of time. Since the intention is to use EOQ as the interrupt when the transfer is complete, that bit must be set for the final word transfer. The following code essentially puts the lowercase alphabet (repeating) into your buffer and adds command words where required:
Code: Select all
// For DMA, there are 2 bytes command data and 2 bytes data data
// Interleave the data
for (i=0;i<sizeof(TXBuffer_1);i+=4)
{
TXBuffer_1[i+0] = (i/2)%26+97; // start at 'a'
TXBuffer_1[i+1] = (i/2)%26+98; // start at 'b'
TXBuffer_1[i+2] = 0;
TXBuffer_1[i+3] = 0x80; // keep CS asserted between transfer, no EOQ
}
TXBuffer_1[i-1] = 0x08; // de-assert CS after final transfer, EOQ
Next we have to configure the DSPI to actually use DMA, which is done with the RSER register. In the DSPIStart routine (in dspi.cpp) I changed the RSER configuration to allow DMA requests for transmit and receive and still trigger an interrupt at the end of queue. Since the code above sets EOQ for the last frame, this will theoretically fire an interrupt after the last word of data is transferred. I just defined a new value and used that instead of the non-DMA version. Note that I also clear the EOQF bit here (before calling the ISR for the first time):
Code: Select all
#define RSER_EOQF_WITH_DMA 0x13030000
//spi->rser = RSER_EOQF_IRQ_ONLY;
spi->rser = RSER_EOQF_WITH_DMA;
spi->sr |= SR_EOQF_MASK;
The final step is to modify the interrupt service routine (again, in dspi.cpp) to handle the start/stop of transfers correctly. This is called the first time by DSPIStart, which simply kicks off the transfer and sets a couple status variables. It should be called again when the transfer is complete, at which point we set some bits and status variables, and post the semaphore to signal that we’re done.
Code: Select all
/*-----------------------------------------------------------------------
DSPI interrupt service routine.
Called at kickoff and by hardware when DSPI transfer complete
------------------------------------------------------------------------*/
void DSPI_Isr_new( unsigned int moduleNum, volatile dspistruct *spiModule )
{
register volatile dspistruct &spi = *spiModule;
//iprintf("DSPI ISR - SR: %08x, RSER: %08X\n",spi.sr,spi.rser);
if(spi.sr & SR_EOQF_MASK) // transfer complete
{
// do not clear the EOQ flag until *AFTER* setting the halt bit
// see MCF54418 manual section 40.4.1: Start and stop of DSPI transfers
spi.mcr |= MCR_HALT_BIT;
spi.sr &= SR_CLR_FLAGS;
driverCxt[moduleNum].DSPI_INT_STATUS = DSPI_OK;
driverCxt[moduleNum].DSPIfinished = TRUE;
if(driverCxt[moduleNum].DSPI_Sem)
OSSemPost( driverCxt[moduleNum].DSPI_Sem );
}
else // start transfer
{
spi.mcr &= ~MCR_HALT_BIT;
driverCxt[moduleNum].DSPI_INT_STATUS = DSPI_BUSY;
driverCxt[moduleNum].DSPIfinished = FALSE;
}
return;
}
I rebuilt system files again to make sure the new ISR and start code is utilized. I added a call to my init_DSPI_DMA() function in my main before starting a DSPI transfer in the normal way, ie:
Code: Select all
DSPIStart(1,(BYTE*)TXBuffer_1,(BYTE*)RXBuffer_1,NUM_BYTES,&DSPI_SEM_1);
then wait for the transfer to finish with a small timeout:
Of course the semaphore always times out and I’m not able to see any data make it to the receive buffer so something is wrong. I think I have all the pieces in place but it appears I’m either misconfiguring or forgetting something. Anybody have thoughts?