Mod5441x and Nano54415 SPI driver available

Discussion to talk about software related topics only.
jch
Posts: 8
Joined: Mon Jan 07, 2013 8:12 am

Re: Mod5441x and Nano54415 SPI driver available

Post by jch »

Do you have new code that takes advantage of DMA? I just started looking into how to get this started as the current non-DMA throughput isn't high enough for our application.

I'm assuming the setup is going to be something like page 9 here: http://cache.freescale.com/files/32bit/ ... AN2867.pdf even though it's a different version of the processor. It looks like that should more or less replace what's happening in your ISR currently, mainly the manual loading of the TX FIFO and emptying of the RX FIFO.
jch
Posts: 8
Joined: Mon Jan 07, 2013 8:12 am

Re: Mod5441x and Nano54415 SPI driver available

Post by jch »

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:

Code: Select all

OSSemPend(&DSPI_SEM_1, 2);
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?
User avatar
dciliske
Posts: 624
Joined: Mon Feb 06, 2012 9:37 am
Location: San Diego, CA
Contact:

Re: Mod5441x and Nano54415 SPI driver available

Post by dciliske »

Jch, first off, you've done quite a lot of digging in there (It's fairly deep...) so congrats. I'm currently trying to get the DMA driven driver integrated into our MMC driver for accessing the SD card. This is also a pretty good test of the driver. I would have posted this portion of the driver sooner, except... I'm having issues with it.

I'll say this, if you're using the MCF54415RM reference manual from the install, or from our website, you're not going to get the DMA working, period. My apologies there. The reason is that Freescale has pretty seriously dropped the ball and that entire chapter is wrong in Rev. 4 of the manual. There are in fact 64 channels on the processor. There are also registers with bits not listed in Rev 4, that if you change will prevent the DMAC from running. You'll want to use Rev. 3 of the reference manual (Download Here; Freescale Forum Thread).

As for actually utilizing the DMA, I've setup the driver to only read/write 8 to 16 bits to the popr/pushr registers. This means we'll only be using CTAR[0] for our framesize/baudrate/etc. It also means you can't set any chip selects active. The workaround is to use the MCR register to invert the inactive state while actually doing the transaction. Finally, it also means you can't set the EOQ bit. In order to actually be informed when the transfer is complete, the driver uses the DMA ISR to tickle the wakeup semaphore after the RX channel finishes draining the RX FIFO. Oh, and one last issue is that I've disabled the TX FIFO to prevent the RX FIFO from overflowing at HIGH (aka, max) baud.

I've attached the current status of the driver; I apologize if it feels weirdly laid out/lack of comments, but I'm kind of tearing it apart at the moment to find my issue :)
Attachments
DSPI_DMA_WORK_IN_PROGRESS.zip
(32.41 KiB) Downloaded 305 times
Dan Ciliske
Project Engineer
Netburner, Inc
jch
Posts: 8
Joined: Mon Jan 07, 2013 8:12 am

Re: Mod5441x and Nano54415 SPI driver available

Post by jch »

The manual being wrong certainly explains a lot, and is pretty much what I was expecting given its DMA channel description inconsistencies. Thanks a bunch for the links and I'll dig in to your new code ASAP.
Post Reply