/*******************************************************************************
 * (c) Copyright 2010-2013 Microsemi SoC Products Group.  All rights reserved.
 * 
 * SmartFusion2 MSS HPDMA bare metal driver implementation.
 *
 * SVN $Revision: 5148 $
 * SVN $Date: 2013-02-27 16:42:01 +0000 (Wed, 27 Feb 2013) $
 */
#include "mss_hpdma.h"

/*==============================================================================
 * Design Notes
 *------------------------------------------------------------------------------
  This SmartFusion2 MSS HPDMA driver only makes use of the first HPDMA
  descriptor. The rationale for this choice is that the pause and clear bits of
  each individual descriptor have undocumented side effects to the other
  descriptors in the HPDMA hardware block. This prevents independent operations
  of the descriptors in particular when it comes to error handling where error
  recovery requires clearing a descriptor. An error on one descriptor would
  result in aborting transfers on the other descriptors. Likewise, it is not
  possible to pause individual descriptors. Setting the pause bit in one
  descriptor results in all descriptors being paused.
 */

#ifdef __cplusplus
extern "C" {
#endif 

/*-------------------------------------------------------------------------*//**
 * Constants
 */
#define HPDMA_ENABLE    1u
#define HPDMA_DISABLE   0u
#define NULL_HANDLER    ((mss_hpdma_handler_t)0)

/* 64 K DMA Transfer */
#define MAX_SIZE_PER_DMA_XFR   0x10000u

#define DESC0       0u

/*-------------------------------------------------------------------------*//**
 * Mask Constants
 */
#define HPDMA_SOFT_RESET            0x00020000u
#define HPDMA_XFR_SIZE_MASK         0xFFFF0000u

#define HPDMACR_XFR_CMP_INT_MASK    0x00100000u
#define HPDMACR_XFR_ERR_INT_MASK    0x00200000u
#define HPDMACR_NON_WORD_INT_MASK   0x00400000u

#define HPDMACR_ALL_IRQ_ENABLE_MASK (HPDMACR_XFR_CMP_INT_MASK | HPDMACR_XFR_ERR_INT_MASK | HPDMACR_NON_WORD_INT_MASK)

#define HPDMAICR_CLEAR_ALL_IRQ      0x000000FFu

#define HPDMAEDR_DCP_ERR_MASK           0x00000100u

#define HPDMAEDR_NON_WORD_ERR_MASK      0x00001000u

/*-------------------------------------------------------------------------*//**
  Current transfer state information.
 */
typedef struct hpdma_xfer_info
{
    hpdma_status_t          state;
    mss_hpdma_handler_t     completion_handler;
    uint32_t                src_addr;
    uint32_t                des_addr;
    uint32_t                xfr_size;
    uint8_t                 xfr_dir;
} hpdma_xfer_info_t;

/*-------------------------------------------------------------------------*//**
  Current transfer state information.
 */
static hpdma_xfer_info_t g_transfer;

/*-------------------------------------------------------------------------*//**
  Interrupt service routines function prototypes.
  The name of these interrupt service routines must match the name of the ISRs
  defined in startup_m2sxxx.s distributed as part of the SmartFusion2 CMSIS.
 */
#if defined(__GNUC__)
__attribute__((__interrupt__)) void HPDMA_Complete_IRQHandler(void);
#else
void HPDMA_Complete_IRQHandler(void);
#endif 

#if defined(__GNUC__)
__attribute__((__interrupt__)) void HPDMA_Error_IRQHandler(void);
#else
void HPDMA_Error_IRQHandler(void);
#endif 

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
void
MSS_HPDMA_init
(
    void
)
{
    /* Reset HPDMA block. */
    SYSREG->SOFT_RST_CR |= HPDMA_SOFT_RESET;

    /* Take HPDMA controller out of reset. */
    SYSREG->SOFT_RST_CR &= ~HPDMA_SOFT_RESET;

    /* Initialize data structures. */
    g_transfer.xfr_size = 0u;
    g_transfer.completion_handler = NULL_HANDLER;
    g_transfer.state = HPDMA_COMPLETED;
    
    /* Disable Interrupt. */
    HPDMA->Descriptor[DESC0].HPDMACR_REG &= ~HPDMACR_ALL_IRQ_ENABLE_MASK;

    /* Clear any previously pended MSS HPDMA interrupt. */
    NVIC_ClearPendingIRQ(HPDMA_Error_IRQn);
    NVIC_ClearPendingIRQ(HPDMA_Complete_IRQn);
    
    /* Clear all interrupts. */
    HPDMA->HPDMAICR_REG = HPDMAICR_CLEAR_ALL_IRQ;
}

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
void
MSS_HPDMA_start
(
    uint32_t src_addr,
    uint32_t dest_addr,
    uint32_t transfer_size,
    uint8_t transfer_dir
)
{
    /* Pause transfer. */
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_PAUSE = HPDMA_ENABLE;
    
    /*
     * Disable all interrupts for the current descriptor. Treat this function as a
     * critical section. Pausing the descriptor transfer is not sufficient in case
     * of non-aligned errors.
     */
    HPDMA->Descriptor[DESC0].HPDMACR_REG &= ~HPDMACR_ALL_IRQ_ENABLE_MASK;
    
    /* Set descriptor transfer direction */
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_XFR_DIR = transfer_dir;

    /* Store Source and destination Address */
    HPDMA->Descriptor[DESC0].HPDMASAR_REG = src_addr;
    HPDMA->Descriptor[DESC0].HPDMADAR_REG = dest_addr;

    /* Set the transfer size to 64K */
    HPDMA->Descriptor[DESC0].HPDMACR_REG &= HPDMA_XFR_SIZE_MASK;

    g_transfer.state = HPDMA_IN_PROGRESS;
    if(transfer_size <= MAX_SIZE_PER_DMA_XFR)
    {
        /* Set descriptor transfer size */
        HPDMA->Descriptor[DESC0].HPDMACR_REG |= (uint16_t)transfer_size;
        g_transfer.xfr_size = 0u;
    }
    else
    {
        g_transfer.xfr_size = transfer_size - MAX_SIZE_PER_DMA_XFR;
        g_transfer.des_addr = dest_addr + MAX_SIZE_PER_DMA_XFR;
        g_transfer.src_addr = src_addr + MAX_SIZE_PER_DMA_XFR;
        g_transfer.xfr_dir = transfer_dir;
    }

    /* Enable interrupts for the requested descriptor. */
    HPDMA->Descriptor[DESC0].HPDMACR_REG |= HPDMACR_ALL_IRQ_ENABLE_MASK;
    NVIC_EnableIRQ(HPDMA_Complete_IRQn);
    NVIC_EnableIRQ(HPDMA_Error_IRQn);
    
    /* Set valid descriptor to start transfer */
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_VALID = HPDMA_ENABLE;

    /* Start transfer */
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_PAUSE = HPDMA_DISABLE;
}

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
void
MSS_HPDMA_pause
(
    void
)
{
    /* Pause transfer */
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_PAUSE = HPDMA_ENABLE;
}

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
void
MSS_HPDMA_resume
(
    void
)
{
    /* Resume transfer */
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_PAUSE = HPDMA_DISABLE;
}

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
void
MSS_HPDMA_abort
(
    void
)
{
    HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_CLR = HPDMA_ENABLE;
}

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
hpdma_status_t
MSS_HPDMA_get_transfer_status
(
    void
)
{
    return g_transfer.state;
}

/*-------------------------------------------------------------------------*//**
 * See "mss_hpdma.h" for details of how to use this function.
 */
void MSS_HPDMA_set_handler
(
    mss_hpdma_handler_t handler
)
{
    if(handler == NULL_HANDLER)
    {
        g_transfer.completion_handler = NULL_HANDLER;
    }
    else
    {
        g_transfer.completion_handler = handler;
    }
}

/*------------------------------------------------------------------------------
  HPDMA Transfer complete service routine.
  This function will be called as a result of any descriptor transfer
  is completed by HPDMA controller .
 */
#if defined(__GNUC__)
__attribute__((__interrupt__)) void HPDMA_Complete_IRQHandler(void)
#else
void HPDMA_Complete_IRQHandler(void)
#endif 
{
    /* Clear interrupt */
    HPDMA_BITBAND->HPDMAICR_CLR_XFR_INT[DESC0] = HPDMA_ENABLE;

    if(g_transfer.xfr_size > 0u)
    {
        MSS_HPDMA_start(g_transfer.src_addr,
                        g_transfer.des_addr,
                        g_transfer.xfr_size,
                        g_transfer.xfr_dir);
    }
    else
    {
        g_transfer.state = HPDMA_COMPLETED;
        if(g_transfer.completion_handler != NULL_HANDLER)
        {
            (*(g_transfer.completion_handler))(HPDMA_COMPLETED);
        }
    }
}

/*------------------------------------------------------------------------------
  HPDMA Transfer Error service routine.
  This function will be called as a result of any descriptor transfer
  has an error condition or it there is non word transfer size error.
 */
#if defined(__GNUC__)
__attribute__((__interrupt__)) void HPDMA_Error_IRQHandler(void)
#else
void HPDMA_Error_IRQHandler(void)
#endif 
{
    uint32_t error_bits;

    /*
     * Handle source/destination errors.
     */
    error_bits = HPDMA->HPDMAEDR_REG & HPDMAEDR_DCP_ERR_MASK;
    if(error_bits != 0u)
    {
        if(g_transfer.completion_handler != NULL_HANDLER)
        {
            if(HPDMA_BITBAND->Descriptor[DESC0].HPDMASR_DCP_SERR)
            {
                g_transfer.state = HPDMA_SOURCE_ERROR;
                (*(g_transfer.completion_handler))(HPDMA_SOURCE_ERROR);
            }
            
            if(HPDMA_BITBAND->Descriptor[DESC0].HPDMASR_DCP_DERR)
            {
                g_transfer.state = HPDMA_DESTINATION_ERROR;
                (*(g_transfer.completion_handler))(HPDMA_DESTINATION_ERROR);
            }
        }
        /* Abort transfer. */
        HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_CLR = HPDMA_ENABLE;
        /* Clear interrupt. */
        HPDMA_BITBAND->HPDMAICR_CLR_XFR_INT[DESC0] = HPDMA_ENABLE;
    }
    
    /*
     * Handle non word aligned errors.
     */
    error_bits = HPDMA->HPDMAEDR_REG & HPDMAEDR_NON_WORD_ERR_MASK;
    if(error_bits != 0u)
    {
        g_transfer.state = HPDMA_WORD_ALIGN_ERROR;
        /* Call handler. */
        if(g_transfer.completion_handler != NULL_HANDLER)
        {
            (*(g_transfer.completion_handler))(HPDMA_WORD_ALIGN_ERROR);
        }
        /* Abort transfer. */
        HPDMA_BITBAND->Descriptor[DESC0].HPDMACR_DCP_CLR = HPDMA_ENABLE;
        /* Clear interrupt. */
        HPDMA_BITBAND->HPDMAICR_NON_WORD_INT[DESC0] = HPDMA_ENABLE;
    }
}

#ifdef __cplusplus
}
#endif

