blob: 9d651e800b41420dcf05cfed9c9a275aa03df6e8 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2015, 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 "peripherals/aic.h"
#include "peripherals/pmc.h"
#include "peripherals/xdmad.h"
#include <assert.h>
#include "compiler.h"
/*----------------------------------------------------------------------------
* Local definitions
*----------------------------------------------------------------------------*/
#define XDMAD_CHANNELS (XDMAC_CONTROLLERS * XDMAC_CHANNELS)
/** DMA state for channel */
enum {
XDMAD_STATE_FREE = 0, /**< Free channel */
XDMAD_STATE_ALLOCATED, /**< Allocated to some peripheral */
XDMAD_STATE_STARTED, /**< DMA started */
XDMAD_STATE_DONE, /**< DMA transfer done */
};
/** DMA driver channel */
struct _xdmad_channel
{
Xdmac *xdmac; /**< XDMAC instance */
uint32_t id; /**< Channel ID */
xdmad_callback_t callback; /**< Callback */
void *user_arg; /**< Callback argument */
uint8_t src_txif; /**< Source TX Interface ID */
uint8_t src_rxif; /**< Source RX Interface ID */
uint8_t dest_txif; /**< Destination TX Interface ID */
uint8_t dest_rxif; /**< Destination RX Interface ID */
volatile uint8_t state; /**< Channel State */
};
/** DMA driver instance */
struct _xdmad {
struct _xdmad_channel channels[XDMAD_CHANNELS];
bool polling;
uint8_t polling_timeout;
};
static struct _xdmad _xdmad;
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
static inline struct _xdmad_channel *_xdmad_channel(uint32_t controller, uint32_t channel)
{
return &_xdmad.channels[controller * XDMAC_CHANNELS + channel];
}
/**
* \brief xDMA interrupt handler
* \param pXdmad Pointer to DMA driver instance.
*/
static void xdmad_handler(void)
{
uint32_t cont;
for (cont= 0; cont< XDMAC_CONTROLLERS; cont++) {
uint32_t chan, gis, gcs;
Xdmac *xdmac = xdmac_get_instance(cont);
gis = xdmac_get_global_isr(xdmac);
if ((gis & 0xFFFF) == 0)
continue;
gcs = xdmac_get_global_channel_status(xdmac);
for (chan = 0; chan < XDMAC_CHANNELS; chan++) {
struct _xdmad_channel *channel;
bool exec = false;
if (!(gis & (1 << chan)))
continue;
channel = _xdmad_channel(cont, chan);
if (channel->state == XDMAD_STATE_FREE)
continue;
if (!(gcs & (1 << chan))) {
uint32_t cis = xdmac_get_channel_isr(xdmac, chan);
if (cis & XDMAC_CIS_BIS) {
if (!(xdmac_get_channel_it_mask(xdmac, chan) & XDMAC_CIM_LIM)) {
channel->state = XDMAD_STATE_DONE;
exec = 1;
}
}
if (cis & XDMAC_CIS_LIS) {
channel->state = XDMAD_STATE_DONE;
exec = 1;
}
if (cis & XDMAC_CIS_DIS) {
channel->state = XDMAD_STATE_DONE;
exec = 1;
}
}
/* Execute callback */
if (exec && channel->callback) {
channel->callback(channel, channel->user_arg);
}
}
}
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
void xdmad_initialize(bool polling)
{
uint32_t cont, chan;
_xdmad.polling = polling;
for (cont = 0; cont < XDMAC_CONTROLLERS; cont++) {
Xdmac* xdmac = xdmac_get_instance(cont);
for (chan = 0; chan < XDMAC_CHANNELS; chan++) {
xdmac_get_channel_isr(xdmac, chan);
struct _xdmad_channel *channel = _xdmad_channel(cont, chan);
channel->xdmac = xdmac;
channel->id = chan;
channel->callback = 0;
channel->user_arg = 0;
channel->src_txif = 0;
channel->src_rxif = 0;
channel->dest_txif = 0;
channel->dest_rxif = 0;
channel->state = XDMAD_STATE_FREE;
}
if (!polling) {
uint32_t pid = xdmac_get_periph_id(xdmac);
/* enable interrupts */
aic_set_source_vector(pid, xdmad_handler);
aic_enable(pid);
}
}
}
void xdmad_poll(void)
{
if (_xdmad.polling)
xdmad_handler();
}
struct _xdmad_channel *xdmad_allocate_channel(uint8_t src, uint8_t dest)
{
uint32_t i;
/* Reject peripheral to peripheral transfers */
if (src != XDMAD_PERIPH_MEMORY && dest != XDMAD_PERIPH_MEMORY) {
return NULL;
}
for (i = 0; i < XDMAD_CHANNELS; i++) {
struct _xdmad_channel *channel = &_xdmad.channels[i];
Xdmac *xdmac = channel->xdmac;
if (channel->state == XDMAD_STATE_FREE) {
/* Check if source peripheral matches this channel controller */
if (src != XDMAD_PERIPH_MEMORY)
if (!is_peripheral_on_xdma_controller(src, xdmac))
continue;
/* Check if destination peripheral matches this channel controller */
if (dest != XDMAD_PERIPH_MEMORY)
if (!is_peripheral_on_xdma_controller(dest, xdmac))
continue;
/* Allocate the channel */
channel->state = XDMAD_STATE_ALLOCATED;
channel->src_txif = get_peripheral_xdma_channel(src, xdmac, true);
channel->src_rxif = get_peripheral_xdma_channel(src, xdmac, false);
channel->dest_txif = get_peripheral_xdma_channel(dest, xdmac, true);
channel->dest_rxif = get_peripheral_xdma_channel(dest, xdmac, false);
return channel;
}
}
return NULL;
}
uint32_t xdmad_free_channel(struct _xdmad_channel *channel)
{
switch (channel->state) {
case XDMAD_STATE_STARTED:
return XDMAD_BUSY;
case XDMAD_STATE_ALLOCATED:
case XDMAD_STATE_DONE:
channel->state = XDMAD_STATE_FREE;
break;
}
return XDMAD_OK;
}
uint32_t xdmad_set_callback(struct _xdmad_channel *channel,
xdmad_callback_t callback, void *user_arg)
{
if (channel->state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
else if (channel->state == XDMAD_STATE_STARTED)
return XDMAD_BUSY;
channel->callback = callback;
channel->user_arg = user_arg;
return XDMAD_OK;
}
uint32_t xdmad_prepare_channel(struct _xdmad_channel *channel)
{
Xdmac *xdmac = channel->xdmac;
if (channel->state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
else if (channel->state == XDMAD_STATE_STARTED)
return XDMAD_BUSY;
/* Clear status */
xdmac_get_global_channel_status(xdmac);
xdmac_get_global_isr(xdmac);
/* Enable clock of the DMA peripheral */
pmc_enable_peripheral(xdmac_get_periph_id(xdmac));
/* Clear status */
xdmac_get_channel_isr(xdmac, channel->id);
/* Disables XDMAC interrupt for the given channel */
xdmac_disable_global_it(xdmac, -1);
xdmac_disable_channel_it(xdmac, channel->id, -1);
/* Disable the given dma channel */
xdmac_disable_channel(xdmac, channel->id);
xdmac_set_src_addr(xdmac, channel->id, 0);
xdmac_set_dest_addr(xdmac, channel->id, 0);
xdmac_set_block_control(xdmac, channel->id, 0);
xdmac_set_channel_config(xdmac, channel->id, XDMAC_CC_PROT_UNSEC);
xdmac_set_descriptor_addr(xdmac, channel->id, 0, 0);
xdmac_set_descriptor_control(xdmac, channel->id, 0);
return XDMAD_OK;
}
bool xdmad_is_transfer_done(struct _xdmad_channel *channel)
{
return channel->state != XDMAD_STATE_STARTED;
}
uint32_t xdmad_configure_transfer(struct _xdmad_channel *channel,
struct _xdmad_cfg *cfg,
uint32_t desc_cntrl,
void *desc_addr)
{
if (channel->state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
else if (channel->state == XDMAD_STATE_STARTED)
return XDMAD_BUSY;
Xdmac *xdmac = channel->xdmac;
if (cfg->cfg.bitfield.dsync == XDMAC_CC_DSYNC_PER2MEM) {
cfg->cfg.bitfield.perid = channel->src_rxif;
} else {
cfg->cfg.bitfield.perid = channel->dest_txif;
}
/* Clear status */
xdmac_get_global_isr(xdmac);
xdmac_get_channel_isr(xdmac, channel->id);
if ((desc_cntrl & XDMAC_CNDC_NDE) == XDMAC_CNDC_NDE_DSCR_FETCH_EN) {
/* Linked List is enabled */
if ((desc_cntrl & XDMAC_CNDC_NDVIEW_Msk)
== XDMAC_CNDC_NDVIEW_NDV0) {
xdmac_set_channel_config(xdmac, channel->id,
cfg->cfg.uint32_value);
xdmac_set_src_addr(xdmac, channel->id, cfg->src_addr);
xdmac_set_dest_addr(xdmac, channel->id, cfg->dest_addr);
}
else if ((desc_cntrl & XDMAC_CNDC_NDVIEW_Msk)
== XDMAC_CNDC_NDVIEW_NDV1) {
xdmac_set_channel_config(xdmac, channel->id,
cfg->cfg.uint32_value);
}
xdmac_set_descriptor_addr(xdmac, channel->id, desc_addr, 0);
xdmac_set_descriptor_control(xdmac, channel->id, desc_cntrl);
xdmac_disable_channel_it(xdmac, channel->id, -1);
xdmac_enable_channel_it(xdmac, channel->id, XDMAC_CIE_LIE);
} else {
/* Linked List is disabled. */
xdmac_set_src_addr(xdmac, channel->id, cfg->src_addr);
xdmac_set_dest_addr(xdmac, channel->id, cfg->dest_addr);
xdmac_set_microblock_control(xdmac, channel->id, cfg->ublock_size);
xdmac_set_block_control(xdmac, channel->id,
cfg->block_size > 1 ? cfg->block_size : 0);
xdmac_set_data_stride_mem_pattern(xdmac, channel->id,
cfg->data_stride);
xdmac_set_src_microblock_stride(xdmac, channel->id,
cfg->src_ublock_stride);
xdmac_set_dest_microblock_stride(xdmac, channel->id,
cfg->dest_ublock_stride);
xdmac_set_channel_config(xdmac, channel->id, cfg->cfg.uint32_value);
xdmac_set_descriptor_addr(xdmac, channel->id, 0, 0);
xdmac_set_descriptor_control(xdmac, channel->id, 0);
xdmac_enable_channel_it(xdmac, channel->id,
XDMAC_CIE_BIE | XDMAC_CIE_DIE |
XDMAC_CIE_FIE | XDMAC_CIE_RBIE |
XDMAC_CIE_WBIE | XDMAC_CIE_ROIE);
}
return XDMAD_OK;
}
uint32_t xdmad_start_transfer(struct _xdmad_channel *channel)
{
if (channel->state == XDMAD_STATE_FREE)
return XDMAD_ERROR;
else if (channel->state == XDMAD_STATE_STARTED)
return XDMAD_BUSY;
/* Change state to 'started' */
channel->state = XDMAD_STATE_STARTED;
/* Start DMA transfer */
xdmac_enable_channel(channel->xdmac, channel->id);
if (!_xdmad.polling) {
xdmac_enable_global_it(channel->xdmac, 1 << channel->id);
}
return XDMAD_OK;
}
uint32_t xdmad_stop_transfer(struct _xdmad_channel *channel)
{
Xdmac *xdmac = channel->xdmac;
/* Disable channel */
xdmac_disable_channel(xdmac, channel->id);
/* Disable interrupts */
xdmac_disable_channel_it(xdmac, channel->id, -1);
/* Clear pending status */
xdmac_get_channel_isr(xdmac, channel->id);
xdmac_get_global_channel_status(xdmac);
/* Change state to 'allocated' */
channel->state = XDMAD_STATE_ALLOCATED;
return XDMAD_OK;
}
/**@}*/