/***************************************************************************//** * @file * @brief Direct memory access (DMA) module peripheral API ******************************************************************************* * # License * Copyright 2018 Silicon Laboratories Inc. www.silabs.com ******************************************************************************* * * SPDX-License-Identifier: Zlib * * The licensor of this software is Silicon Laboratories Inc. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * 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. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 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. * ******************************************************************************/ #include "em_dma.h" #if defined(DMA_PRESENT) #include "em_cmu.h" #include "sl_assert.h" #include "em_bus.h" /***************************************************************************//** * @addtogroup dma DMA - Direct Memory Access * @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) */ #endif /* defined( DMA_PRESENT ) */