* arch/arm/src/samd5e5/sam_dmac.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <arch/samd5e5/chip.h>
#include "arm_internal.h"
#include "sched/sched.h"
#include "sam_dmac.h"
#include "sam_periphclks.h"
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_SAMD5E5_DMAC
#ifndef CONFIG_ARCH_DMA
# warning "SAMD5E5 DMA enabled but CONFIG_ARCH_DMA disabled"
#endif
#ifndef CONFIG_SAMD5E5_DMAC_NDESC
# define CONFIG_SAMD5E5_DMAC_NDESC 0
#endif
* Private Types
****************************************************************************/
enum sam_dmadir_e
{
DMADIR_UNKOWN = 0,
* transfer yet */
DMADIR_TX,
DMADIR_RX
};
struct sam_dmach_s
{
bool dc_inuse;
uint8_t dc_chan;
uint8_t dc_dir;
uint32_t dc_txflags;
uint32_t dc_rxflags;
dma_callback_t dc_callback;
void *dc_arg;
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
struct dma_desc_s *dc_head;
struct dma_desc_s *dc_tail;
#endif
};
* Private Function Prototypes
****************************************************************************/
static void sam_dmaterminate(struct sam_dmach_s *dmach, int result);
static int sam_dmainterrupt(int irq, void *context, void *arg);
static struct dma_desc_s *sam_alloc_desc(struct sam_dmach_s *dmach);
static struct dma_desc_s *sam_append_desc(struct sam_dmach_s *dmach,
uint16_t btctrl, uint16_t btcnt,
uint32_t srcaddr, uint32_t dstaddr);
static void sam_free_desc(struct sam_dmach_s *dmach);
static size_t sam_maxtransfer(struct sam_dmach_s *dmach, uint32_t dmaflags);
static uint16_t sam_bytes2beats(struct sam_dmach_s *dmach, uint32_t dmaflags,
size_t nbytes);
static int sam_txbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
uint32_t maddr, uint32_t dmaflags, size_t nbytes);
static int sam_rxbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
uint32_t maddr, uint32_t dmaflagsr, size_t nbytes);
* Private Data
****************************************************************************/
static mutex_t g_chlock = NXMUTEX_INITIALIZER;
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
static sem_t g_dsem = SEM_INITIALIZER(CONFIG_SAMD5E5_DMAC_NDESC);
#endif
static struct sam_dmach_s g_dmach[SAMD5E5_NDMACHAN];
* descriptors causes TERR and FERR interrupts to be raised immediately after
* starting DMA.
*/
static struct dma_desc_s g_base_desc[SAMD5E5_NDMACHAN]
locate_data(".lpram"), aligned(16);
static struct dma_desc_s g_writeback_desc[SAMD5E5_NDMACHAN]
locate_data(".lpram"), aligned(16);
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
* Also positioned in LPRAM.
*/
static struct dma_desc_s g_dma_desc[CONFIG_SAMD5E5_DMAC_NDESC]
locate_data(".lpram"), aligned(16);
#endif
* Private Functions
****************************************************************************/
* Name: sam_dmaterminate
*
* Description:
* Terminate the DMA transfer and disable the DMA channel
*
****************************************************************************/
static void sam_dmaterminate(struct sam_dmach_s *dmach, int result)
{
irqstate_t flags;
int chan;
flags = enter_critical_section();
chan = dmach->dc_chan;
putreg8(0, SAM_DMAC_CHCTRLA(chan));
putreg8(DMAC_CHCTRLA_SWRST, SAM_DMAC_CHCTRLA(chan));
putreg8(1 << dmach->dc_chan, SAM_DMAC_CHINTENCLR(chan));
leave_critical_section(flags);
sam_free_desc(dmach);
if (dmach->dc_callback)
{
dmach->dc_callback((DMA_HANDLE)dmach, dmach->dc_arg, result);
}
dmach->dc_callback = NULL;
dmach->dc_arg = NULL;
dmach->dc_dir = DMADIR_UNKOWN;
leave_critical_section(flags);
}
* Name: sam_dmainterrupt
*
* Description:
* DMA interrupt handler
*
****************************************************************************/
static int sam_dmainterrupt(int irq, void *context, void *arg)
{
struct sam_dmach_s *dmach;
unsigned int chndx;
uint16_t intpend;
int chan;
while ((intpend = getreg16(SAM_DMAC_INTPEND)) != 0)
{
chndx = (intpend & DMAC_INTPEND_ID_MASK) >> DMAC_INTPEND_ID_SHIFT;
dmach = &g_dmach[chndx];
chan = dmach->dc_chan;
putreg8(DMAC_INT_ALL, SAM_DMAC_CHINTFLAG(chan));
if ((intpend & DMAC_INTPEND_TERR) != 0)
{
sam_dmaterminate(dmach, -EIO);
}
else if ((intpend & DMAC_INTPEND_TCMPL) != 0)
{
sam_dmaterminate(dmach, OK);
}
else if ((intpend & DMAC_INTPEND_TERR) != 0)
{
dmaerr("Invalid descriptor. Channel %d\n", chndx);
sam_dmaterminate(dmach, -EIO);
}
else if ((intpend & DMAC_INTPEND_SUSP) != 0)
{
dmaerr("Channel suspended. Channel %d\n", chndx);
}
}
return OK;
}
* Name: sam_alloc_desc
*
* Description:
* Allocate one DMA descriptor. If the base DMA descriptor list entry is
* unused, then that value structure will be returned. Otherwise, this
* function will search for a free descriptor in the g_desc[] list.
*
* NOTE: Descriptor list entries are freed by the DMA interrupt handler.
* However, since the setting/clearing of the 'in use' indication is atomic,
* no special actions need be performed. It would be a good thing to add
* logic to handle the case where all of the entries are exhausted and we
* could wait for some to be freed by the interrupt handler.
*
****************************************************************************/
static struct dma_desc_s *sam_alloc_desc(struct sam_dmach_s *dmach)
{
struct dma_desc_s *desc;
desc = &g_base_desc[dmach->dc_chan];
if (desc->srcaddr == 0)
{
desc->srcaddr = (uint32_t)-1;
return desc;
}
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
else
{
int i;
* then there will be at least one free descriptor in the table and
* it is ours.
*/
nxsem_wait_uninterruptible(&g_dsem);
* with srcaddr == 0. That srcaddr field is set to zero by the DMA
* transfer complete interrupt handler. The following should be safe
* because that is an atomic operation.
*/
for (i = 0; i < CONFIG_SAMD5E5_DMAC_NDESC; i++)
{
desc = &g_dma_desc[i];
if (desc->srcaddr == 0)
{
* the descriptor.
*/
desc->srcaddr = (uint32_t)-1;
* (for sam_free_desc). We have to do this because we cannot
* use the link in the base descriptor; it will be overwritten
* by the writeback.
*/
if (dmach->dc_head == NULL)
{
dmach->dc_head = desc;
}
return desc;
}
}
* search loop should always be successful.
*/
DEBUGPANIC();
}
#endif
return NULL;
}
* Name: sam_append_desc
*
* Description:
* Allocate and add one descriptor to the DMA channel's descriptor list.
*
****************************************************************************/
static struct dma_desc_s *sam_append_desc(struct sam_dmach_s *dmach,
uint16_t btctrl, uint16_t btcnt,
uint32_t srcaddr, uint32_t dstaddr)
{
struct dma_desc_s *desc;
* unused. Obviously setting it to zero would break that usage.
*/
DEBUGASSERT(srcaddr != 0);
desc = sam_alloc_desc(dmach);
if (desc != NULL)
{
desc->btctrl = btctrl;
desc->btcnt = btcnt;
desc->srcaddr = srcaddr;
desc->dstaddr = dstaddr;
desc->descaddr = 0;
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
if (dmach->dc_tail != NULL)
{
struct dma_desc_s *prev;
DEBUGASSERT(desc != g_base_desc[dmach->dc_chan]);
prev->descaddr = (uint32_t)desc;
}
else
#endif
{
DEBUGASSERT(desc == &g_base_desc[dmach->dc_chan]);
}
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
dmach->dc_tail = desc;
#endif
}
else
{
dmaerr("Failed to allocate descriptor\n");
}
return desc;
}
* Name: sam_free_desc
*
* Description:
* Free all descriptors in the DMA channel's descriptor list.
*
* NOTE: Called from the DMA interrupt handler.
*
****************************************************************************/
static void sam_free_desc(struct sam_dmach_s *dmach)
{
struct dma_desc_s *desc;
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
struct dma_desc_s *next;
#endif
desc = &g_base_desc[dmach->dc_chan];
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
next = dmach->dc_head;
dmach->dc_head = NULL;
dmach->dc_tail = NULL;
#endif
memset(desc, 0, sizeof(struct dma_desc_s));
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
* freeing them)
*/
while (next != NULL)
{
desc = next;
DEBUGASSERT(desc->srcaddr != 0);
next = (struct dma_desc_s *)desc->descaddr;
memset(desc, 0, sizeof(struct dma_desc_s));
nxsem_post(&g_dsem);
}
#endif
}
* Name: sam_maxtransfer
*
* Description:
* Maximum number of bytes that can be sent/received in one transfer
*
****************************************************************************/
static size_t sam_maxtransfer(struct sam_dmach_s *dmach, uint32_t dmaflags)
{
int beatsize;
beatsize = (dmaflags & DMACH_FLAG_BEATSIZE_MASK) >>
LPSRAM_BTCTRL_STEPSIZE_SHIFT;
return (size_t)UINT16_MAX << beatsize;
}
* Name: sam_bytes2beats
*
* Description:
* Convert a count of bytes into a count of beats
*
****************************************************************************/
static uint16_t sam_bytes2beats(struct sam_dmach_s *dmach, uint32_t dmaflags,
size_t nbytes)
{
size_t mask;
int beatsize;
size_t nbeats;
beatsize = (dmaflags & DMACH_FLAG_BEATSIZE_MASK) >>
LPSRAM_BTCTRL_STEPSIZE_SHIFT;
mask = (1 << beatsize) - 1;
nbeats = (nbytes + mask) >> beatsize;
DEBUGASSERT(nbeats <= UINT16_MAX);
return (uint16_t)nbeats;
}
* Name: sam_txbuffer
*
* Description:
* Configure DMA for transmit of one buffer (memory to peripheral). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers.
*
****************************************************************************/
static int sam_txbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
uint32_t maddr, uint32_t dmaflags, size_t nbytes)
{
uint16_t btctrl;
uint16_t btcnt;
uint16_t tmp;
DEBUGASSERT(dmach->dc_dir == DMADIR_UNKOWN || dmach->dc_dir == DMADIR_TX);
*
* This are fixed register selections:
*
* LPSRAM_BTCTRL_VALID - Descriptor is valid
* LPSRAM_BTCTRL_EVOSEL_DISABLE - No event output
* LPSRAM_BTCTRL_BLOCKACT_INT - Disable channel and generate interrupt
* when the last block transfer completes.
*
* Other settings come from the channel configuration:
*
* LPSRAM_BTCTRL_BEATSIZE - Determined by DMACH_FLAG_BEATSIZE
* LPSRAM_BTCTRL_SRCINC - Determined by DMACH_FLAG_MEM_INCREMENT
* LPSRAM_BTCTRL_DSTINC - Determined by DMACH_FLAG_PERIPH_INCREMENT
* LPSRAM_BTCTRL_STEPSEL - Determined by DMACH_FLAG_STEPSEL
* LPSRAM_BTCTRL_STEPSIZE - Determined by DMACH_FLAG_STEPSIZE
*/
btctrl = LPSRAM_BTCTRL_VALID | LPSRAM_BTCTRL_EVOSEL_DISABLE |
LPSRAM_BTCTRL_BLOCKACT_INT;
tmp = (dmaflags & DMACH_FLAG_BEATSIZE_MASK) >>
DMACH_FLAG_BEATSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_BEATSIZE_SHIFT;
* When increment is used, we have to adjust the address in the descriptor
* based on the beat count remaining in the block
*/
btcnt = sam_bytes2beats(dmach, dmaflags, nbytes);
if ((dmaflags & DMACH_FLAG_MEM_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_SRCINC;
maddr += nbytes;
}
if ((dmaflags & DMACH_FLAG_PERIPH_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_DSTINC;
paddr += nbytes;
}
if ((dmaflags & DMACH_FLAG_STEPSEL) == DMACH_FLAG_STEPSEL_PERIPH)
{
btctrl |= LPSRAM_BTCTRL_STEPSEL;
}
tmp = (dmaflags & DMACH_FLAG_STEPSIZE_MASK) >>
LPSRAM_BTCTRL_STEPSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_STEPSIZE_SHIFT;
if (!sam_append_desc(dmach, btctrl, btcnt, maddr, paddr))
{
return -ENOMEM;
}
dmach->dc_dir = DMADIR_TX;
return OK;
}
* Name: sam_rxbuffer
*
* Description:
* Configure DMA for receipt of one buffer (peripheral to memory). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers.
*
****************************************************************************/
static int sam_rxbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
uint32_t maddr, uint32_t dmaflags, size_t nbytes)
{
uint16_t btctrl;
uint16_t btcnt;
uint16_t tmp;
DEBUGASSERT(dmach->dc_dir == DMADIR_UNKOWN || dmach->dc_dir == DMADIR_RX);
*
* This are fixed register selections:
*
* LPSRAM_BTCTRL_VALID - Descriptor is valid
* LPSRAM_BTCTRL_EVOSEL_DISABLE - No event output
* LPSRAM_BTCTRL_BLOCKACT_INT - Disable channel and generate interrupt
* when the last block transfer completes.
*
* Other settings come from the channel configuration:
*
* LPSRAM_BTCTRL_BEATSIZE - Determined by DMACH_FLAG_BEATSIZE
* LPSRAM_BTCTRL_SRCINC - Determined by DMACH_FLAG_PERIPH_INCREMENT
* LPSRAM_BTCTRL_DSTINC - Determined by DMACH_FLAG_MEM_INCREMENT
* LPSRAM_BTCTRL_STEPSEL - Determined by DMACH_FLAG_STEPSEL
* LPSRAM_BTCTRL_STEPSIZE - Determined by DMACH_FLAG_STEPSIZE
*/
btctrl = LPSRAM_BTCTRL_VALID | LPSRAM_BTCTRL_EVOSEL_DISABLE |
LPSRAM_BTCTRL_BLOCKACT_INT;
tmp = (dmaflags & DMACH_FLAG_BEATSIZE_MASK) >>
DMACH_FLAG_BEATSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_BEATSIZE_SHIFT;
btcnt = sam_bytes2beats(dmach, dmaflags, nbytes);
if ((dmaflags & DMACH_FLAG_PERIPH_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_SRCINC;
paddr += nbytes;
}
if ((dmaflags & DMACH_FLAG_MEM_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_DSTINC;
maddr += nbytes;
}
if ((dmaflags & DMACH_FLAG_STEPSEL) == DMACH_FLAG_STEPSEL_MEM)
{
btctrl |= LPSRAM_BTCTRL_STEPSEL;
}
tmp = (dmaflags & DMACH_FLAG_STEPSIZE_MASK) >>
LPSRAM_BTCTRL_STEPSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_STEPSIZE_SHIFT;
if (!sam_append_desc(dmach, btctrl, btcnt, paddr, maddr))
{
return -ENOMEM;
}
dmach->dc_dir = DMADIR_RX;
return OK;
}
* Public Functions
****************************************************************************/
* Name: arm_dma_initialize
*
* Description:
* Initialize the DMA subsystem
*
* Returned Value:
* None
*
****************************************************************************/
void weak_function arm_dma_initialize(void)
{
dmainfo("Initialize DMAC\n");
int i;
for (i = 0; i < SAMD5E5_NDMACHAN; i++)
{
g_dmach[i].dc_chan = i;
}
* not in .bss).
*/
memset(g_base_desc, 0, sizeof(struct dma_desc_s)*SAMD5E5_NDMACHAN);
memset(g_writeback_desc, 0, sizeof(struct dma_desc_s)*SAMD5E5_NDMACHAN);
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
memset(g_dma_desc, 0, sizeof(struct dma_desc_s)*CONFIG_SAMD5E5_DMAC_NDESC);
#endif
sam_ahb_dmac_enableperiph();
putreg16(0, SAM_DMAC_CTRL);
putreg16(DMAC_CTRL_SWRST, SAM_DMAC_CTRL);
irq_attach(SAM_IRQ_DMACH0, sam_dmainterrupt, NULL);
irq_attach(SAM_IRQ_DMACH1, sam_dmainterrupt, NULL);
irq_attach(SAM_IRQ_DMACH2, sam_dmainterrupt, NULL);
irq_attach(SAM_IRQ_DMACH3, sam_dmainterrupt, NULL);
irq_attach(SAM_IRQ_DMACH4_31, sam_dmainterrupt, NULL);
* written when the DMAC is disabled.
*/
putreg32((uint32_t)g_base_desc, SAM_DMAC_BASEADDR);
putreg32((uint32_t)g_writeback_desc, SAM_DMAC_WRBADDR);
#warning Missing logic
putreg16(DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0 | DMAC_CTRL_LVLEN1 |
DMAC_CTRL_LVLEN2, SAM_DMAC_CTRL);
up_enable_irq(SAM_IRQ_DMACH0);
up_enable_irq(SAM_IRQ_DMACH1);
up_enable_irq(SAM_IRQ_DMACH2);
up_enable_irq(SAM_IRQ_DMACH3);
up_enable_irq(SAM_IRQ_DMACH4_31);
}
* Name: sam_dmachannel
*
* Description:
* Allocate a DMA channel. This function sets aside a DMA channel and
* gives the caller exclusive access to the DMA channel.
*
* The naming convention in all of the DMA interfaces is that one side is
* the 'peripheral' and the other is 'memory'. However, the interface
* could still be used if, for example, both sides were memory although
* the naming would be awkward.
*
* Returned Value:
* If a DMA channel if the required FIFO size is available, this function
* returns a non-NULL, void* DMA channel handle. NULL is returned on any
* failure.
*
****************************************************************************/
DMA_HANDLE sam_dmachannel(uint32_t txflags, uint32_t rxflags)
{
struct sam_dmach_s *dmach;
irqstate_t flags;
unsigned int chndx;
int ret;
dmach = NULL;
ret = nxmutex_lock(&g_chlock);
if (ret < 0)
{
return NULL;
}
for (chndx = 0; chndx < SAMD5E5_NDMACHAN; chndx++)
{
struct sam_dmach_s *candidate = &g_dmach[chndx];
if (!candidate->dc_inuse)
{
dmach = candidate;
dmach->dc_inuse = true;
dmach->dc_txflags = txflags;
dmach->dc_rxflags = rxflags;
flags = enter_critical_section();
putreg8(0, SAM_DMAC_CHCTRLA(chndx));
putreg8(DMAC_CHCTRLA_SWRST, SAM_DMAC_CHCTRLA(chndx));
putreg8(1 << chndx, SAM_DMAC_CHINTENCLR(chndx));
leave_critical_section(flags);
break;
}
}
nxmutex_unlock(&g_chlock);
dmainfo("chflags: %08x returning dmach: %p\n", (int)chflags, dmach);
return (DMA_HANDLE)dmach;
}
* Name: sam_dmaconfig
*
* Description:
* There are two channel usage models: (1) The channel is allocated and
* configured in one step. This is the typical case where a DMA channel
* performs a constant role. The alternative is (2) where the DMA channel
* is reconfigured on the fly. In this case, the chflags provided to
* sam_dmachannel are not usedand sam_dmaconfig() is called before each DMA
* to configure the DMA channel appropriately.
*
* Returned Value:
* None
*
****************************************************************************/
void sam_dmaconfig(DMA_HANDLE handle, uint32_t txflags, uint32_t rxflags)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
dmainfo("txflags: %08lx rxflags: %08lx\n",
(unsigned long)txflags, (unsigned long)rxflags);
dmach->dc_txflags = txflags;
dmach->dc_rxflags = rxflags;
}
* Name: sam_dmafree
*
* Description:
* Release a DMA channel. NOTE: The 'handle' used in this argument must
* NEVER be used again until sam_dmachannel() is called again to re-gain
* a valid handle.
*
* Returned Value:
* None
*
****************************************************************************/
void sam_dmafree(DMA_HANDLE handle)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
dmainfo("dmach: %p\n", dmach);
DEBUGASSERT((dmach != NULL) && (dmach->dc_inuse));
* operation and so should be safe.
*/
dmach->dc_txflags = 0;
dmach->dc_rxflags = 0;
dmach->dc_inuse = false;
}
* Name: sam_dmatxsetup
*
* Description:
* Configure DMA for transmit of one buffer (memory to peripheral). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers. Calls to sam_dmatxsetup() and sam_dmarxsetup()
* must not be intermixed on the same transfer, however.
*
****************************************************************************/
int sam_dmatxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr,
size_t nbytes)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
ssize_t remaining = (ssize_t)nbytes;
size_t maxtransfer;
int ret = OK;
dmainfo("dmach: %p paddr: %08x maddr: %08x nbytes: %d\n",
dmach, (int)paddr, (int)maddr, (int)nbytes);
DEBUGASSERT(dmach);
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
dmainfo("dc_head: %p dc_tail: %p\n", dmach->dc_head, dmach->dc_tail);
#endif
* transfers and the number of bytes per transfer.
*/
maxtransfer = sam_maxtransfer(dmach, dmach->dc_txflags);
while (remaining > maxtransfer)
{
ret = sam_txbuffer(dmach, paddr, maddr, dmach->dc_txflags,
maxtransfer);
if (ret == OK)
{
remaining -= maxtransfer;
* do do).
*
* REVISIT: What if stepsize is not 1?
*/
if ((dmach->dc_txflags & DMACH_FLAG_PERIPH_INCREMENT) != 0)
{
paddr += maxtransfer;
}
if ((dmach->dc_txflags & DMACH_FLAG_MEM_INCREMENT) != 0)
{
maddr += maxtransfer;
}
}
}
if (ret == OK && remaining > 0)
{
ret = sam_txbuffer(dmach, paddr, maddr, dmach->dc_txflags, remaining);
}
return ret;
}
* Name: sam_dmarxsetup
*
* Description:
* Configure DMA for receipt of one buffer (peripheral to memory). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers. Calls to sam_dmatxsetup() and sam_dmarxsetup()
* must not be intermixed on the same transfer, however.
*
****************************************************************************/
int sam_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr,
size_t nbytes)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
ssize_t remaining = (ssize_t)nbytes;
size_t maxtransfer;
int ret = OK;
dmainfo("dmach: %p paddr: %08x maddr: %08x nbytes: %d\n",
dmach, (int)paddr, (int)maddr, (int)nbytes);
DEBUGASSERT(dmach);
#if CONFIG_SAMD5E5_DMAC_NDESC > 0
dmainfo("dc_head: %p dc_tail: %p\n", dmach->dc_head, dmach->dc_tail);
#endif
* transfers and the number of bytes per transfer.
*/
maxtransfer = sam_maxtransfer(dmach, dmach->dc_rxflags);
while (remaining > maxtransfer)
{
ret = sam_rxbuffer(dmach, paddr, maddr, dmach->dc_rxflags,
maxtransfer);
if (ret == OK)
{
remaining -= maxtransfer;
* to do so).
*
* REVISIT: What if stepsize is not 1?
*/
if ((dmach->dc_rxflags & DMACH_FLAG_PERIPH_INCREMENT) != 0)
{
paddr += maxtransfer;
}
if ((dmach->dc_rxflags & DMACH_FLAG_MEM_INCREMENT) != 0)
{
maddr += maxtransfer;
}
}
}
if (ret == OK && remaining > 0)
{
ret = sam_rxbuffer(dmach, paddr, maddr, dmach->dc_rxflags, remaining);
}
return ret;
}
* Name: sam_dmastart
*
* Description:
* Start the DMA transfer
*
****************************************************************************/
int sam_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
struct dma_desc_s *head;
irqstate_t flags;
uint32_t dmaflags;
uint32_t ctrla;
uint32_t regval32;
uint8_t regval8;
int chan;
int ret = -EINVAL;
dmainfo("dmach: %p callback: %p arg: %p\n", dmach, callback, arg);
DEBUGASSERT(dmach != NULL && dmach->dc_chan < SAMD5E5_NDMACHAN);
head = &g_base_desc[dmach->dc_chan];
chan = dmach->dc_chan;
* descriptor list, the base entry).
*/
if (head->srcaddr != 0)
{
* completes.
*/
dmach->dc_callback = callback;
dmach->dc_arg = arg;
if (dmach->dc_dir == DMADIR_TX)
{
dmaflags = dmach->dc_txflags;
}
else
{
dmaflags = dmach->dc_rxflags;
}
flags = enter_critical_section();
putreg8(0, SAM_DMAC_CHCTRLA(chan));
*
* DMAC_CHCTRLA_ENABLE - Set later
* DMAC_CHCTRLA_RUNSTDBY - Determined by DMACH_FLAG_RUNINSTDBY
* DMAC_CHCTRLA_TRIGSRC - Determined by DMACH_FLAG_PERIPH_TRIGSRC
* DMAC_CHCTRLA_TRIGACT - Determined by DMACH_FLAG_PERIPH_TRIGACT
* DMAC_CHCTRLA_BURSTLEN - Determined by DMACH_FLAG_BURSTLEN
* DMAC_CHCTRLA_THRESHOLD - Determined by DMACH_FLAG_THRESHOLD
*/
ctrla = 0;
if (dmaflags & DMACH_FLAG_RUNINSTDBY)
{
ctrla |= DMAC_CHCTRLA_RUNSTDBY;
}
regval32 = (dmaflags & DMACH_FLAG_PERIPH_TRIGSRC_MASK) >>
DMACH_FLAG_PERIPH_TRIGSRC_SHIFT;
ctrla |= DMAC_CHCTRLA_TRIGSRC(regval32) ;
regval32 = (dmaflags & DMAC_CHCTRLA_TRIGACT_MASK) >>
DMAC_CHCTRLA_TRIGACT_SHIFT;
ctrla |= DMAC_CHCTRLA_TRIGACT(regval32) ;
regval32 = ((dmaflags & DMACH_FLAG_BURSTLEN_MASK) >>
DMACH_FLAG_BURSTLEN_SHIFT) + 1;
ctrla |= DMAC_CHCTRLA_BURSTLEN(regval32) ;
regval32 = ((dmaflags & DMACH_FLAG_BURSTLEN_MASK) >>
DMACH_FLAG_BURSTLEN_SHIFT) + 1;
ctrla |= DMAC_CHCTRLA_THRESHOLD(regval32) ;
putreg32(ctrla, SAM_DMAC_CHCTRLA(chan));
*
* DMAC_CHCTRLB_EVIE=0 - No channel input actions
* DMAC_CHCTRLB_EVOE=0 - Channel event output disabled
* DMAC_CHCTRLB_TRIGACT_BEAT - One trigger required for beat transfer
* DMAC_CHCTRLB_CMD_NOACTION - No action
*/
putreg32(DMAC_CHCTRLB_CMD_NOACTION, SAM_DMAC_CHCTRLB(chan));
regval8 = (dmaflags & DMACH_FLAG_PRIORITY_MASK) >>
DMACH_FLAG_PRIORITY_SHIFT;
putreg8(DMAC_CHPRILVL(regval8), SAM_DMAC_CHPRILVL(chan));
*
* DMAC_CHEVCTRL_EVACT - Normal transfer and trigger
* DMAC_CHEVCTRL_EVMODE - Default, block event output selection
* DMAC_CHEVCTRL_EVIE - No channel input actions
* DMAC_CHEVCTRL_EVOE - Channel event output disabled/
*/
regval8 = DMAC_CHEVCTRL_EVACT_TRIG | DMAC_CHEVCTRL_EVOMODE_DEFAULT;
putreg8(DMAC_CHPRILVL(regval8), SAM_DMAC_CHEVCTRL(chan));
ctrla = getreg8(SAM_DMAC_CHCTRLA(chan));
ctrla |= DMAC_CHCTRLA_ENABLE;
putreg8(ctrla, SAM_DMAC_CHCTRLA(chan));
putreg8(DMAC_INT_TERR | DMAC_INT_TCMPL, SAM_DMAC_CHINTENSET(chan));
leave_critical_section(flags);
ret = OK;
}
return ret;
}
* Name: sam_dmastop
*
* Description:
* Cancel the DMA. After sam_dmastop() is called, the DMA channel is
* reset and sam_dmarx/txsetup() must be called before sam_dmastart() can
* be called again
*
****************************************************************************/
void sam_dmastop(DMA_HANDLE handle)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
irqstate_t flags;
dmainfo("dmach: %p\n", dmach);
DEBUGASSERT(dmach != NULL);
flags = enter_critical_section();
sam_dmaterminate(dmach, -EINTR);
leave_critical_section(flags);
}
* Name: sam_dmasample
*
* Description:
* Sample DMA register contents
*
* Assumptions:
* - DMA handle allocated by sam_dmachannel()
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA_INFO
void sam_dmasample(DMA_HANDLE handle, struct sam_dmaregs_s *regs)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
uintptr_t base;
irqstate_t flags;
int chan;
flags = enter_critical_section();
chan = dmach->dc_chan;
base = SAM_DMAC_CHAN_BASE(chan);
regs->chan = chan;
regs->ctrl = getreg16(SAM_DMAC_CTRL);
regs->crcctrl = getreg16(SAM_DMAC_CRCCTRL);
regs->crcdatain = getreg32(SAM_DMAC_CRCDATAIN);
regs->crcchksum = getreg32(SAM_DMAC_CRCCHKSUM);
regs->crcstatus = getreg8(SAM_DMAC_CRCSTATUS);
regs->dbgctrl = getreg8(SAM_DMAC_DBGCTRL);
regs->swtrigctrl = getreg32(SAM_DMAC_SWTRIGCTRL);
regs->prictrl0 = getreg32(SAM_DMAC_PRICTRL0);
regs->intpend = getreg16(SAM_DMAC_INTPEND);
regs->intstatus = getreg32(SAM_DMAC_INTSTATUS);
regs->busych = getreg32(SAM_DMAC_BUSYCH);
regs->pendch = getreg32(SAM_DMAC_PENDCH);
regs->active = getreg32(SAM_DMAC_ACTIVE);
regs->baseaddr = getreg32(SAM_DMAC_BASEADDR);
regs->wrbaddr = getreg32(SAM_DMAC_WRBADDR);
regs->chctrla = getreg32(base + SAM_DMAC_CHCTRLA_OFFSET);
regs->chctrlb = getreg8(base + SAM_DMAC_CHCTRLB_OFFSET);
regs->chprilvl = getreg8(base + SAM_DMAC_CHINTFLAG_OFFSET);
regs->chevctrl = getreg8(base + SAM_DMAC_CHINTFLAG_OFFSET);
regs->chintflag = getreg8(base + SAM_DMAC_CHINTFLAG_OFFSET);
regs->chstatus = getreg8(base + SAM_DMAC_CHSTATU_OFFSETS);
leave_critical_section(flags);
}
#endif
* Name: sam_dmadump
*
* Description:
* Dump previously sampled DMA register contents
*
* Assumptions:
* - DMA handle allocated by sam_dmachannel()
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA_INFO
void sam_dmadump(DMA_HANDLE handle, const struct sam_dmaregs_s *regs,
const char *msg)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
irqstate_t flags;
flags = enter_critical_section();
dmainfo("%s\n", msg);
dmainfo(" DMAC Global Registers:\n");
dmainfo(" CTRL: %04x CRCCTRL: %04x "
"CRCDATAIN: %08x CRCCHKSUM: %08x\n",
regs->ctrl, regs->crcctrl, regs->crcdatain, regs->crcchksum);
dmainfo(" CRCSTATUS: %02x DBGCTRL: %02x "
"SWTRIGCTRL: %08x PRICTRL0: %08x\n",
regs->crcstatus, regs->dbgctrl, regs->swtrigctrl, regs->prictrl0);
dmainfo(" INTPEND: %04x INTSTATUS: %08x "
"BUSYCH: %08x PENDCH: %08x\n",
regs->intpend, regs->intstatus, regs->busych, regs->pendch);
dmainfo(" ACTIVE: %08x BASEADDR: %08x WRBADDR: %08x\n",
regs->active, regs->baseaddr, regs->wrbaddr);
dmainfo(" DMAC Channel %u Registers:\n", regs->chan);
dmainfo(" CHCRTRLA: %08x CHCRTRLB: %02x "
"CHPRILVL: %02x CHPRILVL: %02x\n",
regs->chctrla, regs->chctrlb, regs->chprilvl, priv->chprilvl);
dmainfo(" CHINFLAG: %02x CHSTATUS: %02x\n",
regs->chintflag, regs->chstatus);
}
#endif
#endif