blob: 2c70a996ec3870ddd149d4c7abe2f4e195ce87c9 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2014, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are 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"
#include <assert.h>
static uint8_t xDmad_Initialized = 0;
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
/**
* \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;
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
/**
* \brief Initialize xDMA driver instance.
* \param pXdmad Pointer to xDMA driver instance.
* \param bPollingMode Polling DMA transfer:
* 1. Via XDMAD_IsTransferDone(); or
* 2. Via XDMAD_Handler().
*/
void XDMAD_Initialize( sXdmad *pXdmad, uint8_t bPollingMode )
{
uint32_t j;
uint32_t volatile timer=0x7FF;
assert( pXdmad) ;
LockMutex(pXdmad->xdmaMutex, timer);
if (xDmad_Initialized) {
ReleaseMutex(pXdmad->xdmaMutex);
return;
}
pXdmad->pXdmacs = XDMAC;
pXdmad->pollingMode = bPollingMode;
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 = 0;
pXdmad->XdmaChannels[j].pArg = 0;
pXdmad->XdmaChannels[j].bIrqOwner = 0;
pXdmad->XdmaChannels[j].bSrcPeriphID = 0;
pXdmad->XdmaChannels[j].bDstPeriphID = 0;
pXdmad->XdmaChannels[j].bSrcTxIfID = 0;
pXdmad->XdmaChannels[j].bSrcRxIfID = 0;
pXdmad->XdmaChannels[j].bDstTxIfID = 0;
pXdmad->XdmaChannels[j].bDstRxIfID = 0;
pXdmad->XdmaChannels[j].state = XDMAD_STATE_FREE;
}
xDmad_Initialized = 1;
ReleaseMutex(pXdmad->xdmaMutex);
}
/**
* \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 pxDmad Pointer to DMA driver instance.
*/
void XDMAD_Handler( sXdmad *pDmad)
{
Xdmac *pXdmac;
sXdmadChannel *pCh;
uint32_t xdmaChannelIntStatus, xdmaGlobaIntStatus,xdmaGlobalChStatus;
uint8_t bExec = 0;
uint8_t _iChannel;
assert( pDmad != NULL ) ;
pXdmac = pDmad->pXdmacs;
xdmaGlobaIntStatus = XDMAC_GetGIsr(pXdmac);
if ((xdmaGlobaIntStatus & 0xFFFFFF) != 0) {
xdmaGlobalChStatus = XDMAC_GetGlobalChStatus(pXdmac);
for (_iChannel = 0; _iChannel < pDmad->numChannels; _iChannel ++) {
if (!(xdmaGlobaIntStatus & (1<<_iChannel))) continue;
pCh = &pDmad->XdmaChannels[_iChannel];
if ( pCh->state == XDMAD_STATE_FREE) return ;
if ((xdmaGlobalChStatus & ( XDMAC_GS_ST0 << _iChannel)) == 0) {
bExec = 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;
}
SCB_CleanInvalidateDCache();
} else {
SCB_CleanInvalidateDCache();
/* Block end interrupt for LLI dma mode */
if( XDMAC_GetChannelIsr( pXdmac, _iChannel) & XDMAC_CIS_BIS) {
/* Execute callback */
pCh->fCallback(_iChannel, pCh->pArg);
}
}
/* Execute callback */
if (bExec && pCh->fCallback) {
pCh->fCallback(_iChannel, pCh->pArg);
}
}
}
}
/**
* \brief Check if DMA transfer is finished.
* In polling mode XDMAD_Handler() is polled.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
*/
eXdmadRC XDMAD_IsTransferDone( sXdmad *pXdmad, uint32_t dwChannel )
{
uint8_t iChannel = (dwChannel) & 0xFF;
uint8_t state;
assert( pXdmad != NULL ) ;
if (iChannel >= pXdmad->numChannels)
return XDMAD_ERROR;
SCB_CleanInvalidateDCache();
state = pXdmad->XdmaChannels[iChannel].state;
if ( state == XDMAD_STATE_ALLOCATED ) return XDMAD_OK;
if ( state == XDMAD_STATE_FREE ) return XDMAD_ERROR;
else if ( state != XDMAD_STATE_DONE ) {
if(pXdmad->pollingMode) XDMAD_Handler( pXdmad);
return XDMAD_BUSY;
}
return XDMAD_OK;
}
/**
* \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);
if ( pXdmad->pollingMode == 0 ) {
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;
}
/**@}*/