blob: 369868ceb01d425f3e2b3c05e51415b045281402 [file] [log] [blame]
/***************************************************************************//**
* @file em_ldma.c
* @brief Direct memory access (LDMA) 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.@n
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.@n
* 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_ldma.h"
#if defined(LDMA_PRESENT) && (LDMA_COUNT == 1)
#include <stddef.h>
#include "em_assert.h"
#include "em_bus.h"
#include "em_cmu.h"
#include "em_core.h"
/***************************************************************************//**
* @addtogroup emlib
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup LDMA
* @{
******************************************************************************/
#if defined(LDMA_IRQ_HANDLER_TEMPLATE)
/***************************************************************************//**
* @brief
* A template for an LDMA IRQ handler.
******************************************************************************/
void LDMA_IRQHandler(void)
{
uint32_t ch;
/* Get all pending and enabled interrupts. */
uint32_t pending = LDMA_IntGetEnabled();
/* Loop on an LDMA error to enable debugging. */
while (pending & LDMA_IF_ERROR) {
}
/* Iterate over all LDMA channels. */
for (ch = 0; ch < DMA_CHAN_COUNT; ch++) {
uint32_t mask = 0x1 << ch;
if (pending & mask) {
/* Clear the interrupt flag. */
LDMA->IFC = mask;
/* Perform more actions here, execute callbacks, and so on. */
}
}
}
#endif
/***************************************************************************//**
* @brief
* De-initialize the LDMA controller.
*
* LDMA interrupts are disabled and the LDMA clock is stopped.
******************************************************************************/
void LDMA_DeInit(void)
{
NVIC_DisableIRQ(LDMA_IRQn);
LDMA->IEN = 0;
#if defined(_LDMA_CHDIS_MASK)
LDMA->CHDIS = _LDMA_CHEN_MASK;
#else
LDMA->CHEN = 0;
#endif
#if defined(LDMA_EN_EN)
LDMA->EN = 0;
#endif
#if !defined(_SILICON_LABS_32B_SERIES_2)
CMU_ClockEnable(cmuClock_LDMA, false);
#endif
}
/***************************************************************************//**
* @brief
* Enable or disable an LDMA channel request.
*
* @details
* Use this function to enable or disable an LDMA channel request. This will
* prevent the LDMA from proceeding after its current transaction if disabled.
*
* @param[in] channel
* LDMA channel to enable or disable requests.
*
* @param[in] enable
* If 'true', the request will be enabled. If 'false', the request will be disabled.
******************************************************************************/
void LDMA_EnableChannelRequest(int ch, bool enable)
{
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
BUS_RegBitWrite(&LDMA->REQDIS, ch, !enable);
}
/***************************************************************************//**
* @brief
* Initialize the LDMA controller.
*
* @details
* This function will disable all the LDMA channels and enable the LDMA bus
* clock in the CMU. This function will also enable the LDMA IRQ in the NVIC
* and set the LDMA IRQ priority to a user-configurable priority. The LDMA
* interrupt priority is configured using the @ref LDMA_Init_t structure.
*
* @note
* Since this function enables the LDMA IRQ, always add a custom
* LDMA_IRQHandler to the application to handle any interrupts
* from LDMA.
*
* @param[in] init
* A pointer to the initialization structure used to configure the LDMA.
******************************************************************************/
void LDMA_Init(const LDMA_Init_t *init)
{
uint32_t ldmaCtrlVal = 0;
EFM_ASSERT(init != NULL);
EFM_ASSERT(!((init->ldmaInitCtrlNumFixed << _LDMA_CTRL_NUMFIXED_SHIFT)
& ~_LDMA_CTRL_NUMFIXED_MASK));
#if defined(_LDMA_CTRL_SYNCPRSCLREN_SHIFT) && defined (_LDMA_CTRL_SYNCPRSSETEN_SHIFT)
EFM_ASSERT(!((init->ldmaInitCtrlSyncPrsClrEn << _LDMA_CTRL_SYNCPRSCLREN_SHIFT)
& ~_LDMA_CTRL_SYNCPRSCLREN_MASK));
EFM_ASSERT(!((init->ldmaInitCtrlSyncPrsSetEn << _LDMA_CTRL_SYNCPRSSETEN_SHIFT)
& ~_LDMA_CTRL_SYNCPRSSETEN_MASK));
#endif
#if defined(_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
EFM_ASSERT(!((init->ldmaInitCtrlSyncPrsClrEn << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
& ~_LDMA_SYNCHWEN_SYNCCLREN_MASK));
EFM_ASSERT(!((init->ldmaInitCtrlSyncPrsSetEn << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
& ~_LDMA_SYNCHWEN_SYNCSETEN_MASK));
#endif
EFM_ASSERT(init->ldmaInitIrqPriority < (1 << __NVIC_PRIO_BITS));
#if defined(LDMA_EN_EN)
LDMA->EN = LDMA_EN_EN;
#endif
#if !defined(_SILICON_LABS_32B_SERIES_2)
CMU_ClockEnable(cmuClock_LDMA, true);
#endif
ldmaCtrlVal = (init->ldmaInitCtrlNumFixed << _LDMA_CTRL_NUMFIXED_SHIFT);
#if defined(_LDMA_CTRL_SYNCPRSCLREN_SHIFT) && defined (_LDMA_CTRL_SYNCPRSSETEN_SHIFT)
ldmaCtrlVal |= (init->ldmaInitCtrlSyncPrsClrEn << _LDMA_CTRL_SYNCPRSCLREN_SHIFT)
| (init->ldmaInitCtrlSyncPrsSetEn << _LDMA_CTRL_SYNCPRSSETEN_SHIFT);
#endif
LDMA->CTRL = ldmaCtrlVal;
#if defined(_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
LDMA->SYNCHWEN = (init->ldmaInitCtrlSyncPrsClrEn << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
| (init->ldmaInitCtrlSyncPrsSetEn << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT);
#endif
#if defined(_LDMA_CHDIS_MASK)
LDMA->CHDIS = _LDMA_CHEN_MASK;
#else
LDMA->CHEN = 0;
#endif
LDMA->DBGHALT = 0;
LDMA->REQDIS = 0;
/* Enable the LDMA error interrupt. */
#if defined (LDMA_IEN_ERRORIEN)
LDMA->IEN = LDMA_IEN_ERRORIEN;
#elif defined (LDMA_IEN_ERROR)
LDMA->IEN = LDMA_IEN_ERROR;
#else
#error "IEN register not defined!!!"
#endif
#if defined (LDMA_HAS_SET_CLEAR)
LDMA->IF_CLR = 0xFFFFFFFFU;
#else
LDMA->IFC = 0xFFFFFFFFU;
#endif
NVIC_ClearPendingIRQ(LDMA_IRQn);
/* Range is 0-7, where 0 is the highest priority. */
NVIC_SetPriority(LDMA_IRQn, init->ldmaInitIrqPriority);
NVIC_EnableIRQ(LDMA_IRQn);
}
/***************************************************************************//**
* @brief
* Start a DMA transfer.
*
* @param[in] ch
* A DMA channel.
*
* @param[in] transfer
* The initialization structure used to configure the transfer.
*
* @param[in] descriptor
* The transfer descriptor, which can be an array of descriptors linked together.
******************************************************************************/
void LDMA_StartTransfer(int ch,
const LDMA_TransferCfg_t *transfer,
const LDMA_Descriptor_t *descriptor)
{
#if !(defined (_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT))
uint32_t tmp;
#endif
CORE_DECLARE_IRQ_STATE;
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
EFM_ASSERT(transfer != NULL);
#if defined (_LDMAXBAR_CH_REQSEL_MASK)
EFM_ASSERT(!(transfer->ldmaReqSel & ~_LDMAXBAR_CH_REQSEL_MASK));
#elif defined (_LDMA_CH_REQSEL_MASK)
EFM_ASSERT(!(transfer->ldmaReqSel & ~_LDMA_CH_REQSEL_MASK));
#endif
#if defined (_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsClrOff << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
& ~_LDMA_SYNCHWEN_SYNCCLREN_MASK));
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsClrOn << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
& ~_LDMA_SYNCHWEN_SYNCCLREN_MASK));
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsSetOff << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
& ~_LDMA_SYNCHWEN_SYNCSETEN_MASK));
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsSetOn << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
& ~_LDMA_SYNCHWEN_SYNCSETEN_MASK));
#elif defined (_LDMA_CTRL_SYNCPRSCLREN_SHIFT) && defined (_LDMA_CTRL_SYNCPRSSETEN_SHIFT)
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsClrOff << _LDMA_CTRL_SYNCPRSCLREN_SHIFT)
& ~_LDMA_CTRL_SYNCPRSCLREN_MASK));
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsClrOn << _LDMA_CTRL_SYNCPRSCLREN_SHIFT)
& ~_LDMA_CTRL_SYNCPRSCLREN_MASK));
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsSetOff << _LDMA_CTRL_SYNCPRSSETEN_SHIFT)
& ~_LDMA_CTRL_SYNCPRSSETEN_MASK));
EFM_ASSERT(!((transfer->ldmaCtrlSyncPrsSetOn << _LDMA_CTRL_SYNCPRSSETEN_SHIFT)
& ~_LDMA_CTRL_SYNCPRSSETEN_MASK));
#endif
EFM_ASSERT(!((transfer->ldmaCfgArbSlots << _LDMA_CH_CFG_ARBSLOTS_SHIFT)
& ~_LDMA_CH_CFG_ARBSLOTS_MASK));
EFM_ASSERT(!((transfer->ldmaCfgSrcIncSign << _LDMA_CH_CFG_SRCINCSIGN_SHIFT)
& ~_LDMA_CH_CFG_SRCINCSIGN_MASK));
EFM_ASSERT(!((transfer->ldmaCfgDstIncSign << _LDMA_CH_CFG_DSTINCSIGN_SHIFT)
& ~_LDMA_CH_CFG_DSTINCSIGN_MASK));
EFM_ASSERT(!((transfer->ldmaLoopCnt << _LDMA_CH_LOOP_LOOPCNT_SHIFT)
& ~_LDMA_CH_LOOP_LOOPCNT_MASK));
#if defined(LDMAXBAR)
LDMAXBAR->CH[ch].REQSEL = transfer->ldmaReqSel;
#else
LDMA->CH[ch].REQSEL = transfer->ldmaReqSel;
#endif
LDMA->CH[ch].LOOP = (transfer->ldmaLoopCnt << _LDMA_CH_LOOP_LOOPCNT_SHIFT);
LDMA->CH[ch].CFG = (transfer->ldmaCfgArbSlots << _LDMA_CH_CFG_ARBSLOTS_SHIFT)
| (transfer->ldmaCfgSrcIncSign << _LDMA_CH_CFG_SRCINCSIGN_SHIFT)
| (transfer->ldmaCfgDstIncSign << _LDMA_CH_CFG_DSTINCSIGN_SHIFT);
/* Set the descriptor address. */
LDMA->CH[ch].LINK = (uint32_t)descriptor & _LDMA_CH_LINK_LINKADDR_MASK;
/* Clear the pending channel interrupt. */
#if defined (LDMA_HAS_SET_CLEAR)
LDMA->IF_CLR = chMask;
#else
LDMA->IFC = chMask;
#endif
/* A critical region. */
CORE_ENTER_ATOMIC();
/* Enable the channel interrupt. */
LDMA->IEN |= chMask;
if (transfer->ldmaReqDis) {
LDMA->REQDIS |= chMask;
}
if (transfer->ldmaDbgHalt) {
LDMA->DBGHALT |= chMask;
}
#if defined (_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
LDMA->SYNCHWEN_CLR =
((transfer->ldmaCtrlSyncPrsClrOff << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
| (transfer->ldmaCtrlSyncPrsSetOff << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT))
& _LDMA_SYNCHWEN_MASK;
LDMA->SYNCHWEN_SET =
((transfer->ldmaCtrlSyncPrsClrOn << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
| (transfer->ldmaCtrlSyncPrsSetOn << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT))
& _LDMA_SYNCHWEN_MASK;
#elif defined (_LDMA_CTRL_SYNCPRSCLREN_SHIFT) && defined (_LDMA_CTRL_SYNCPRSSETEN_SHIFT)
tmp = LDMA->CTRL;
if (transfer->ldmaCtrlSyncPrsClrOff) {
tmp &= ~_LDMA_CTRL_SYNCPRSCLREN_MASK
| (~transfer->ldmaCtrlSyncPrsClrOff << _LDMA_CTRL_SYNCPRSCLREN_SHIFT);
}
if (transfer->ldmaCtrlSyncPrsClrOn) {
tmp |= transfer->ldmaCtrlSyncPrsClrOn << _LDMA_CTRL_SYNCPRSCLREN_SHIFT;
}
if (transfer->ldmaCtrlSyncPrsSetOff) {
tmp &= ~_LDMA_CTRL_SYNCPRSSETEN_MASK
| (~transfer->ldmaCtrlSyncPrsSetOff << _LDMA_CTRL_SYNCPRSSETEN_SHIFT);
}
if (transfer->ldmaCtrlSyncPrsSetOn) {
tmp |= transfer->ldmaCtrlSyncPrsSetOn << _LDMA_CTRL_SYNCPRSSETEN_SHIFT;
}
LDMA->CTRL = tmp;
#else
#error "SYNC Set and SYNC Clear not defined"
#endif
BUS_RegMaskedClear(&LDMA->CHDONE, chMask); /* Clear the done flag. */
LDMA->LINKLOAD = chMask; /* Start a transfer by loading the descriptor. */
/* A critical region end. */
CORE_EXIT_ATOMIC();
}
/***************************************************************************//**
* @brief
* Stop a DMA transfer.
*
* @note
* The DMA will complete the current AHB burst transfer before stopping.
*
* @param[in] ch
* A DMA channel to stop.
******************************************************************************/
void LDMA_StopTransfer(int ch)
{
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
CORE_ATOMIC_SECTION(
LDMA->IEN &= ~chMask;
#if defined(_LDMA_CHDIS_MASK)
LDMA->CHDIS = chMask;
#else
BUS_RegMaskedClear(&LDMA->CHEN, chMask);
#endif
)
}
/***************************************************************************//**
* @brief
* Check if a DMA transfer has completed.
*
* @param[in] ch
* A DMA channel to check.
*
* @return
* True if transfer has completed, false if not.
******************************************************************************/
bool LDMA_TransferDone(int ch)
{
bool retVal = false;
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
CORE_ATOMIC_SECTION(
if (
#if defined(_LDMA_CHSTATUS_MASK)
((LDMA->CHSTATUS & chMask) == 0)
#else
((LDMA->CHEN & chMask) == 0)
#endif
&& ((LDMA->CHDONE & chMask) == chMask)) {
retVal = true;
}
)
return retVal;
}
/***************************************************************************//**
* @brief
* Get the number of items remaining in a transfer.
*
* @note
* This function does not take into account that a DMA transfer with
* a chain of linked transfers might be ongoing. It will only check the
* count for the current transfer.
*
* @param[in] ch
* The channel number of the transfer to check.
*
* @return
* A number of items remaining in the transfer.
******************************************************************************/
uint32_t LDMA_TransferRemainingCount(int ch)
{
uint32_t remaining, done, iflag;
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
CORE_ATOMIC_SECTION(
iflag = LDMA->IF;
done = LDMA->CHDONE;
remaining = LDMA->CH[ch].CTRL;
)
iflag &= chMask;
done &= chMask;
remaining = (remaining & _LDMA_CH_CTRL_XFERCNT_MASK)
>> _LDMA_CH_CTRL_XFERCNT_SHIFT;
if (done || ((remaining == 0) && iflag)) {
return 0;
}
return remaining + 1;
}
/** @} (end addtogroup LDMA) */
/** @} (end addtogroup emlib) */
#endif /* defined( LDMA_PRESENT ) && ( LDMA_COUNT == 1 ) */