blob: 3cf6a2ed448fd213f293e6323b1fd9e71830f93b [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 "board.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 sucessful, 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)) )
{
return XDMAD_ALLOC_FAILED;
}
}
/* dma transfer from memory to peripheral */
if ( bSrcID == XDMAD_TRANSFER_MEMORY )
{
if( (!XDMAIF_IsValidatedPeripherOnDma(bDstID)) )
{
return XDMAD_ALLOC_FAILED;
}
}
for (i = 0; i < pXdmad->numChannels; i ++)
{
if (( pXdmad->XdmaChannels[i].state == XDMAD_STATE_FREE ) || ( pXdmad->XdmaChannels[i].state == XDMAD_STATE_DONE ))
{
/* 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);
}
}
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;
assert( pXdmad != NULL ) ;
if (xDmad_Initialized) 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;
}
/**
* \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 sucessful, 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;
dwChannel = XDMAD_AllocateXdmacChannel( pXdmad, bSrcID, bDstID );
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 ) ;
switch ( pXdmad->XdmaChannels[iChannel].state )
{
case XDMAD_STATE_START:
case XDMAD_STATE_ALLOCATED:
return XDMAD_BUSY;
case XDMAD_STATE_DONE:
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 ( 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;
assert( pXdmad != NULL ) ;
Xdmac *pXdmac = pXdmad->pXdmacs;
if ( pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_FREE )
return XDMAD_ERROR;
else if ( pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_START )
return XDMAD_BUSY;
/* Clear dummy status */
XDMAC_GetGlobalChStatus( pXdmac );
XDMAC_GetGIsr (pXdmac);
/* 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, (uint32_t)-1);
XDMAC_DisableChannelIt (pXdmac, iChannel, (uint32_t)-1);
/* 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_GetChannelIsr( pXdmac, _iChannel);
if (xdmaChannelIntStatus & XDMAC_CIS_BIS) {
if((XDMAC_GetChannelItMask(pXdmac, _iChannel) & XDMAC_CIM_LIM) == 0 ) {
pCh->state = XDMAD_STATE_DONE ;
bExec = 1;
}
//printf("XDMAC_CIS_BIS\n\r");
}
if (xdmaChannelIntStatus & XDMAC_CIS_FIS) {
// printf("XDMAC_CIS_FIS\n\r");
}
if (xdmaChannelIntStatus & XDMAC_CIS_RBEIS) {
//printf("XDMAC_CIS_RBEIS\n\r");
}
if (xdmaChannelIntStatus & XDMAC_CIS_WBEIS) {
// printf("XDMAC_CIS_WBEIS\n\r");
}
if (xdmaChannelIntStatus & XDMAC_CIS_ROIS) {
// printf("XDMAC_CIS_ROIS\n\r");
}
if (xdmaChannelIntStatus & XDMAC_CIS_LIS) {
//printf("XDMAC_CIS_LIS\n\r");
pCh->state = XDMAD_STATE_DONE ;
bExec = 1;
}
if (xdmaChannelIntStatus & XDMAC_CIS_DIS )
{
pCh->state = XDMAD_STATE_DONE ;
bExec = 1;
}
}
/* Execute callback */
if (bExec && pCh->fCallback) {
//XDMAC_DisableGIt( pXdmac,1 << _iChannel);
pCh->fCallback(_iChannel, pCh->pArg);
}
}
} // if condtion
}
/**
* \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;
state = pXdmad->XdmaChannels[iChannel].state;
assert( pXdmad != NULL ) ;
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)
{
uint8_t iChannel = (dwChannel) & 0xFF;
Xdmac *pXdmac = pXdmad->pXdmacs;
XDMAC_GetGIsr(pXdmac);
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, (uint32_t)-1);
XDMAC_EnableChannelIt (pXdmac,iChannel, XDMAC_CIE_LIE );
}
/* LLI is disabled. */
else
{
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,
XDMAC_CIE_BIE |
XDMAC_CIE_DIE |
XDMAC_CIE_FIE |
XDMAC_CIE_RBIE |
XDMAC_CIE_WBIE |
XDMAC_CIE_ROIE);
}
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;
Xdmac *pXdmac = pXdmad->pXdmacs;
if ( pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_FREE )
{
printf("-E- XDMAD_STATE_FREE \n\r");
return XDMAD_ERROR;
}
else if ( pXdmad->XdmaChannels[iChannel].state == XDMAD_STATE_START )
{
printf("-E- XDMAD_STATE_START \n\r");
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,1 << 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;
Xdmac *pXdmac = pXdmad->pXdmacs;
pXdmad->XdmaChannels[_iChannel].state = XDMAD_STATE_ALLOCATED;
/* Disable channel */
XDMAC_DisableChannel(pXdmac, _iChannel);
/* Disable interrupts */
XDMAC_DisableChannelIt(pXdmac, _iChannel, (uint32_t)-1);
/* Clear pending status */
XDMAC_GetChannelIsr( pXdmac, _iChannel);
XDMAC_GetGlobalChStatus(pXdmac);
return XDMAD_OK;
}
/**@}*/