blob: f43eadb4cedf70d7360307136e263ca2067dd6f0 [file] [log] [blame]
/*******************************************************************************
* (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