/* ----------------------------------------------------------------------------
 *         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;
}

/**@}*/

