/* ---------------------------------------------------------------------------- */
/* Atmel Microcontroller Software Support */
/* SAM Software Package License */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation */
/* */
/* All rights reserved. */
/* */
/* Redistribution and use in source and binary forms, with or without */
/* modification, are permitted provided that the following condition is met: */
/* */
/* - Redistributions of source code must retain the above copyright notice, */
/* this list of conditions and the disclaimer below. */
/* */
/* Atmel's name may not be used to endorse or promote products derived from */
/* this software without specific prior written permission. */
/* */
/* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR */
/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE */
/* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */
/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/* ---------------------------------------------------------------------------- */
/** \addtogroup xdmad_module
*
* \section Xdma xDma Configuration Usage
*
* To configure a XDMA channel, the user has to follow these few steps :
* <ul>
* <li> Initialize a XDMA driver instance by XDMAD_Initialize().</li>
* <li> choose an available (disabled) channel using XDMAD_AllocateChannel().</li>
* <li> After the XDMAC selected channel has been programmed,
* XDMAD_PrepareChannel() is to enable clock and dma peripheral of the DMA, and
* set Configuration register to set up the transfer type (memory or non-memory
* peripheral for source and destination) and flow control device.</li>
* <li> Invoke XDMAD_StartTransfer() to start DMA transfer or
* XDMAD_StopTransfer() to force stop DMA transfer.</li>
* <li> Once the buffer of data is transferred, XDMAD_IsTransferDone()
* checks if DMA transfer is finished.</li>
* <li> XDMAD_Handler() handles XDMA interrupt, and invoking XDMAD_SetCallback()
* if provided.</li>
* </ul>
*
* Related files:\n
* \ref xdmad.h\n
* \ref xdmad.c.\n
*/
/** \file */
/** \addtogroup dmad_functions
@{*/
/*----------------------------------------------------------------------------
* Includes
*----------------------------------------------------------------------------*/
#include "chip.h"
#ifdef __rtems__
#include "../../../utils/utility.h"
#include <rtems/irq-extension.h>
#include <rtems/sysinit.h>
#include <bsp/fatal.h>
#endif /* __rtems__ */
#include <assert.h>
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
static void XDMAD_Handler(void *arg);
/**
* \brief Try to allocate a DMA channel for on given controller.
* \param pDmad Pointer to DMA driver instance.
* \param bSrcID Source peripheral ID, 0xFF for memory.
* \param bDstID Destination peripheral ID, 0xFF for memory.
* \return Channel number if allocation successful, return
* DMAD_ALLOC_FAILED if allocation failed.
*/
static uint32_t XDMAD_AllocateXdmacChannel(sXdmad *pXdmad,
uint8_t bSrcID,
uint8_t bDstID)
{
uint32_t i;
/* Can't support peripheral to peripheral */
if (((bSrcID != XDMAD_TRANSFER_MEMORY)
&& (bDstID != XDMAD_TRANSFER_MEMORY)))
return XDMAD_ALLOC_FAILED;
/* dma transfer from peripheral to memory */
if (bDstID == XDMAD_TRANSFER_MEMORY) {
if ((!XDMAIF_IsValidatedPeripherOnDma(bSrcID))) {
TRACE_ERROR("%s:: Allocation failed", __FUNCTION__);
return XDMAD_ALLOC_FAILED;
}
}
/* dma transfer from memory to peripheral */
if (bSrcID == XDMAD_TRANSFER_MEMORY) {
if ((!XDMAIF_IsValidatedPeripherOnDma(bDstID))) {
TRACE_ERROR("%s:: Allocation failed", __FUNCTION__);
return XDMAD_ALLOC_FAILED;
}
}
for (i = 0; i < pXdmad->numChannels; i ++) {
if (pXdmad->XdmaChannels[i].state == XDMAD_STATE_FREE) {
/* Allocate the channel */
pXdmad->XdmaChannels[i].state = XDMAD_STATE_ALLOCATED;
/* Get general informations */
pXdmad->XdmaChannels[i].bSrcPeriphID = bSrcID;
pXdmad->XdmaChannels[i].bDstPeriphID = bDstID;
pXdmad->XdmaChannels[i].bSrcTxIfID =
XDMAIF_Get_ChannelNumber(bSrcID, 0);
pXdmad->XdmaChannels[i].bSrcRxIfID =
XDMAIF_Get_ChannelNumber(bSrcID, 1);
pXdmad->XdmaChannels[i].bDstTxIfID =
XDMAIF_Get_ChannelNumber(bDstID, 0);
pXdmad->XdmaChannels[i].bDstRxIfID =
XDMAIF_Get_ChannelNumber(bDstID, 1);
return ((i) & 0xFF);
}
}
TRACE_ERROR("%s:: Allocation failed, all channels are occupied", __FUNCTION__);
return XDMAD_ALLOC_FAILED;
}
void XDMAD_DoNothingCallback(uint32_t Channel, void *pArg)
{
/* Do nothing */
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
sXdmad XDMAD_Instance;
static void XDMAD_SysInitialize(void)
{
sXdmad *pXdmad;
uint32_t j;
rtems_status_code sc;
pXdmad = &XDMAD_Instance;
pXdmad->pXdmacs = XDMAC;
pXdmad->numControllers = XDMAC_CONTROLLER_NUM;
pXdmad->numChannels = (XDMAC_GTYPE_NB_CH(XDMAC_GetType(XDMAC)) + 1);
for (j = 0; j < pXdmad->numChannels; j ++) {
pXdmad->XdmaChannels[j].fCallback = XDMAD_DoNothingCallback;
}
sc = rtems_interrupt_handler_install(
ID_XDMAC,
"XDMA",
RTEMS_INTERRUPT_UNIQUE,
XDMAD_Handler,
pXdmad
);
if (sc != RTEMS_SUCCESSFUL) {
bsp_fatal(ATSAM_FATAL_XDMA_IRQ_INSTALL);
}
}
RTEMS_SYSINIT_ITEM(XDMAD_SysInitialize, RTEMS_SYSINIT_BSP_START,
RTEMS_SYSINIT_ORDER_LAST);
/**
* \brief Allocate a XDMA channel for upper layer.
* \param pXdmad Pointer to xDMA driver instance.
* \param bSrcID Source peripheral ID, 0xFF for memory.
* \param bDstID Destination peripheral ID, 0xFF for memory.
* \return Channel number if allocation successful, return
* XDMAD_ALLOC_FAILED if allocation failed.
*/
uint32_t XDMAD_AllocateChannel(sXdmad *pXdmad,
uint8_t bSrcID,
uint8_t bDstID)
{
uint32_t dwChannel = XDMAD_ALLOC_FAILED;
uint32_t volatile timer = 0x7FF;
LockMutex(pXdmad->xdmaMutex, timer);
dwChannel = XDMAD_AllocateXdmacChannel(pXdmad, bSrcID, bDstID);
ReleaseMutex(pXdmad->xdmaMutex);
return dwChannel;
}
/**
* \brief Free the specified xDMA channel.
* \param pXdmad Pointer to xDMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
*/
eXdmadRC XDMAD_FreeChannel(sXdmad *pXdmad,
uint32_t dwChannel)
{
uint8_t iChannel = (dwChannel) & 0xFF;
assert(pXdmad != NULL);
if (iChannel >= pXdmad->numChannels) return XDMAD_ERROR;
switch (pXdmad->XdmaChannels[iChannel].state) {
case XDMAD_STATE_ALLOCATED:
case XDMAD_STATE_START:
case XDMAD_STATE_IN_XFR:
return XDMAD_BUSY;
case XDMAD_STATE_DONE:
case XDMAD_STATE_HALTED:
pXdmad->XdmaChannels[iChannel].state = XDMAD_STATE_FREE;
break;
}
return XDMAD_OK;
}
/**
* \brief Set the callback function for xDMA channel transfer.
* \param pXdmad Pointer to xDMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
* \param fCallback Pointer to callback function.
* \param pArg Pointer to optional argument for callback.
*/
eXdmadRC XDMAD_SetCallback(sXdmad *pXdmad,
uint32_t dwChannel,
XdmadTransferCallback fCallback,
void *pArg)
{
uint8_t iChannel = (dwChannel) & 0xFF;
assert(pXdmad != NULL);
if (iChannel >= pXdmad->numChannels) return XDMAD_ERROR;
if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
else if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_START)
return XDMAD_BUSY;
pXdmad->XdmaChannels[iChannel].fCallback = fCallback;
pXdmad->XdmaChannels[iChannel].pArg = pArg;
return XDMAD_OK;
}
/**
* \brief Enable clock of the xDMA peripheral, Enable the dma peripheral,
* configure configuration register for xDMA transfer.
* \param pXdmad Pointer to xDMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
* \param dwCfg Configuration value.
*/
eXdmadRC XDMAD_PrepareChannel(sXdmad *pXdmad, uint32_t dwChannel)
{
uint8_t iChannel = (dwChannel) & 0xFF;
Xdmac *pXdmac = pXdmad->pXdmacs;
assert(pXdmad != NULL);
if (iChannel >= pXdmad->numChannels) return XDMAD_ERROR;
if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
else if ((pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_START)
|| (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_IN_XFR))
return XDMAD_BUSY;
/* Enable clock of the DMA peripheral */
if (!PMC_IsPeriphEnabled(ID_XDMAC))
PMC_EnablePeripheral(ID_XDMAC);
/* Clear dummy status */
XDMAC_GetChannelIsr(pXdmac, iChannel);
/* Disables XDMAC interrupt for the given channel. */
XDMAC_DisableGIt (pXdmac, iChannel);
XDMAC_DisableChannelIt (pXdmac, iChannel, 0xFF);
/* Disable the given dma channel. */
XDMAC_DisableChannel(pXdmac, iChannel);
XDMAC_SetSourceAddr(pXdmac, iChannel, 0);
XDMAC_SetDestinationAddr(pXdmac, iChannel, 0);
XDMAC_SetBlockControl(pXdmac, iChannel, 0);
XDMAC_SetChannelConfig(pXdmac, iChannel, 0x20);
XDMAC_SetDescriptorAddr(pXdmac, iChannel, 0, 0);
XDMAC_SetDescriptorControl(pXdmac, iChannel, 0);
return XDMAD_OK;
}
/**
* \brief xDMA interrupt handler
* \param arg Pointer to DMA driver instance.
*/
static void XDMAD_Handler(void *arg)
{
sXdmad *pDmad;
Xdmac *pXdmac;
sXdmadChannel *pCh;
uint32_t xdmaChannelIntStatus, xdmaGlobaIntStatus, xdmaGlobalChStatus;
uint8_t bExec;
uint8_t _iChannel;
pDmad = arg;
pXdmac = pDmad->pXdmacs;
xdmaGlobaIntStatus = XDMAC_GetGIsr(pXdmac) & 0xFFFFFF;
xdmaGlobalChStatus = XDMAC_GetGlobalChStatus(pXdmac);
while (xdmaGlobaIntStatus != 0) {
_iChannel = 31 - __builtin_clz(xdmaGlobaIntStatus);
xdmaGlobaIntStatus &= ~(UINT32_C(1) << _iChannel);
pCh = &pDmad->XdmaChannels[_iChannel];
bExec = 0;
if ((xdmaGlobalChStatus & (XDMAC_GS_ST0 << _iChannel)) == 0) {
xdmaChannelIntStatus = XDMAC_GetMaskChannelIsr(pXdmac, _iChannel);
if (xdmaChannelIntStatus & XDMAC_CIS_BIS) {
if ((XDMAC_GetChannelItMask(pXdmac, _iChannel) & XDMAC_CIM_LIM) == 0) {
pCh->state = XDMAD_STATE_DONE;
bExec = 1;
}
TRACE_DEBUG("XDMAC_CIS_BIS\n\r");
}
if (xdmaChannelIntStatus & XDMAC_CIS_FIS)
TRACE_DEBUG("XDMAC_CIS_FIS\n\r");
if (xdmaChannelIntStatus & XDMAC_CIS_RBEIS)
TRACE_DEBUG("XDMAC_CIS_RBEIS\n\r");
if (xdmaChannelIntStatus & XDMAC_CIS_WBEIS)
TRACE_DEBUG("XDMAC_CIS_WBEIS\n\r");
if (xdmaChannelIntStatus & XDMAC_CIS_ROIS)
TRACE_DEBUG("XDMAC_CIS_ROIS\n\r");
if (xdmaChannelIntStatus & XDMAC_CIS_LIS) {
TRACE_DEBUG("XDMAC_CIS_LIS\n\r");
pCh->state = XDMAD_STATE_DONE;
bExec = 1;
}
if (xdmaChannelIntStatus & XDMAC_CIS_DIS) {
pCh->state = XDMAD_STATE_DONE;
bExec = 1;
}
} else {
/* Block end interrupt for LLI dma mode */
if (XDMAC_GetChannelIsr(pXdmac, _iChannel) & XDMAC_CIS_BIS) {
bExec = 1;
}
}
/* Execute callback */
if (bExec)
pCh->fCallback(_iChannel, pCh->pArg);
}
}
/**
* \brief Configure DMA for a single transfer.
* \param pXdmad Pointer to xDMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
*/
eXdmadRC XDMAD_ConfigureTransfer(sXdmad *pXdmad,
uint32_t dwChannel,
sXdmadCfg *pXdmaParam,
uint32_t dwXdmaDescCfg,
uint32_t dwXdmaDescAddr,
uint32_t dwXdmaIntEn)
{
uint8_t iChannel = (dwChannel) & 0xFF;
assert(pXdmad != NULL);
if (iChannel >= pXdmad->numChannels)
return XDMAD_ERROR;
Xdmac *pXdmac = pXdmad->pXdmacs;
XDMAC_GetChannelIsr(pXdmac, iChannel);
if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_START)
return XDMAD_BUSY;
/* Linked List is enabled */
if ((dwXdmaDescCfg & XDMAC_CNDC_NDE) == XDMAC_CNDC_NDE_DSCR_FETCH_EN) {
if ((dwXdmaDescCfg & XDMAC_CNDC_NDVIEW_Msk) == XDMAC_CNDC_NDVIEW_NDV0) {
XDMAC_SetChannelConfig(pXdmac, iChannel, pXdmaParam->mbr_cfg);
XDMAC_SetSourceAddr(pXdmac, iChannel, pXdmaParam->mbr_sa);
XDMAC_SetDestinationAddr(pXdmac, iChannel, pXdmaParam->mbr_da);
}
if ((dwXdmaDescCfg & XDMAC_CNDC_NDVIEW_Msk) == XDMAC_CNDC_NDVIEW_NDV1)
XDMAC_SetChannelConfig(pXdmac, iChannel, pXdmaParam->mbr_cfg);
XDMAC_SetDescriptorAddr(pXdmac, iChannel, dwXdmaDescAddr, 0);
XDMAC_SetDescriptorControl(pXdmac, iChannel, dwXdmaDescCfg);
XDMAC_DisableChannelIt (pXdmac, iChannel, 0xFF);
XDMAC_EnableChannelIt (pXdmac, iChannel, dwXdmaIntEn);
} else {
/* LLI is disabled. */
XDMAC_SetSourceAddr(pXdmac, iChannel, pXdmaParam->mbr_sa);
XDMAC_SetDestinationAddr(pXdmac, iChannel, pXdmaParam->mbr_da);
XDMAC_SetMicroblockControl(pXdmac, iChannel, pXdmaParam->mbr_ubc);
XDMAC_SetBlockControl(pXdmac, iChannel, pXdmaParam->mbr_bc);
XDMAC_SetDataStride_MemPattern(pXdmac, iChannel, pXdmaParam->mbr_ds);
XDMAC_SetSourceMicroBlockStride(pXdmac, iChannel, pXdmaParam->mbr_sus);
XDMAC_SetDestinationMicroBlockStride(pXdmac, iChannel, pXdmaParam->mbr_dus);
XDMAC_SetChannelConfig(pXdmac, iChannel, pXdmaParam->mbr_cfg);
XDMAC_SetDescriptorAddr(pXdmac, iChannel, 0, 0);
XDMAC_SetDescriptorControl(pXdmac, iChannel, 0);
XDMAC_EnableChannelIt (pXdmac, iChannel, dwXdmaIntEn);
}
return XDMAD_OK;
}
/**
* \brief Start xDMA transfer.
* \param pXdmad Pointer to XDMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
*/
eXdmadRC XDMAD_StartTransfer(sXdmad *pXdmad, uint32_t dwChannel)
{
uint8_t iChannel = (dwChannel) & 0xFF;
assert(pXdmad != NULL);
if (iChannel >= pXdmad->numChannels) return XDMAD_ERROR;
Xdmac *pXdmac = pXdmad->pXdmacs;
if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_FREE) {
TRACE_ERROR("%s:: XDMAD_STATE_FREE \n\r", __FUNCTION__);
return XDMAD_ERROR;
} else if (pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_START) {
TRACE_ERROR("%s:: XDMAD_STATE_START \n\r", __FUNCTION__)
return XDMAD_BUSY;
}
/* Change state to transferring */
pXdmad->XdmaChannels[iChannel].state = XDMAD_STATE_START;
XDMAC_EnableChannel(pXdmac, iChannel);
XDMAC_EnableGIt(pXdmac, iChannel);
return XDMAD_OK;
}
/**
* \brief Stop DMA transfer.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
*/
eXdmadRC XDMAD_StopTransfer(sXdmad *pXdmad, uint32_t dwChannel)
{
uint8_t _iChannel = (dwChannel) & 0xFF;
assert(pXdmad != NULL);
if (_iChannel >= pXdmad->numChannels) return XDMAD_ERROR;
Xdmac *pXdmac = pXdmad->pXdmacs;
pXdmad->XdmaChannels[_iChannel].state = XDMAD_STATE_HALTED;
/* Disable channel */
XDMAC_DisableChannel(pXdmac, _iChannel);
/* Disable interrupts */
XDMAC_DisableChannelIt(pXdmac, _iChannel, 0xFF);
/* Clear pending status */
XDMAC_GetChannelIsr(pXdmac, _iChannel);
XDMAC_GetGlobalChStatus(pXdmac);
return XDMAD_OK;
}
/**@}*/