iLLD_TC22x  1.0
How to use the DMA Interface driver?
Collaboration diagram for How to use the DMA Interface driver?:

The DMA interface driver provides a default configuration for data moves without intervention of the CPU or other on chip devices.

In the following sections it will be described, how to integrate the driver into the application framework for different use cases.

Preparation

Include Files

Include following header file into your C code:

Module initialisation

Declare the dma handle as a global variable:

// DMA handle

Initialize the DMA with following code:

// create module config
IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);
// initialize module
// IfxDma_Dma dma; // declared globally
IfxDma_Dma_initModule(&dma, &dmaConfig);

This only has to be done once in the application.

The "IfxDma_Dma dma" handle should either be declared as a global variable (as shown in this example), or it can be created locally if desired:

IfxDma_Dma_createModuleHandle(&dma, &MODULE_DMA);

Memory-to-Memory Transfers

A large amount of data should be copied between SRI based memories, e.g. from Flash into the DSPR of the current CPU. It's recommended to use 256 bit moves for this purpose for best performance.

This requires, that source and target locations are 256 bit (32 byte) aligned. With the GCC compiler this can be achieved by adding attribute ((aligned(64))) to the arrays:

#define MEMORY_TRANSFER_NUM_WORDS 1024
uint32 __attribute__ ((aligned(64))) memoryDestination[MEMORY_TRANSFER_NUM_WORDS];

Channel configuration and handling for the data move:

// construct the channel configuration
// select DMA channel which should be used
// source and destination address
chnCfg.sourceAddress = (uint32)0x80000000; // somewhere in flash section, here: start of PFlash (only for demo)
chnCfg.destinationAddress = (uint32)memoryDestination;
// move size, transfer count and request/operation mode
chnCfg.transferCount = (4 * MEMORY_TRANSFER_NUM_WORDS) / 32; // e.g. 1024 words require 128 * 256 bit moves
// transfer configuration into DMA channel registers
IfxDma_Dma_initChannel(&chn, &chnCfg);
// start transfer and wait until it's finished

Peripheral-to-Memory Transfers

The content of 8 ADC result registers should be transfered to a memory location in DSPR whenever an VADC autoscan has been finished. After the DMA transaction, an interrupt should be triggered so that the CPU can process the conversion results.

We use following global variables:

// buffer for autoscanned conversion result values
#define NUM_SCANNED_CHANNELS 8
static uint16 vadcResultBuffer[NUM_SCANNED_CHANNELS];
// VADC handle
// VADC group handle
static IfxVadc_Adc_Group adcGroup;
// DMA channel handle
static IfxDma_Dma_Channel dmaChn;

Create an interrupt handler for the DMA channel request:

// priorities are normally defined in Ifx_IntPrioDef.h
#define IFX_INTPRIO_DMA_CH0 1
IFX_INTERRUPT(dmaCh0ISR, 0, IFX_INTPRIO_DMA_CH0)
{
// ...
// do something with the conversion results in vadcResultBuffer[]
// ...
// re-init DMA channel destination address
IfxDma_Dma_setChannelDestinationAddress(&dmaChn, ADDR_CPU_DSPR(IfxCpu_getCoreId(), &vadcResultBuffer[0]));
// start new transaction
IfxDma_Dma_setChannelTransferCount(&dmaChn, NUM_SCANNED_CHANNELS);
{
uint32 channels = 0xff; // all 8 channels
uint32 mask = 0xff; // modify the selection of all channels
// configure autoscan (single shot, not continuous scan)
IfxVadc_Adc_setScan(&adcGroup, channels, mask);
}
}

ADC configuration:

// create configuration
IfxVadc_Adc_initModuleConfig(&adcConfig, &MODULE_VADC);
// initialize module
// IfxVadc_Adc vadc; // declared globally
IfxVadc_Adc_initModule(&vadc, &adcConfig);
// create group config
IfxVadc_Adc_GroupConfig adcGroupConfig;
IfxVadc_Adc_initGroupConfig(&adcGroupConfig, &vadc);
// initialize the group
//IfxVadc_Adc_Group adcGroup; // defined globally
adcGroupConfig.groupId = IfxVadc_GroupId_0;
adcGroupConfig.master = adcGroupConfig.groupId;
// enable all arbiter request sources
adcGroupConfig.arbiter.requestSlotQueueEnabled = TRUE; // enable Queue mode
adcGroupConfig.arbiter.requestSlotScanEnabled = TRUE; // enable Scan mode
adcGroupConfig.arbiter.requestSlotBackgroundScanEnabled = TRUE; // enable Background scan
// enable all gates in "always" mode (no edge detection)
IfxVadc_Adc_initGroup(&adcGroup, &adcGroupConfig);
{
// create channel config
IfxVadc_Adc_ChannelConfig adcChannelConfig;
IfxVadc_Adc_initChannelConfig(&adcChannelConfig, &adcGroup);
// initialize the channels
for(int i=0; i<NUM_SCANNED_CHANNELS; ++i) {
adcChannelConfig.channelId = (IfxVadc_ChannelId)i;
// initialize the channel
IfxVadc_Adc_Channel adcChannel;
IfxVadc_Adc_initChannel(&adcChannel, &adcChannelConfig);
}
}
adcGroup.group->RCR[0].B.SRGEN = 1; // interrupt when new result is available
// send service request to DMA Channel 0
IfxSrc_init((Ifx_SRC_SRCR*)&MODULE_SRC.VADC.G[0], IfxSrc_Tos_dma, 0);
IfxSrc_enable((Ifx_SRC_SRCR*)&MODULE_SRC.VADC.G[0]);

And finally the DMA channel configuration

// create module config
IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);
// initialize module
IfxDma_Dma_initModule(&dma, &dmaConfig);
{
// construct the channel configuration
// select DMA channel which should be used
chnCfg.hardwareRequestEnabled = TRUE; // will be triggered from VADC service request
// interrupt configuration
chnCfg.channelInterruptEnabled = TRUE; // service request from DMA after all words have been transfered
chnCfg.channelInterruptPriority = IFX_INTPRIO_DMA_CH0;
// source and destination address
chnCfg.sourceAddress = (uint32)&adcGroup.group->RES[0]; // first result register
chnCfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_1; // keep this address
chnCfg.destinationAddress = IFXCPU_GLB_ADDR_DSPR(IfxCpu_getCoreId(), &vadcResultBuffer[0]); // move into result buffer
chnCfg.destinationAddressIncrementStep = IfxDma_ChannelIncrementStep_1; // increment once (=2 bytes) with each write
// move size, transfer count and request/operation mode
chnCfg.transferCount = NUM_SCANNED_CHANNELS; // for the scanned channels
chnCfg.operationMode = IfxDma_ChannelOperationMode_continuous; // hw request enable remains set after transaction
// transfer configuration into DMA channel registers
// IfxDma_Dma_Channel dmaChn; // declared globally
IfxDma_Dma_initChannel(&dmaChn, &chnCfg);
// configure IRQ handler which will be called after all result registers have been transfered
IfxCpu_Irq_installInterruptHandler(&dmaCh0ISR, IFX_INTPRIO_DMA_CH0);
// enable CPU interrupts
}

In order to start the initial channel conversions, use:

{
uint32 channels = 0xff; // all 8 channels
uint32 mask = 0xff; // modify the selection of all channels
// configure and start autoscan (single shot, not continuous mode)
IfxVadc_Adc_setScan(&adcGroup, channels, mask);
}

DMA will transfer the results to DSPR during the autoscan (whenever a new result is availale), and invoke the dmaCh0ISR function once all channels have been converted.

The ISR will re-configure the DMA channel and re-start the autoscan.

Linked Lists

Linked lists allow to initiate multiple DMA transactions from independent transaction sets which are typically stored in a DSPR memory location, and fetched and executed from the DMA channel without further CPU interaction.

Following example demonstrates, how 5 different transactions can be initiated from a single request. We copy the data of 5 CAN message objects to a DSPR location.

Includes and global variables:

#include <IfxCan_reg.h>
// DMA channel handle
// Linked List storage
// IMPORTANT: it has to be aligned to an 64bit address, otherwise DMA can't read it
#define NUM_LINKED_LIST_ITEMS 5
__attribute__ ((aligned(64))) Ifx_DMA_CH linkedList[NUM_LINKED_LIST_ITEMS] ;
// transfer these values to various CAN_MODATA[LH] registers via linked lists
#define NUM_TRANSFERED_WORDS 2
uint32 sourceBuffer[NUM_LINKED_LIST_ITEMS][NUM_TRANSFERED_WORDS];
const uint32 destinationAddresses[NUM_LINKED_LIST_ITEMS] = {
(uint32)&CAN_MODATAL0,
(uint32)&CAN_MODATAL1,
(uint32)&CAN_MODATAL2,
(uint32)&CAN_MODATAL3,
(uint32)&CAN_MODATAL4,
};

Following code to prepare CAN for this demo:

// enable CAN (no Ifx LLD available yet)
{
CAN_CLC.U = 0x0100;
if( CAN_CLC.U ); // synch access
// select f_clc as kernel clock
CAN_MCR.B.CLKSEL = 1;
// configure fractional divider
CAN_FDR.U = 0x43ff;
// wait until RAM has been initialized
while( CAN_PANCTR.B.BUSY );
}

Build a linked list

// create module config
IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);
// initialize module
IfxDma_Dma_initModule(&dma, &dmaConfig);
// initial channel configuration
// following settings are used by all transactions
cfg.transferCount = NUM_TRANSFERED_WORDS;
// generate linked list items
for(int i=0; i<NUM_LINKED_LIST_ITEMS; ++i) {
cfg.destinationAddress = destinationAddresses[i];
// address to next transaction set
cfg.shadowAddress = IFXCPU_GLB_ADDR_DSPR(IfxCpu_getCoreId(), (uint32)&linkedList[(i + 1) % NUM_LINKED_LIST_ITEMS]);
// transfer first transaction set into DMA channel
if( i == 0 ) {
}
// transfer into linked list storage
IfxDma_Dma_initLinkedListEntry((void *)&linkedList[i], &cfg);
if( i == 0 ) {
// - trigger channel interrupt once the first transaction set has been loaded (again) into DMA channel
linkedList[i].CHCSR.B.SIT = 1;
} else {
// - activate SCH (transaction request) for each entry, expect for the first one (linked list terminated here)
linkedList[i].CHCSR.B.SCH = 1;
}
}

The transfer can be started via software with:

// clear service request flag
(IfxDma_Dma_getSrcPointer(&chn))->B.CLRR = 1;
// start linked list transaction
// wait for service request which is triggered at the end of linked list transfers
while( !(IfxDma_Dma_getSrcPointer(&chn))->B.SRR );

In order to synchronize with the end of linked list operations, it's recommended to poll the service request flag (triggered via linkedList[NUM_LINKED_LIST_ITEMS-1].CHCSR.B.SIT after the last word has been transfered), and not the transaction count as shown before, because a linked list will initiate multiple transactions.