1174 lines
39 KiB
C
1174 lines
39 KiB
C
/***************************************************************************//**
|
|
* @file
|
|
* @brief Direct memory access (DMA) module peripheral API
|
|
* @author Energy Micro AS
|
|
* @version 3.0.0
|
|
*******************************************************************************
|
|
* @section License
|
|
* <b>(C) Copyright 2012 Energy Micro AS, http://www.energymicro.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: Energy Micro AS has no
|
|
* obligation to support this Software. Energy Micro AS 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.
|
|
*
|
|
* Energy Micro AS 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"
|
|
#include "em_cmu.h"
|
|
#include "em_assert.h"
|
|
#include "em_bitband.h"
|
|
|
|
/***************************************************************************//**
|
|
* @addtogroup EM_Library
|
|
* @{
|
|
******************************************************************************/
|
|
|
|
/***************************************************************************//**
|
|
* @addtogroup DMA
|
|
* @brief Direct Memory Access (DMA) Peripheral API
|
|
* @details
|
|
* These 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. Please refer to
|
|
* the EFM32 reference manual for further details.
|
|
*
|
|
* The term 'descriptor' is used as a synonym to the 'channel control data
|
|
* structure' term.
|
|
*
|
|
* In order to use the DMA controller, the initialization function must have
|
|
* been executed once (normally during system init):
|
|
* @verbatim
|
|
* DMA_Init();
|
|
* @endverbatim
|
|
*
|
|
* Then, normally a user of a DMA channel configures the channel:
|
|
* @verbatim
|
|
* DMA_CfgChannel();
|
|
* @endverbatim
|
|
*
|
|
* The channel configuration only has to be done once, if reusing the channel
|
|
* for the same purpose later.
|
|
*
|
|
* In order 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, the alternate descriptor array must be programmed:
|
|
* @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.
|
|
*
|
|
* In order 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 descriptor for DMA cycle.
|
|
*
|
|
* @details
|
|
* This function prepares the last pieces of 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 behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to prepare for DMA cycle.
|
|
*
|
|
* @param[in] cycleCtrl
|
|
* DMA cycle type to prepare for.
|
|
*
|
|
* @param[in] primary
|
|
* @li true - prepare primary descriptor
|
|
* @li false - prepare 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. Please
|
|
* refer to the reference manual for further details on burst usage.
|
|
*
|
|
* @param[in] dst
|
|
* Address to start location to transfer data to. If NULL, leave setting in
|
|
* descriptor as is.
|
|
*
|
|
* @param[in] src
|
|
* Address to start location to transfer data from. If NULL, leave setting in
|
|
* descriptor as is.
|
|
*
|
|
* @param[in] nMinus1
|
|
* 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,
|
|
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 descriptor to configure */
|
|
if (primary)
|
|
{
|
|
descr = primDescr;
|
|
}
|
|
else
|
|
{
|
|
descr = ((DMA_DESCRIPTOR_TypeDef *)(DMA->ALTCTRLBASE)) + channel;
|
|
}
|
|
|
|
/* If callback defined, update info on whether callback is issued */
|
|
/* for primary or alternate descriptor. 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 = 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 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 ******************************
|
|
******************************************************************************/
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Interrupt handler for 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 in order to be able to support callback actions.
|
|
******************************************************************************/
|
|
void DMA_IRQHandler(void)
|
|
{
|
|
DMA_DESCRIPTOR_TypeDef *descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE);
|
|
int channel;
|
|
DMA_CB_TypeDef *cb;
|
|
uint32_t pending;
|
|
uint32_t pendingPrio;
|
|
uint32_t prio;
|
|
uint32_t primaryCpy;
|
|
int i;
|
|
|
|
/* Get all pending interrupts */
|
|
pending = DMA->IF;
|
|
|
|
/* Check for bus error */
|
|
if (pending & DMA_IF_ERR)
|
|
{
|
|
/* Loop here to enable the debugger to see what has happened */
|
|
while (1)
|
|
;
|
|
}
|
|
|
|
/* 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 */
|
|
/* honouring priority within group. */
|
|
while (pendingPrio)
|
|
{
|
|
if (pendingPrio & 1)
|
|
{
|
|
/* Clear pending interrupt prior to invoking callback, in case it */
|
|
/* sets up another DMA cycle. */
|
|
DMA->IFC = 1 << channel;
|
|
|
|
/* Normally, no point in enabling interrupt without callback, but */
|
|
/* check if callback is defined anyway. Callback info is always */
|
|
/* located in primary descriptor. */
|
|
cb = (DMA_CB_TypeDef *)(descr[channel].USER);
|
|
if (cb)
|
|
{
|
|
/* Toggle next-descriptor indicator always prior to invoking */
|
|
/* callback (in case callback reconfigurs 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;
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
************************** GLOBAL FUNCTIONS *******************************
|
|
******************************************************************************/
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Activate 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 behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to activate DMA cycle for.
|
|
*
|
|
* @param[in] primary
|
|
* @li true - activate using primary descriptor
|
|
* @li false - activate using alternate descriptor
|
|
*
|
|
* @param[in] dst
|
|
* Address to start location to transfer data to. If NULL, leave setting in
|
|
* descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] src
|
|
* Address to start location to transfer data from. If NULL, leave setting in
|
|
* descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] nMinus1
|
|
* 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,
|
|
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 channel */
|
|
DMA->CHSWREQ = chBit; /* Activate with SW request */
|
|
}
|
|
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Activate 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 behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to activate DMA cycle for.
|
|
*
|
|
* @param[in] primary
|
|
* @li true - activate using primary descriptor
|
|
* @li false - activate using 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. Please
|
|
* refer to the reference manual for further details on burst usage.
|
|
*
|
|
* @param[in] dst
|
|
* Address to start location to transfer data to. If NULL, leave setting in
|
|
* descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] src
|
|
* Address to start location to transfer data from. If NULL, leave setting in
|
|
* descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] nMinus1
|
|
* 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,
|
|
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 peripheral device */
|
|
DMA->CHENS = 1 << channel;
|
|
}
|
|
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Activate 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 behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* 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. Please
|
|
* refer to the reference manual for further details on burst usage. Notice
|
|
* that this setting is used for both the primary and alternate descriptors.
|
|
*
|
|
* @param[in] primDst
|
|
* Address to start location to transfer data to, for primary descriptor.
|
|
* If NULL, leave setting in descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] primSrc
|
|
* Address to start location to transfer data from, for primary descriptor.
|
|
* If NULL, leave setting in descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] primNMinus1
|
|
* 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
|
|
* Address to start location to transfer data to, for alternate descriptor.
|
|
* If NULL, leave setting in descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] altSrc
|
|
* Address to start location to transfer data from, for alternate descriptor.
|
|
* If NULL, leave setting in descriptor as is from a previous activation.
|
|
*
|
|
* @param[in] altNMinus1
|
|
* Number of DMA transfer elements (minus 1) to transfer (<= 1023), for
|
|
* 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,
|
|
void *primSrc,
|
|
unsigned int primNMinus1,
|
|
void *altDst,
|
|
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 primary descriptor last in order to start cycle using it */
|
|
DMA_Prepare(channel,
|
|
dmaCycleCtrlPingPong,
|
|
true,
|
|
useBurst,
|
|
primDst,
|
|
primSrc,
|
|
primNMinus1);
|
|
|
|
/* Enable channel, request signal is provided by peripheral device */
|
|
DMA->CHENS = 1 << channel;
|
|
}
|
|
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Activate 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 behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to activate DMA cycle for.
|
|
*
|
|
* @param[in] useBurst
|
|
* The burst feature is only used on peripherals supporting DMA bursts
|
|
* (and thus 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. Please
|
|
* refer to the reference manual for further details on burst usage.
|
|
*
|
|
* @param[in,out] altDescr
|
|
* Pointer to start of array with prepared alternate descriptors. The last
|
|
* descriptor will have its cycle control type reprogrammed to basic type.
|
|
*
|
|
* @param[in] count
|
|
* Number of alternate descriptors in @p altDescr array. 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));
|
|
|
|
/* We have to configure the primary descriptor properly in order to */
|
|
/* transfer one complete alternate descriptor from the alternate */
|
|
/* descriptor table into the actual alternate descriptor. */
|
|
descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE) + channel;
|
|
|
|
/* Set source end address to point to 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 callback configuration, */
|
|
/* and already configured when channel is configured. Do not modify it. */
|
|
|
|
/* Determine from alternate configuration whether this is a memory or */
|
|
/* 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 last alternate descriptor to basic or auto-request cycle type in */
|
|
/* order to have dma_done signal asserted when complete. Otherwise 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 callback defined, update info on whether callback is issued for */
|
|
/* primary or alternate descriptor. Not really useful for scatter-gather, */
|
|
/* but do for consistency. Always set to alternate, since that is the last */
|
|
/* descriptor actually used. */
|
|
cb = (DMA_CB_TypeDef *)(descr->USER);
|
|
if (cb)
|
|
{
|
|
cb->primary = false;
|
|
}
|
|
|
|
/* Configure 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 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 primary descriptor */
|
|
DMA->CHALTC = chBit;
|
|
|
|
/* Enable channel */
|
|
DMA->CHENS = chBit;
|
|
|
|
/* Send request if memory scatter-gather, otherwise request signal is */
|
|
/* provided by 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 setup a channel for a certain type of use.
|
|
*
|
|
* @note
|
|
* If using this function on a channel already in use by the DMA controller,
|
|
* the behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to configure.
|
|
*
|
|
* @param[in] cfg
|
|
* 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 primary descriptor */
|
|
descr = (DMA_DESCRIPTOR_TypeDef *)(DMA->CTRLBASE);
|
|
descr[channel].USER = (uint32_t)(cfg->cb);
|
|
|
|
/* Set to specified priority for channel */
|
|
if (cfg->highPri)
|
|
{
|
|
DMA->CHPRIS = 1 << channel;
|
|
}
|
|
else
|
|
{
|
|
DMA->CHPRIC = 1 << channel;
|
|
}
|
|
|
|
/* Set DMA signal source select */
|
|
DMA->CH[channel].CTRL = cfg->select;
|
|
|
|
/* Enable/disable interrupt as specified */
|
|
if (cfg->enableInt)
|
|
{
|
|
DMA->IFC = (1 << channel);
|
|
BITBAND_Peripheral(&(DMA->IEN), channel, 1);
|
|
}
|
|
else
|
|
{
|
|
BITBAND_Peripheral(&(DMA->IEN), channel, 0);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Configure DMA descriptor for auto-request, basic or ping-pong DMA cycles.
|
|
*
|
|
* @details
|
|
* This function is used for configuration of a descriptor for the following
|
|
* DMA cycle types:
|
|
*
|
|
* @li auto-request - used for 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, please 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 behaviour is undefined.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to configure for.
|
|
*
|
|
* @param[in] primary
|
|
* @li true - configure primary descriptor
|
|
* @li false - configure alternate descriptor
|
|
*
|
|
* @param[in] cfg
|
|
* 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 descriptor to configure */
|
|
if (primary)
|
|
{
|
|
descr = (DMA_DESCRIPTOR_TypeDef *)DMA->CTRLBASE;
|
|
}
|
|
else
|
|
{
|
|
descr = (DMA_DESCRIPTOR_TypeDef *)DMA->ALTCTRLBASE;
|
|
}
|
|
descr += channel;
|
|
|
|
/* Prepare the descriptor */
|
|
/* 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(_EFM32_GIANT_FAMILY)
|
|
/***************************************************************************//**
|
|
* @brief Configure DMA channel for Loop mode or 2D transfer.
|
|
*
|
|
* @details
|
|
* For 2D transfer, set cfg->enable to "false", and only configure nMinus1
|
|
* to same width as channel descriptor.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to configure for.
|
|
*
|
|
* @param[in] cfg
|
|
* Configuration to use.
|
|
******************************************************************************/
|
|
void DMA_CfgLoop(unsigned int channel, DMA_CfgLoop_TypeDef *cfg)
|
|
{
|
|
EFM_ASSERT(channel <= 1);
|
|
EFM_ASSERT(cfg->nMinus1 <= 1023);
|
|
|
|
/* Configure 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;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************//**
|
|
* @brief Configure DMA channel 2D transfer properties.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to configure for.
|
|
*
|
|
* @param[in] cfg
|
|
* Configuration to use.
|
|
******************************************************************************/
|
|
void DMA_CfgRect(unsigned int channel, DMA_CfgRect_TypeDef *cfg)
|
|
{
|
|
EFM_ASSERT(channel == 0);
|
|
EFM_ASSERT(cfg->dstStride <= 2047);
|
|
EFM_ASSERT(cfg->srcStride <= 2047);
|
|
EFM_ASSERT(cfg->height <= 1023);
|
|
|
|
/* Configure 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 descriptor must be fully
|
|
* configured prior to starting the scatter-gather DMA cycle.
|
|
*
|
|
* The DMA cycle is not activated by this function, please 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 start of memory area holding the alternate descriptors.
|
|
*
|
|
* @param[in] indx
|
|
* Alternate descriptor index number to configure (numbered from 0).
|
|
*
|
|
* @param[in] cfg
|
|
* 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 selected entry in 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 */
|
|
/* done when setting up the primary descriptor, ie when activating. */
|
|
(0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) |
|
|
(cycleCtrl << _DMA_CTRL_CYCLE_CTRL_SHIFT);
|
|
}
|
|
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Check if DMA channel is enabled.
|
|
*
|
|
* @details
|
|
* The DMA channel is disabled when the DMA controller has finished a DMA
|
|
* cycle.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to check.
|
|
*
|
|
* @return
|
|
* true if 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
|
|
* Initializes DMA controller.
|
|
*
|
|
* @details
|
|
* This function will reset and prepare the DMA controller for use. Although
|
|
* it may be used several times, it is normally only used during system
|
|
* init. If reused during normal operation, notice that any ongoing DMA
|
|
* transfers will be aborted. When completed, the DMA controller is in
|
|
* an enabled state.
|
|
*
|
|
* @note
|
|
* Must be invoked before using the DMA controller.
|
|
*
|
|
* @param[in] init
|
|
* Pointer to a structure containing DMA init information.
|
|
******************************************************************************/
|
|
void DMA_Init(DMA_Init_TypeDef *init)
|
|
{
|
|
EFM_ASSERT(init);
|
|
|
|
/* Make sure control block is properly aligned */
|
|
EFM_ASSERT(!((uint32_t)(init->controlBlock) & (256 - 1)));
|
|
|
|
/* Make sure DMA clock is enabled prior to accessing DMA module */
|
|
CMU_ClockEnable(cmuClock_DMA, true);
|
|
|
|
/* Make sure DMA controller is set to a known reset state */
|
|
DMA_Reset();
|
|
|
|
/* Clear/enable DMA interrupts */
|
|
NVIC_ClearPendingIRQ(DMA_IRQn);
|
|
NVIC_EnableIRQ(DMA_IRQn);
|
|
|
|
/* Enable bus error interrupt */
|
|
DMA->IEN = DMA_IEN_ERR;
|
|
|
|
/* Set pointer to control block, notice that this ptr 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 primary and alternate descriptors, when completing use of a
|
|
* descriptor. While the other descriptor is in use by the DMA controller,
|
|
* the SW should refresh the completed descriptor. This is typically done from
|
|
* the callback defined for the ping-pong cycle.
|
|
*
|
|
* @param[in] channel
|
|
* DMA channel to refresh ping-pong descriptor for.
|
|
*
|
|
* @param[in] primary
|
|
* @li true - refresh primary descriptor
|
|
* @li false - refresh 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. Please
|
|
* refer to the reference manual for further details on burst usage.
|
|
*
|
|
* @param[in] dst
|
|
* Address to start location to transfer data to. If NULL, leave setting in
|
|
* descriptor as is.
|
|
*
|
|
* @param[in] src
|
|
* Address to start location to transfer data from. If NULL, leave setting in
|
|
* descriptor as is.
|
|
*
|
|
* @param[in] nMinus1
|
|
* 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 shall stop @b after completing use
|
|
* of this descriptor.
|
|
******************************************************************************/
|
|
void DMA_RefreshPingPong(unsigned int channel,
|
|
bool primary,
|
|
bool useBurst,
|
|
void *dst,
|
|
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 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 = 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
|
|
* Notice 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 EM_Library) */
|