blob: 35ed9cdf89910ac8848b88ef2280bb65a8076b6b [file] [log] [blame]
/***************************************************************************//**
* @file em_dma.c
* @brief Direct memory access (DMA) module peripheral API
* @version 5.6.0
*******************************************************************************
* # License
* <b>Copyright 2016 Silicon Laboratories, Inc. www.silabs.com</b>
*******************************************************************************
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
* obligation to support this Software. Silicon Labs is providing the
* Software "AS IS", with no express or implied warranties of any kind,
* including, but not limited to, any implied warranties of merchantability
* or fitness for any particular purpose or warranties against infringement
* of any proprietary rights of a third party.
*
* Silicon Labs will not be liable for any consequential, incidental, or
* special damages, or any other relief, or for any claim by any third party,
* arising from your use of this Software.
*
******************************************************************************/
#include "em_dma.h"
#if defined(DMA_PRESENT)
#include "em_cmu.h"
#include "em_assert.h"
#include "em_bus.h"
/***************************************************************************//**
* @addtogroup emlib
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup DMA
* @brief Direct Memory Access (DMA) Peripheral API
* @details
* DMA access functions provide basic support for the following
* types of DMA cycles:
*
* @li @b Basic, used for transferring data between memory and peripherals.
* @li @b Auto-request, used for transferring data between memory locations.
* @li @b Ping-pong, used for for continuous transfer of data between memory
* and peripherals, automatically toggling between primary and alternate
* descriptors.
* @li @b Memory @b scatter-gather, used for transferring a number of buffers
* between memory locations.
* @li @b Peripheral @b scatter-gather, used for transferring a number of
* buffers between memory and peripherals.
*
* A basic understanding of the DMA controller is assumed. See
* the reference manual for more details.
*
* The term 'descriptor' is synonymous to the 'channel control data
* structure' term.
*
* To use the DMA controller, the initialization function must have
* been executed once (normally during the system initialization):
* @verbatim
* DMA_Init();
* @endverbatim
*
* Normally, a DMA channel is configured:
* @verbatim
* DMA_CfgChannel();
* @endverbatim
*
* The channel configuration only has to be done once if reusing the channel
* for the same purpose later.
*
* To set up a DMA cycle, the primary and/or alternate descriptor
* has to be set up as indicated below.
*
* For basic or auto-request cycles, use once on either primary or alternate
* descriptor:
* @verbatim
* DMA_CfgDescr();
* @endverbatim
*
* For ping-pong cycles, configure both primary or alternate descriptors:
* @verbatim
* DMA_CfgDescr(); // Primary descriptor config
* DMA_CfgDescr(); // Alternate descriptor config
* @endverbatim
*
* For scatter-gather cycles, program the alternate descriptor array:
* @verbatim
* // 'n' is the number of scattered buffers
* // 'descr' points to the start of the alternate descriptor array
*
* // Fill in 'cfg'
* DMA_CfgDescrScatterGather(descr, 0, cfg);
* // Fill in 'cfg'
* DMA_CfgDescrScatterGather(descr, 1, cfg);
* :
* // Fill in 'cfg'
* DMA_CfgDescrScatterGather(descr, n - 1, cfg);
* @endverbatim
*
* In many cases, the descriptor configuration only has to be done once if
* re-using the channel for the same type of DMA cycles later.
*
* To activate the DMA cycle, use the respective DMA_Activate...()
* function.
*
* For ping-pong DMA cycles, use DMA_RefreshPingPong() from the callback to
* prepare the completed descriptor for reuse. Notice that the refresh must
* be done prior to the other active descriptor completes, otherwise the
* ping-pong DMA cycle will halt.
* @{
******************************************************************************/
/*******************************************************************************
************************** LOCAL FUNCTIONS ********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief
* Prepare the descriptor for the DMA cycle.
*
* @details
* This function prepares the last parts of the configuration required to start a
* DMA cycle. Since the DMA controller itself modifies some parts of the
* descriptor during use, those parts need to be refreshed if reusing a
* descriptor configuration.
*
* @note
* If using this function on a descriptor already activated and in use by the
* DMA controller, the behavior is undefined.
*
* @param[in] channel
* The DMA channel to prepare for the DMA cycle.
*
* @param[in] cycleCtrl
* The DMA cycle type to prepare for.
*
* @param[in] primary
* @li true - prepare the primary descriptor
* @li false - prepare an alternate descriptor
*
* @param[in] useBurst
* The burst feature is only used on peripherals supporting DMA bursts.
* Bursts must not be used if the total length (as given by nMinus1) is
* less than the arbitration rate configured for the descriptor.
* See the reference manual for more details on burst usage.
*
* @param[in] dst
* An address to a start location to transfer data to. If NULL, leave setting in
* descriptor as is.
*
* @param[in] src
* An address to a start location to transfer data from. If NULL, leave setting in
* descriptor as is.
*
* @param[in] nMinus1
* A number of elements (minus 1) to transfer (<= 1023).
******************************************************************************/
static void DMA_Prepare(unsigned int channel,
DMA_CycleCtrl_TypeDef cycleCtrl,
bool primary,
bool useBurst,
void *dst,
const void *src,
unsigned int nMinus1)
{
DMA_DESCRIPTOR_TypeDef *descr;
DMA_DESCRIPTOR_TypeDef *primDescr;
DMA_CB_TypeDef *cb;
uint32_t inc;
uint32_t chBit;
uint32_t tmp;
primDescr = ((DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE)) + channel;
/* Find a descriptor to configure. */
if (primary) {
descr = primDescr;
} else {
descr = ((DMA_DESCRIPTOR_TypeDef *)(DMA->ALTCTRLBASE)) + channel;
}
/* If callback is defined, update information on whether the callback is issued */
/* for primary or alternate descriptor. This is mainly needed for ping-pong */
/* cycles. */
cb = (DMA_CB_TypeDef *)(primDescr->USER);
if (cb) {
cb->primary = (uint8_t)primary;
}
if (src) {
inc = (descr->CTRL & _DMA_CTRL_SRC_INC_MASK) >> _DMA_CTRL_SRC_INC_SHIFT;
if (inc == _DMA_CTRL_SRC_INC_NONE) {
descr->SRCEND = (volatile void*)src;
} else {
descr->SRCEND = (void *)((uint32_t)src + (nMinus1 << inc));
}
}
if (dst) {
inc = (descr->CTRL & _DMA_CTRL_DST_INC_MASK) >> _DMA_CTRL_DST_INC_SHIFT;
if (inc == _DMA_CTRL_DST_INC_NONE) {
descr->DSTEND = dst;
} else {
descr->DSTEND = (void *)((uint32_t)dst + (nMinus1 << inc));
}
}
chBit = 1 << channel;
if (useBurst) {
DMA->CHUSEBURSTS = chBit;
} else {
DMA->CHUSEBURSTC = chBit;
}
if (primary) {
DMA->CHALTC = chBit;
} else {
DMA->CHALTS = chBit;
}
/* Set the cycle control. */
tmp = descr->CTRL & ~(_DMA_CTRL_CYCLE_CTRL_MASK | _DMA_CTRL_N_MINUS_1_MASK);
tmp |= nMinus1 << _DMA_CTRL_N_MINUS_1_SHIFT;
tmp |= (uint32_t)cycleCtrl << _DMA_CTRL_CYCLE_CTRL_SHIFT;
descr->CTRL = tmp;
}
/** @endcond */
/*******************************************************************************
************************ INTERRUPT FUNCTIONS ******************************
******************************************************************************/
#ifndef EXCLUDE_DEFAULT_DMA_IRQ_HANDLER
/***************************************************************************//**
* @brief
* Interrupt handler for the DMA cycle completion handling.
*
* @details
* Clears any pending flags and calls registered callback (if any).
*
* If using the default interrupt vector table setup provided, this function
* is automatically placed in the IRQ table due to weak linking. If taking
* control over the interrupt vector table in some other way, this interrupt
* handler must be installed to support callback actions.
*
* For the user to implement a custom IRQ handler or run without
* a DMA IRQ handler, define EXCLUDE_DEFAULT_DMA_IRQ_HANDLER
* with a \#define statement or with the compiler option -D.
*
******************************************************************************/
void DMA_IRQHandler(void)
{
int channel;
DMA_CB_TypeDef *cb;
uint32_t pending;
uint32_t pendingPrio;
uint32_t prio;
uint32_t primaryCpy;
int i;
/* Get all pending and enabled interrupts. */
pending = DMA->IF;
pending &= DMA->IEN;
/* Assert on bus error. */
EFM_ASSERT(!(pending & DMA_IF_ERR));
/* Process all pending channel interrupts. First process channels */
/* defined with high priority, then those with default priority. */
prio = DMA->CHPRIS;
pendingPrio = pending & prio;
for (i = 0; i < 2; i++) {
channel = 0;
/* Process pending interrupts within high/default priority group */
/* honoring the priority within the group. */
while (pendingPrio) {
if (pendingPrio & 1) {
DMA_DESCRIPTOR_TypeDef *descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE);
uint32_t chmask = 1 << channel;
/* Clear a pending interrupt prior to invoking the callback, in case it */
/* sets up another DMA cycle. */
DMA->IFC = chmask;
/* Normally, no point in enabling interrupt without the callback, but */
/* check if the callback is defined anyway. Callback information is always */
/* located in the primary descriptor. */
cb = (DMA_CB_TypeDef *)(descr[channel].USER);
if (cb) {
/* Toggle next-descriptor indicator always prior to invoking */
/* callback (in case callback reconfigures something). */
primaryCpy = cb->primary;
cb->primary ^= 1;
if (cb->cbFunc) {
cb->cbFunc(channel, (bool)primaryCpy, cb->userPtr);
}
}
}
pendingPrio >>= 1;
channel++;
}
/* On second iteration, process default priority channels. */
pendingPrio = pending & ~prio;
}
}
#endif /* EXCLUDE_DEFAULT_DMA_IRQ_HANDLER */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Activate the DMA auto-request cycle (used for memory-memory transfers).
*
* @details
* Prior to activating the DMA cycle, the channel and descriptor to be used
* must have been properly configured.
*
* @note
* If using this function on a channel already activated and in use by the
* DMA controller, the behavior is undefined.
*
* @param[in] channel
* The DMA channel to activate the DMA cycle for.
*
* @param[in] primary
* @li true - activate using the primary descriptor
* @li false - activate using an alternate descriptor
*
* @param[in] dst
* An ddress to a start location to transfer data to. If NULL, leave setting in
* descriptor as is from a previous activation.
*
* @param[in] src
* An address to a start location to transfer data from. If NULL, leave setting in
* descriptor as is from a previous activation.
*
* @param[in] nMinus1
* A number of DMA transfer elements (minus 1) to transfer (<= 1023). The
* size of the DMA transfer element (1, 2 or 4 bytes) is configured with
* DMA_CfgDescr().
******************************************************************************/
void DMA_ActivateAuto(unsigned int channel,
bool primary,
void *dst,
const void *src,
unsigned int nMinus1)
{
uint32_t chBit;
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(nMinus1 <= (_DMA_CTRL_N_MINUS_1_MASK >> _DMA_CTRL_N_MINUS_1_SHIFT));
DMA_Prepare(channel,
dmaCycleCtrlAuto,
primary,
false,
dst,
src,
nMinus1);
chBit = 1 << channel;
DMA->CHENS = chBit; /* Enable the channel. */
DMA->CHSWREQ = chBit; /* Activate with the software request. */
}
/***************************************************************************//**
* @brief
* Activate the DMA basic cycle (used for memory-peripheral transfers).
*
* @details
* Prior to activating the DMA cycle, the channel and descriptor to be used
* must have been properly configured.
*
* @note
* If using this function on a channel already activated and in use by the
* DMA controller, the behavior is undefined.
*
* @param[in] channel
* The DMA channel to activate the DMA cycle for.
*
* @param[in] primary
* @li true - activate using the primary descriptor
* @li false - activate using an alternate descriptor
*
* @param[in] useBurst
* The burst feature is only used on peripherals supporting DMA bursts.
* Bursts must not be used if the total length (as given by nMinus1) is
* less than the arbitration rate configured for the descriptor.
* See the reference manual for more details on burst usage.
*
* @param[in] dst
* An address to a start location to transfer data to. If NULL, leave setting in
* descriptor as is from a previous activation.
*
* @param[in] src
* An address to a start location to transfer data from. If NULL, leave setting in
* descriptor as is from a previous activation.
*
* @param[in] nMinus1
* A number of DMA transfer elements (minus 1) to transfer (<= 1023). The
* size of the DMA transfer element (1, 2 or 4 bytes) is configured with
* DMA_CfgDescr().
******************************************************************************/
void DMA_ActivateBasic(unsigned int channel,
bool primary,
bool useBurst,
void *dst,
const void *src,
unsigned int nMinus1)
{
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(nMinus1 <= (_DMA_CTRL_N_MINUS_1_MASK >> _DMA_CTRL_N_MINUS_1_SHIFT));
DMA_Prepare(channel,
dmaCycleCtrlBasic,
primary,
useBurst,
dst,
src,
nMinus1);
/* Enable channel, request signal is provided by the peripheral device. */
DMA->CHENS = 1 << channel;
}
/***************************************************************************//**
* @brief
* Activate a DMA ping-pong cycle (used for memory-peripheral transfers).
*
* @details
* Prior to activating the DMA cycle, the channel and both descriptors must
* have been properly configured. The primary descriptor is always the first
* descriptor to be used by the DMA controller.
*
* @note
* If using this function on a channel already activated and in use by the
* DMA controller, the behavior is undefined.
*
* @param[in] channel
* The DMA channel to activate DMA cycle for.
*
* @param[in] useBurst
* The burst feature is only used on peripherals supporting DMA bursts.
* Bursts must not be used if the total length (as given by nMinus1) is
* less than the arbitration rate configured for the descriptors.
* See the reference manual for more details on burst usage. Notice
* that this setting is used for both the primary and alternate descriptors.
*
* @param[in] primDst
* An address to a start location to transfer data to, for the primary descriptor.
* If NULL, leave setting in descriptor as is from a previous activation.
*
* @param[in] primSrc
* An address to a start location to transfer data from, for the primary descriptor.
* If NULL, leave setting in the descriptor as is from a previous activation.
*
* @param[in] primNMinus1
* A number of DMA transfer elements (minus 1) to transfer (<= 1023), for
* primary descriptor. The size of the DMA transfer element (1, 2 or 4 bytes)
* is configured with DMA_CfgDescr().
*
* @param[in] altDst
* An address to a start location to transfer data to, for an alternate descriptor.
* If NULL, leave setting in descriptor as is from a previous activation.
*
* @param[in] altSrc
* An address to a start location to transfer data from, for an alternate descriptor.
* If NULL, leave setting in descriptor as is from a previous activation.
*
* @param[in] altNMinus1
* A number of DMA transfer elements (minus 1) to transfer (<= 1023), for
* an alternate descriptor. The size of the DMA transfer element (1, 2 or 4 bytes)
* is configured with DMA_CfgDescr().
******************************************************************************/
void DMA_ActivatePingPong(unsigned int channel,
bool useBurst,
void *primDst,
const void *primSrc,
unsigned int primNMinus1,
void *altDst,
const void *altSrc,
unsigned int altNMinus1)
{
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(primNMinus1 <= (_DMA_CTRL_N_MINUS_1_MASK >> _DMA_CTRL_N_MINUS_1_SHIFT));
EFM_ASSERT(altNMinus1 <= (_DMA_CTRL_N_MINUS_1_MASK >> _DMA_CTRL_N_MINUS_1_SHIFT));
/* Prepare alternate descriptor first */
DMA_Prepare(channel,
dmaCycleCtrlPingPong,
false,
useBurst,
altDst,
altSrc,
altNMinus1);
/* Prepare the primary descriptor last to start a cycle using it. */
DMA_Prepare(channel,
dmaCycleCtrlPingPong,
true,
useBurst,
primDst,
primSrc,
primNMinus1);
/* Enable the channel, the request signal is provided by the peripheral device. */
DMA->CHENS = 1 << channel;
}
/***************************************************************************//**
* @brief
* Activate the DMA scatter-gather cycle (used for either memory-peripheral
* or memory-memory transfers).
*
* @details
* Prior to activating the DMA cycle, the array with alternate descriptors
* must have been properly configured. This function can be reused without
* reconfiguring the alternate descriptors, as long as @p count is the same.
*
* @note
* If using this function on a channel already activated and in use by the
* DMA controller, the behavior is undefined.
*
* @param[in] channel
* The DMA channel to activate DMA cycle for.
*
* @param[in] useBurst
* The burst feature is only used on peripherals supporting DMA bursts
* (this parameter is ignored for memory scatter-gather cycles).
* This parameter determines if bursts should be enabled during DMA transfers
* using the alternate descriptors. Bursts must not be used if the total
* length (as given by nMinus1 for the alternate descriptor) is
* less than the arbitration rate configured for the descriptor.
* See the reference manual for more details on burst usage.
*
* @param[in,out] altDescr
* A pointer to a start of an array with prepared alternate descriptors. The last
* descriptor will have its cycle control type reprogrammed to a basic type.
*
* @param[in] count
* A number of alternate descriptors in @p altDescr array. The maximum number of
* alternate descriptors is 256.
******************************************************************************/
void DMA_ActivateScatterGather(unsigned int channel,
bool useBurst,
DMA_DESCRIPTOR_TypeDef *altDescr,
unsigned int count)
{
DMA_DESCRIPTOR_TypeDef *descr;
DMA_CB_TypeDef *cb;
uint32_t cycleCtrl;
uint32_t chBit;
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(altDescr);
EFM_ASSERT(count && (count <= 256));
/* Configure the primary descriptor properly to */
/* transfer one complete alternate descriptor from the alternate */
/* descriptor table into the actual alternate descriptor. */
descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE) + channel;
/* Set the source end address to point to the alternate descriptor array. */
descr->SRCEND = (uint32_t *)altDescr + (count * 4) - 1;
/* The destination end address in the primary descriptor MUST point */
/* to the corresponding alternate descriptor in scatter-gather mode. */
descr->DSTEND = (uint32_t *)((DMA_DESCRIPTOR_TypeDef *)(DMA->ALTCTRLBASE)
+ channel + 1) - 1;
/* The user field of the descriptor is used for the callback configuration */
/* and is already configured when the channel is configured. Do not modify it. */
/* Determine from alternate configuration whether this is a memory or a */
/* peripheral scatter-gather by looking at the first alternate descriptor. */
cycleCtrl = altDescr->CTRL & _DMA_CTRL_CYCLE_CTRL_MASK;
cycleCtrl &= ~(1 << _DMA_CTRL_CYCLE_CTRL_SHIFT);
EFM_ASSERT((cycleCtrl == dmaCycleCtrlMemScatterGather)
|| (cycleCtrl == dmaCycleCtrlPerScatterGather));
/* Set the last alternate descriptor to basic or auto-request a cycle type in */
/* order to have dma_done signal asserted when complete. Otherwise, an interrupt */
/* will not be triggered when done. */
altDescr[count - 1].CTRL &= ~_DMA_CTRL_CYCLE_CTRL_MASK;
if (cycleCtrl == dmaCycleCtrlMemScatterGather) {
altDescr[count - 1].CTRL |= (uint32_t)dmaCycleCtrlAuto
<< _DMA_CTRL_CYCLE_CTRL_SHIFT;
} else {
altDescr[count - 1].CTRL |= (uint32_t)dmaCycleCtrlBasic
<< _DMA_CTRL_CYCLE_CTRL_SHIFT;
}
/* If the callback is defined, update the information on whether the callback is issued for */
/* primary or alternate descriptors. Not really useful for scatter-gather */
/* but there for consistency. Always set to alternate, since that is the last */
/* descriptor used. */
cb = (DMA_CB_TypeDef *)(descr->USER);
if (cb) {
cb->primary = false;
}
/* Configure the primary descriptor control word. */
descr->CTRL = ((uint32_t)dmaDataInc4 << _DMA_CTRL_DST_INC_SHIFT)
| ((uint32_t)dmaDataSize4 << _DMA_CTRL_DST_SIZE_SHIFT)
| ((uint32_t)dmaDataInc4 << _DMA_CTRL_SRC_INC_SHIFT)
| ((uint32_t)dmaDataSize4 << _DMA_CTRL_SRC_SIZE_SHIFT)
/* Use the same protection scheme as for alternate descriptors. */
| (altDescr->CTRL & _DMA_CTRL_SRC_PROT_CTRL_MASK)
| ((uint32_t)dmaArbitrate4 << _DMA_CTRL_R_POWER_SHIFT)
| (((count * 4) - 1) << _DMA_CTRL_N_MINUS_1_SHIFT)
| (((uint32_t)useBurst & 1) << _DMA_CTRL_NEXT_USEBURST_SHIFT)
| cycleCtrl;
chBit = 1 << channel;
/* Start with the primary descriptor. */
DMA->CHALTC = chBit;
/* Enable the channel. */
DMA->CHENS = chBit;
/* Send a request if memory scatter-gather. Otherwise, the request signal is */
/* provided by the peripheral. */
if (cycleCtrl == dmaCycleCtrlMemScatterGather) {
DMA->CHSWREQ = chBit;
}
}
/***************************************************************************//**
* @brief
* Configure a DMA channel.
*
* @details
* Configure miscellaneous issues for a DMA channel. This function is typically
* used once to set up a channel for a certain type of use.
*
* @note
* If using this function on a channel already in use by the DMA controller,
* the behavior is undefined.
*
* @param[in] channel
* The DMA channel to configure.
*
* @param[in] cfg
* The configuration to use.
******************************************************************************/
void DMA_CfgChannel(unsigned int channel, DMA_CfgChannel_TypeDef *cfg)
{
DMA_DESCRIPTOR_TypeDef *descr;
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(cfg);
/* Always keep callback configuration reference in the primary descriptor. */
descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE);
descr[channel].USER = (uint32_t)(cfg->cb);
/* Set to a specified priority for a channel. */
if (cfg->highPri) {
DMA->CHPRIS = 1 << channel;
} else {
DMA->CHPRIC = 1 << channel;
}
/* Set the DMA signal source select. */
DMA->CH[channel].CTRL = cfg->select;
/* Enable/disable an interrupt as specified. */
if (cfg->enableInt) {
DMA->IFC = (1 << channel);
BUS_RegBitWrite(&(DMA->IEN), channel, 1);
} else {
BUS_RegBitWrite(&(DMA->IEN), channel, 0);
}
}
/***************************************************************************//**
* @brief
* Configure the DMA descriptor for auto-request, basic, or ping-pong DMA cycles.
*
* @details
* This function is used to configure a descriptor for the following
* DMA cycle types:
*
* @li auto-request - used for a memory/memory transfer
* @li basic - used for a peripheral/memory transfer
* @li ping-pong - used for a ping-pong-based peripheral/memory transfer
* style providing time to refresh one descriptor while the other is
* in use.
*
* The DMA cycle is not activated. See DMA_ActivateAuto(),
* DMA_ActivateBasic(), or DMA_ActivatePingPong() to activate the DMA cycle.
* In many cases, the configuration only has to be done once, and all
* subsequent cycles may be activated with the activate function.
*
* For ping-pong DMA cycles, this function must be used both on the primary
* and the alternate descriptor prior to activating the DMA cycle.
*
* Notice that the DMA channel must also be configured. See DMA_CfgChannel().
*
* @note
* If using this function on a descriptor already activated and in use by
* the DMA controller, the behavior is undefined.
*
* @param[in] channel
* The DMA channel to configure for.
*
* @param[in] primary
* @li true - configure the primary descriptor
* @li false - configure an alternate descriptor
*
* @param[in] cfg
* The configuration to use.
******************************************************************************/
void DMA_CfgDescr(unsigned int channel,
bool primary,
DMA_CfgDescr_TypeDef *cfg)
{
DMA_DESCRIPTOR_TypeDef *descr;
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(cfg);
/* Find a descriptor to configure. */
if (primary) {
descr = (DMA_DESCRIPTOR_TypeDef *)DMA->CTRLBASE;
} else {
descr = (DMA_DESCRIPTOR_TypeDef *)DMA->ALTCTRLBASE;
}
descr += channel;
/* Prepare the descriptor. */
/* The source/destination end addresses set when started. */
descr->CTRL = (cfg->dstInc << _DMA_CTRL_DST_INC_SHIFT)
| (cfg->size << _DMA_CTRL_DST_SIZE_SHIFT)
| (cfg->srcInc << _DMA_CTRL_SRC_INC_SHIFT)
| (cfg->size << _DMA_CTRL_SRC_SIZE_SHIFT)
| ((uint32_t)(cfg->hprot) << _DMA_CTRL_SRC_PROT_CTRL_SHIFT)
| (cfg->arbRate << _DMA_CTRL_R_POWER_SHIFT)
| (0 << _DMA_CTRL_N_MINUS_1_SHIFT) /* Set when activated. */
| (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) /* Set when activated. */
| DMA_CTRL_CYCLE_CTRL_INVALID; /* Set when activated. */
}
#if defined(_DMA_LOOP0_MASK) && defined(_DMA_LOOP1_MASK)
/***************************************************************************//**
* @brief Configure the DMA channel for Loop mode or 2D transfer.
*
* @details
* For 2D transfer, set cfg->enable to "false" and only configure nMinus1
* to the same width as the channel descriptor.
*
* @param[in] channel
* The DMA channel to configure for.
*
* @param[in] cfg
* The configuration to use.
******************************************************************************/
void DMA_CfgLoop(unsigned int channel, DMA_CfgLoop_TypeDef *cfg)
{
EFM_ASSERT(channel <= 1);
EFM_ASSERT(cfg->nMinus1 <= 1023);
/* Configure the LOOP setting. */
switch ( channel ) {
case 0:
DMA->LOOP0 = (cfg->enable << _DMA_LOOP0_EN_SHIFT)
| (cfg->nMinus1 << _DMA_LOOP0_WIDTH_SHIFT);
break;
case 1:
DMA->LOOP1 = (cfg->enable << _DMA_LOOP1_EN_SHIFT)
| (cfg->nMinus1 << _DMA_LOOP1_WIDTH_SHIFT);
break;
}
}
#endif
#if defined(_DMA_RECT0_MASK)
/***************************************************************************//**
* @brief Configure the DMA channel 2D transfer properties.
*
* @param[in] channel
* The DMA channel to configure for.
*
* @param[in] cfg
* The configuration to use.
******************************************************************************/
void DMA_CfgRect(unsigned int channel, DMA_CfgRect_TypeDef *cfg)
{
(void)channel; /* Unused parameter */
EFM_ASSERT(channel == 0);
EFM_ASSERT(cfg->dstStride <= 2047);
EFM_ASSERT(cfg->srcStride <= 2047);
EFM_ASSERT(cfg->height <= 1023);
/* Configure the rectangular/2D copy. */
DMA->RECT0 = (cfg->dstStride << _DMA_RECT0_DSTSTRIDE_SHIFT)
| (cfg->srcStride << _DMA_RECT0_SRCSTRIDE_SHIFT)
| (cfg->height << _DMA_RECT0_HEIGHT_SHIFT);
}
#endif
/***************************************************************************//**
* @brief
* Configure an alternate DMA descriptor for use with scatter-gather DMA
* cycles.
*
* @details
* In scatter-gather mode, the alternate descriptors are located in one
* contiguous memory area. Each of the alternate descriptors must be fully
* configured prior to starting the scatter-gather DMA cycle.
*
* The DMA cycle is not activated by this function. See
* DMA_ActivateScatterGather() to activate the DMA cycle. In some cases, the
* alternate configuration only has to be done once and all subsequent
* transfers may be activated with the activate function.
*
* Notice that the DMA channel must also be configured, see DMA_CfgChannel().
*
* @param[in] descr
* Points to the start of a memory area holding the alternate descriptors.
*
* @param[in] indx
* An alternate descriptor index number to configure (numbered from 0).
*
* @param[in] cfg
* The configuration to use.
******************************************************************************/
void DMA_CfgDescrScatterGather(DMA_DESCRIPTOR_TypeDef *descr,
unsigned int indx,
DMA_CfgDescrSGAlt_TypeDef *cfg)
{
uint32_t cycleCtrl;
EFM_ASSERT(descr);
EFM_ASSERT(cfg);
/* Point to a selected entry in the alternate descriptor table. */
descr += indx;
if (cfg->srcInc == dmaDataIncNone) {
descr->SRCEND = cfg->src;
} else {
descr->SRCEND = (void *)((uint32_t)(cfg->src)
+ ((uint32_t)(cfg->nMinus1) << cfg->srcInc));
}
if (cfg->dstInc == dmaDataIncNone) {
descr->DSTEND = cfg->dst;
} else {
descr->DSTEND = (void *)((uint32_t)(cfg->dst)
+ ((uint32_t)(cfg->nMinus1) << cfg->dstInc));
}
/* User-definable part not used. */
descr->USER = 0;
if (cfg->peripheral) {
cycleCtrl = (uint32_t)dmaCycleCtrlPerScatterGather + 1;
} else {
cycleCtrl = (uint32_t)dmaCycleCtrlMemScatterGather + 1;
}
descr->CTRL = (cfg->dstInc << _DMA_CTRL_DST_INC_SHIFT)
| (cfg->size << _DMA_CTRL_DST_SIZE_SHIFT)
| (cfg->srcInc << _DMA_CTRL_SRC_INC_SHIFT)
| (cfg->size << _DMA_CTRL_SRC_SIZE_SHIFT)
| ((uint32_t)(cfg->hprot) << _DMA_CTRL_SRC_PROT_CTRL_SHIFT)
| (cfg->arbRate << _DMA_CTRL_R_POWER_SHIFT)
| ((uint32_t)(cfg->nMinus1) << _DMA_CTRL_N_MINUS_1_SHIFT)
/* Never set next useburst bit since the descriptor used after the */
/* alternate descriptor is the primary descriptor which operates on */
/* memory. If the alternate descriptors need to have useBurst set, this */
/* is done when setting up the primary descriptor, i.e., when activating. */
| (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT)
| (cycleCtrl << _DMA_CTRL_CYCLE_CTRL_SHIFT);
}
/***************************************************************************//**
* @brief
* Enable or disable a DMA channel.
*
* @details
* Use this function to explicitly enable or disable a DMA channel. A DMA
* channel is automatically disabled when the DMA controller has finished a
* transaction.
*
* @param[in] channel
* The DMA channel to enable or disable.
*
* @param[in] enable
* If 'true', the channel will be enabled. If 'false', the channel will be
* disabled.
******************************************************************************/
void DMA_ChannelEnable(unsigned int channel, bool enable)
{
EFM_ASSERT(channel < DMA_CHAN_COUNT);
if (enable) {
DMA->CHENS = 1 << channel;
} else {
DMA->CHENC = 1 << channel;
}
}
/***************************************************************************//**
* @brief
* Check if the DMA channel is enabled.
*
* @details
* The DMA channel is disabled when the DMA controller has finished a DMA
* cycle.
*
* @param[in] channel
* The DMA channel to check.
*
* @return
* True if the channel is enabled, false if not.
******************************************************************************/
bool DMA_ChannelEnabled(unsigned int channel)
{
EFM_ASSERT(channel < DMA_CHAN_COUNT);
return (bool)((DMA->CHENS >> channel) & 1);
}
/***************************************************************************//**
* @brief
* Enable or disable a DMA channel request.
*
* @details
* Use this function to enable or disable a DMA channel request. This will
* prevent the DMA from proceeding after its current transaction if disabled.
*
* @param[in] channel
* The DMA channel to enable or disable the request on.
*
* @param[in] enable
* If 'true', the request will be enabled. If 'false', the request will be disabled.
******************************************************************************/
void DMA_ChannelRequestEnable(unsigned int channel, bool enable)
{
EFM_ASSERT(channel < DMA_CHAN_COUNT);
if (enable) {
BUS_RegBitWrite(&DMA->CHREQMASKC, channel, 1);
} else {
BUS_RegBitWrite(&DMA->CHREQMASKS, channel, 1);
}
}
/***************************************************************************//**
* @brief
* Initialize the DMA controller.
*
* @details
* This function resets and prepares the DMA controller for use. Although
* it may be used several times, it is normally only used during system
* initialization. If reused during a normal operation, any ongoing DMA
* transfers will be aborted. When complete, the DMA controller is in
* an enabled state.
*
* @note
* Must be invoked before using the DMA controller.
*
* @param[in] init
* A pointer to a structure containing the DMA initialization information.
******************************************************************************/
void DMA_Init(DMA_Init_TypeDef *init)
{
EFM_ASSERT(init);
/* Make sure that the control block is properly aligned. */
#if (DMA_CHAN_COUNT <= 4)
EFM_ASSERT(!((uint32_t)(init->controlBlock) & (128 - 1)));
#elif (DMA_CHAN_COUNT <= 8) || (DMA_CHAN_COUNT <= 12)
EFM_ASSERT(!((uint32_t)(init->controlBlock) & (256 - 1)));
#else
#error "Unsupported DMA channel count (em_dma.c)."
#endif
/* Make sure that the DMA clock is enabled prior to accessing the DMA module. */
CMU_ClockEnable(cmuClock_DMA, true);
/* Make sure that the DMA controller is set to a known reset state. */
DMA_Reset();
/* Clear/enable DMA interrupts. */
NVIC_ClearPendingIRQ(DMA_IRQn);
NVIC_EnableIRQ(DMA_IRQn);
/* Enable the bus error interrupt. */
DMA->IEN = DMA_IEN_ERR;
/* Set the pointer to a control block. Notice that the pointer must have been */
/* properly aligned according to requirements defined in the reference */
/* manual. */
DMA->CTRLBASE = (uint32_t)(init->controlBlock);
/* Configure and enable the DMA controller. */
DMA->CONFIG = ((uint32_t)(init->hprot) << _DMA_CONFIG_CHPROT_SHIFT)
| DMA_CONFIG_EN;
}
/***************************************************************************//**
* @brief
* Refresh a descriptor used in a DMA ping-pong cycle.
*
* @details
* During a ping-pong DMA cycle, the DMA controller automatically alternates
* between the primary and alternate descriptors, when completing use of a
* descriptor. While the other descriptor is in use by the DMA controller,
* the software should refresh the completed descriptor. This is typically done from
* the callback defined for the ping-pong cycle.
*
* @param[in] channel
* The DMA channel to refresh the ping-pong descriptor for.
*
* @param[in] primary
* @li true - refresh the primary descriptor
* @li false - refresh an alternate descriptor
*
* @param[in] useBurst
* The burst feature is only used on peripherals supporting DMA bursts.
* Bursts must not be used if the total length (as given by nMinus1) is
* less than the arbitration rate configured for the descriptor.
* See the reference manual for more details on burst usage.
*
* @param[in] dst
* An address to a start location to transfer data to. If NULL, leave setting in
* descriptor as is.
*
* @param[in] src
* An address to a start location to transfer data from. If NULL, leave setting in
* descriptor as is.
*
* @param[in] nMinus1
* A number of DMA transfer elements (minus 1) to transfer (<= 1023). The
* size of the DMA transfer element (1, 2 or 4 bytes) is configured with
* DMA_CfgDescr().
*
* @param[in] stop
* Indicate that the DMA ping-pong cycle stops @b when done using
* this descriptor.
******************************************************************************/
void DMA_RefreshPingPong(unsigned int channel,
bool primary,
bool useBurst,
void *dst,
const void *src,
unsigned int nMinus1,
bool stop)
{
DMA_CycleCtrl_TypeDef cycleCtrl;
DMA_DESCRIPTOR_TypeDef *descr;
uint32_t inc;
uint32_t chBit;
uint32_t tmp;
EFM_ASSERT(channel < DMA_CHAN_COUNT);
EFM_ASSERT(nMinus1 <= (_DMA_CTRL_N_MINUS_1_MASK >> _DMA_CTRL_N_MINUS_1_SHIFT));
/* The ping-pong DMA cycle may be stopped by issuing a basic cycle type. */
if (stop) {
cycleCtrl = dmaCycleCtrlBasic;
} else {
cycleCtrl = dmaCycleCtrlPingPong;
}
/* Find a descriptor to configure. */
if (primary) {
descr = ((DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE)) + channel;
} else {
descr = ((DMA_DESCRIPTOR_TypeDef *)(DMA->ALTCTRLBASE)) + channel;
}
if (src) {
inc = (descr->CTRL & _DMA_CTRL_SRC_INC_MASK) >> _DMA_CTRL_SRC_INC_SHIFT;
if (inc == _DMA_CTRL_SRC_INC_NONE) {
descr->SRCEND = (volatile void*)src;
} else {
descr->SRCEND = (void *)((uint32_t)src + (nMinus1 << inc));
}
}
if (dst) {
inc = (descr->CTRL & _DMA_CTRL_DST_INC_MASK) >> _DMA_CTRL_DST_INC_SHIFT;
if (inc == _DMA_CTRL_DST_INC_NONE) {
descr->DSTEND = dst;
} else {
descr->DSTEND = (void *)((uint32_t)dst + (nMinus1 << inc));
}
}
chBit = 1 << channel;
if (useBurst) {
DMA->CHUSEBURSTS = chBit;
} else {
DMA->CHUSEBURSTC = chBit;
}
/* Set cycle control. */
tmp = descr->CTRL & ~(_DMA_CTRL_CYCLE_CTRL_MASK | _DMA_CTRL_N_MINUS_1_MASK);
tmp |= nMinus1 << _DMA_CTRL_N_MINUS_1_SHIFT;
tmp |= cycleCtrl << _DMA_CTRL_CYCLE_CTRL_SHIFT;
descr->CTRL = tmp;
}
/***************************************************************************//**
* @brief
* Reset the DMA controller.
*
* @details
* This functions will disable the DMA controller and set it to a reset
* state.
*
* @note
* Note that any ongoing transfers will be aborted.
******************************************************************************/
void DMA_Reset(void)
{
int i;
/* Disable DMA interrupts */
NVIC_DisableIRQ(DMA_IRQn);
/* Put the DMA controller into a known state, first disabling it. */
DMA->CONFIG = _DMA_CONFIG_RESETVALUE;
DMA->CHUSEBURSTC = _DMA_CHUSEBURSTC_MASK;
DMA->CHREQMASKC = _DMA_CHREQMASKC_MASK;
DMA->CHENC = _DMA_CHENC_MASK;
DMA->CHALTC = _DMA_CHALTC_MASK;
DMA->CHPRIC = _DMA_CHPRIC_MASK;
DMA->ERRORC = DMA_ERRORC_ERRORC;
DMA->IEN = _DMA_IEN_RESETVALUE;
DMA->IFC = _DMA_IFC_MASK;
/* Clear channel control flags. */
for (i = 0; i < DMA_CHAN_COUNT; i++) {
DMA->CH[i].CTRL = _DMA_CH_CTRL_RESETVALUE;
}
}
/** @} (end addtogroup DMA) */
/** @} (end addtogroup emlib) */
#endif /* defined( DMA_PRESENT ) */