blob: 14fd7313cf031900157bf7892f0bbd4315183026 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* ATMEL Microcontroller Software Support
* ----------------------------------------------------------------------------
* Copyright (c) 2011, 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 dmad_module
*
* \section DmaConfig Dma Configuration Usage
*
* To configure a DMA channel, the user has to follow these few steps :
* <ul>
* <li> Initialize a DMA driver instance by DMAD_Initialize().</li>
* <li> choose an available (disabled) channel using DMAD_AllocateChannel().</li>
* <li> After the DMAC selected channel has been programmed, DMAD_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> Configure DMA multi-buffer transfers using DMAD_PrepareMultiTransfer() to set up the chain of Linked List Items,
* single-buffer transfers using DMAD_PrepareSingleTransfer().</li>
* <li> Invoke DMAD_StartTransfer() to start DMA transfer, or DMAD_StopTransfer() to force stop DMA transfer.</li>
* <li> If picture-in-picture mode is enabled, DMAD_ConfigurePIP() helps to configure PIP mode.</li>
* <li> Once the buffer of data is transferred, DMAD_IsTransferDone() checks if DMA transfer is finished.</li>
* <li> DMAD_Handler() handles DMA interrupt, and invoking DMAD_SetCallback() if provided.</li>
* </ul>
*
* Related files:\n
* \ref dmad.h\n
* \ref dmad.c.\n
*/
/** \file */
/** \addtogroup dmad_functions
@{*/
/*----------------------------------------------------------------------------
* Includes
*----------------------------------------------------------------------------*/
#include "board.h"
#include <assert.h>
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
/**
* \brief Try to allocate a DMA channel for on given controller.
* \param pDmad Pointer to DMA driver instance.
* \param ucDmac DMA controller ID.
* \param ucSrcID Source peripheral ID, 0xFF for memory.
* \param ucDstID Destination peripheral ID, 0xFF for memory.
* \return Channel number if allocation sucessful, return
* DMA_ALLOC_FAILED if allocation failed.
*/
static uint32_t DMAD_AllocateDmacChannel( sDmad *pDmad,
uint8_t ucDmac,
uint8_t ucSrcID,
uint8_t ucDstID)
{
uint32_t i;
/* Can't support peripheral to peripheral */
if ((( ucSrcID != DMA_TRANSFER_MEMORY ) && ( ucDstID != DMA_TRANSFER_MEMORY )))
{
return DMA_ALLOC_FAILED;
}
/* dma transfer from peripheral to memory */
if ( ucDstID == DMA_TRANSFER_MEMORY)
{
if( (!DMAIF_IsValidatedPeripherOnDma(ucDmac, ucSrcID)) )
{
return DMA_ALLOC_FAILED;
}
}
/* dma transfer from memory to peripheral */
if ( ucSrcID == DMA_TRANSFER_MEMORY )
{
if( (!DMAIF_IsValidatedPeripherOnDma(ucDmac, ucDstID)) )
{
return DMA_ALLOC_FAILED;
}
}
for (i = 0; i < pDmad->ucNumChannels; i ++)
{
if ( pDmad->dmaChannels[ucDmac][i].ucState == DMAD_FREE )
{
/* Allocate the channel */
pDmad->dmaChannels[ucDmac][i].ucState = DMAD_IN_USE;
/* Get general informations */
pDmad->dmaChannels[ucDmac][i].ucSrcPeriphID = ucSrcID;
pDmad->dmaChannels[ucDmac][i].ucDstPeriphID = ucDstID;
pDmad->dmaChannels[ucDmac][i].ucSrcTxIfID =
DMAIF_GetChannelNumber(ucDmac, ucSrcID, 0);
pDmad->dmaChannels[ucDmac][i].ucSrcRxIfID =
DMAIF_GetChannelNumber(ucDmac, ucSrcID, 1);
pDmad->dmaChannels[ucDmac][i].ucDstTxIfID =
DMAIF_GetChannelNumber(ucDmac, ucDstID, 0);
pDmad->dmaChannels[ucDmac][i].ucDstRxIfID =
DMAIF_GetChannelNumber(ucDmac, ucDstID, 1);
return ((ucDmac << DMAC_CHANNEL_NUM)) | ((i) & 0xFF);
}
}
return DMA_ALLOC_FAILED;
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
/**
* \brief Initialize DMA driver instance.
* \param pDmad Pointer to DMA driver instance.
* \param ucPollingMode Polling DMA transfer:
* 1. Via DMAD_IsTransferDone(); or
* 2. Via DMAD_Handler().
*/
void DMAD_Initialize( sDmad *pDmad,
uint8_t ucPollingMode )
{
uint32_t i, j;
assert( pDmad != NULL ) ;
pDmad->pDmacs[0] = DMAC;
pDmad->ucPollingModes = ucPollingMode;
pDmad->ucNumControllers = DMAC_CONTROLLER_NUM;
pDmad->ucNumChannels = DMAC_CHANNEL_NUM;
for (i = 0; i < pDmad->ucNumControllers; i ++)
{
for (j = 0; j < pDmad->ucNumChannels; j ++)
{
pDmad->dmaChannels[i][j].fCallback = 0;
pDmad->dmaChannels[i][j].pArg = 0;
pDmad->dmaChannels[i][j].ucIrqOwner = 0;
pDmad->dmaChannels[i][j].ucSrcPeriphID = 0;
pDmad->dmaChannels[i][j].ucDstPeriphID = 0;
pDmad->dmaChannels[i][j].ucSrcTxIfID = 0;
pDmad->dmaChannels[i][j].ucSrcRxIfID = 0;
pDmad->dmaChannels[i][j].ucDstTxIfID = 0;
pDmad->dmaChannels[i][j].ucDstRxIfID = 0;
pDmad->dmaChannels[i][j].ucState = DMAD_FREE;
}
}
}
/**
* \brief DMA interrupt handler
* \param pDmad Pointer to DMA driver instance.
*/
void DMAD_Handler( sDmad *pDmad )
{
Dmac *pDmac;
sDmadChannel *pCh;
uint8_t ucController, ucChannel;
uint32_t dmaSr, chSr;
uint32_t dmaRc = DMAD_OK;
assert( pDmad != NULL ) ;
for (ucController = 0; ucController < pDmad->ucNumControllers; ucController ++)
{
pDmac = pDmad->pDmacs[ucController];
/* Check raw status but not masked one for polling mode support */
dmaSr = DMAC_GetStatus( pDmac );
if ((dmaSr & 0x00FFFFFF) == 0) continue;
chSr = DMAC_GetChannelStatus( pDmac );
// printf("iDma(%x,%x)\n\r", dmaSr, chSr);
for (ucChannel = 0; ucChannel < pDmad->ucNumChannels; ucChannel ++)
{
uint8_t bExec = 1;
pCh = &pDmad->dmaChannels[ucController][ucChannel];
/* Error */
if (dmaSr & (DMAC_EBCIDR_ERR0 << ucChannel))
{
DMAC_DisableChannel( pDmac, ucChannel );
if (pCh->ucState > DMAD_IN_USE) pCh->ucState = DMAD_STALL;
dmaRc = DMAD_ERROR;
}
/* Chained buffer complete */
else if (dmaSr & (DMAC_EBCIDR_CBTC0 << ucChannel))
{
DMAC_DisableChannel( pDmac, ucChannel );
if (pCh->ucState > DMAD_IN_USE) pCh->ucState = DMAD_IN_USE;
dmaRc = DMAD_OK;
}
/* Buffer complete */
else if (dmaSr & (DMAC_EBCIDR_BTC0 << ucChannel))
{
dmaRc = DMAD_PARTIAL_DONE;
/* Re-enable */
if ((chSr & (DMAC_CHSR_ENA0 << ucChannel)) == 0)
{
DMAC_EnableChannel( pDmac, ucChannel );
}
}
else
{
bExec = 0;
}
/* Execute callback */
if (bExec && pCh->fCallback)
{
pCh->fCallback(dmaRc, pCh->pArg);
}
}
}
}
/**
* \brief Allocate a DMA channel for upper layer.
* \param pDmad Pointer to DMA driver instance.
* \param ucSrcID Source peripheral ID, 0xFF for memory.
* \param ucDstID Destination peripheral ID, 0xFF for memory.
* \return Channel number if allocation sucessful, return
* DMA_ALLOC_FAILED if allocation failed.
*/
uint32_t DMAD_AllocateChannel( sDmad *pDmad,
uint8_t ucSrcID,
uint8_t ucDstID)
{
uint8_t ucController;
uint32_t dwChannel = DMA_ALLOC_FAILED;
for ( ucController = 0; ucController < pDmad->ucNumControllers; ucController ++)
{
dwChannel = DMAD_AllocateDmacChannel( pDmad, ucController,
ucSrcID, ucDstID );
if (dwChannel != DMA_ALLOC_FAILED)
break;
}
return dwChannel;
}
/**
* \brief Free the specified DMA channel.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << 8 | ChannelNumber.
*/
eDmadRC DMAD_FreeChannel( sDmad *pDmad, uint32_t dwChannel )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
switch ( pDmad->dmaChannels[ucController][ucChannel].ucState )
{
case DMAD_IN_XFR:
return DMAD_BUSY;
case DMAD_IN_USE:
pDmad->dmaChannels[ucController][ucChannel].ucState = DMAD_FREE;
break;
}
return DMAD_OK;
}
/**
* \brief Set the callback function for DMA channel transfer.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
* \param fCallback Pointer to callback function.
* \param pArg Pointer to optional argument for callback.
*/
eDmadRC DMAD_SetCallback( sDmad *pDmad, uint32_t dwChannel,
DmadTransferCallback fCallback, void* pArg )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
{printf("DMAD set call back error \n\r");
return DMAD_ERROR;}
else if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_IN_XFR )
return DMAD_BUSY;
pDmad->dmaChannels[ucController][ucChannel].fCallback = fCallback;
pDmad->dmaChannels[ucController][ucChannel].pArg = pArg;
return DMAD_OK;
}
/**
* \brief Enable clock of the DMA peripheral, Enable the dma peripheral,
* configure configuration register for DMA transfer.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
* \param dwCfg Configuration value.
*/
eDmadRC DMAD_PrepareChannel( sDmad *pDmad,
uint32_t dwChannel,
uint32_t dwCfg )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
Dmac *pDmac = pDmad->pDmacs[ucController];
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
return DMAD_ERROR;
else if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_IN_XFR )
return DMAD_BUSY;
DMAC_SetCFG( pDmac, ucChannel, dwCfg );
/* Enable clock of the DMA peripheral */
if (!PMC_IsPeriphEnabled( ID_DMAC ))
{
PMC_EnablePeripheral( ID_DMAC );
}
/* Enables the DMAC peripheral. */
DMAC_Enable( pDmac );
/* Disables DMAC interrupt for the given channel. */
DMAC_DisableIt (pDmac,
(DMAC_EBCIDR_BTC0 << ucChannel)
|(DMAC_EBCIDR_CBTC0 << ucChannel)
|(DMAC_EBCIDR_ERR0 << ucChannel) );
/* Disable the given dma channel. */
DMAC_DisableChannel( pDmac, ucChannel );
return DMAD_OK;
}
/**
* \brief Check if DMA transfer is finished.
* In polling mode DMAD_Handler() is polled.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
*/
eDmadRC DMAD_IsTransferDone( sDmad *pDmad, uint32_t dwChannel )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
return DMAD_ERROR;
else if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_IN_XFR )
{
if ( pDmad->ucPollingModes ) DMAD_Handler( pDmad );
return DMAD_BUSY;
}
return DMAD_OK;
}
/**
* \brief Start DMA transfer.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
*/
eDmadRC DMAD_StartTransfer( sDmad *pDmad, uint32_t dwChannel )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
Dmac *pDmac = pDmad->pDmacs[ucController];
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
{
printf("DMAD_START TRANSFER ERROR\n\r");
return DMAD_ERROR;
}
else if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_IN_XFR )
{
printf("DMAD_START TRANSFER BUSY\n\r");
return DMAD_BUSY;
}
/* Change ucState to transferring */
pDmad->dmaChannels[ucController][ucChannel].ucState = DMAD_IN_XFR;
DMAC_EnableChannel(pDmac, ucChannel);
if ( pDmad->ucPollingModes == 0 )
{
/* Monitor status in interrupt handler */
DMAC_EnableIt(pDmac, (DMAC_EBCIDR_BTC0 << ucChannel)
|(DMAC_EBCIDR_CBTC0 << ucChannel)
|(DMAC_EBCIDR_ERR0 << ucChannel) );
}
return DMAD_OK;
}
/**
* \brief Start DMA transfers on the same controller.
* \param pDmad Pointer to DMA driver instance.
* \param bDmac DMA Controller number.
* \param bmChannels Channels bitmap.
*/
eDmadRC DMAD_StartTransfers( sDmad *pDmad, uint8_t bDmac, uint32_t bmChannels )
{
uint32_t ucChannel;
uint32_t dwChs = 0, dwIts = 0;
assert( pDmad != NULL ) ;
Dmac *pDmac = pDmad->pDmacs[bDmac];
for (ucChannel = 0; ucChannel < pDmad->ucNumChannels; ucChannel ++)
{
uint32_t dwChBit = 1 << ucChannel;
/* Skipped channels */
if ( pDmad->dmaChannels[bDmac][ucChannel].ucState == DMAD_FREE )
continue;
else if ( pDmad->dmaChannels[bDmac][ucChannel].ucState == DMAD_IN_XFR )
continue;
/* Log to start bit map */
if (bmChannels & dwChBit)
{
dwChs |= dwChBit;
dwIts |= ( (DMAC_EBCIDR_BTC0 << ucChannel)
|(DMAC_EBCIDR_CBTC0 << ucChannel)
|(DMAC_EBCIDR_ERR0 << ucChannel) );
/* Change ucState */
pDmad->dmaChannels[bDmac][ucChannel].ucState = DMAD_IN_XFR;
}
}
DMAC_EnableChannels(pDmac, dwChs);
if ( pDmad->ucPollingModes == 0 )
{
/* Monitor status in interrupt handler */
DMAC_EnableIt( pDmac, dwIts );
}
return DMAD_OK;
}
/**
* \brief Stop DMA transfer.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
*/
eDmadRC DMAD_StopTransfer( sDmad *pDmad, uint32_t dwChannel )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
Dmac *pDmac = pDmad->pDmacs[ucController];
sDmadChannel *pCh = &pDmad->dmaChannels[ucController][ucChannel];
uint32_t to = 0x1000;
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
return DMAD_ERROR;
if ( pDmad->dmaChannels[ucController][ucChannel].ucState != DMAD_IN_XFR )
{
/* Disable channel */
DMAC_DisableChannel(pDmac, ucChannel);
/* Disable interrupts */
DMAC_DisableIt(pDmac, (DMAC_EBCIDR_BTC0 << ucChannel)
|(DMAC_EBCIDR_CBTC0 << ucChannel)
|(DMAC_EBCIDR_ERR0 << ucChannel) );
/* Clear pending status */
DMAC_GetChannelStatus(pDmac);
DMAC_GetStatus(pDmac);
return DMAD_OK;
}
/* Suspend */
DMAC_SuspendChannel(pDmac, ucChannel);
/* Poll empty */
for (;to; to --)
{
if (DMAC_GetChannelStatus(pDmac) & (DMAC_CHSR_EMPT0 << ucChannel))
{
break;
}
}
/* Disable channel */
DMAC_DisableChannel(pDmac, ucChannel);
/* Disable interrupts */
DMAC_DisableIt(pDmac, (DMAC_EBCIDR_BTC0 << ucChannel)
|(DMAC_EBCIDR_CBTC0 << ucChannel)
|(DMAC_EBCIDR_ERR0 << ucChannel) );
/* Clear pending status */
DMAC_GetChannelStatus(pDmac);
DMAC_GetStatus(pDmac);
/* Resume */
DMAC_RestoreChannel(pDmac, ucChannel);
/* Change ucState */
pDmad->dmaChannels[ucController][ucChannel].ucState = DMAD_IN_USE;
/* Invoke callback */
if (pCh->fCallback) pCh->fCallback(DMAD_CANCELED, pCh->pArg);
return DMAD_OK;
}
/**
* \brief Configure DMA for a single transfefr.
* \param pDmad Pointer to DMA driver instance.
* \param dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
* \param pXfrDesc Pointer to DMA Linked List.
*/
eDmadRC DMAD_PrepareSingleTransfer( sDmad *pDmad,
uint32_t dwChannel,
sDmaTransferDescriptor *pXfrDesc )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
Dmac *pDmac = pDmad->pDmacs[ucController];
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
return DMAD_ERROR;
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_IN_XFR )
return DMAD_BUSY;
DMAC_SetSourceAddr(pDmac, ucChannel, pXfrDesc->dwSrcAddr);
DMAC_SetDestinationAddr(pDmac, ucChannel, pXfrDesc->dwDstAddr);
DMAC_SetDescriptorAddr(pDmac, ucChannel, 0, 0);
DMAC_SetControlA(pDmac, ucChannel, pXfrDesc->dwCtrlA);
DMAC_SetControlB(pDmac, ucChannel, pXfrDesc->dwCtrlB);
return DMAD_OK;
}
/**
* \brief Configure DMA multi-buffer transfers using linked lists
* \param pDmad Pointer to DMA driver instance.
* \param _dwChannel ControllerNumber << DMAC_CHANNEL_NUM | ChannelNumber.
* \param pXfrDesc Pointer to DMA Linked List.
*/
eDmadRC DMAD_PrepareMultiTransfer( sDmad *pDmad,
uint32_t dwChannel,
sDmaTransferDescriptor *pXfrDesc )
{
uint8_t ucController = (dwChannel >> DMAC_CHANNEL_NUM);
uint8_t ucChannel = (dwChannel) & 0xFF;
assert( pDmad != NULL ) ;
Dmac *pDmac = pDmad->pDmacs[ucController];
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_FREE )
return DMAD_ERROR;
if ( pDmad->dmaChannels[ucController][ucChannel].ucState == DMAD_IN_XFR )
return DMAD_BUSY;
DMAC_SetDescriptorAddr( pDmac, ucChannel, (uint32_t)pXfrDesc, 0 );
DMAC_SetControlB( pDmac, ucChannel, 0);
return DMAD_OK;
}
/**@}*/