nuttx/arch/arm/src/lpc54xx/lpc54_dma.c

637 lines
18 KiB
C
Raw Normal View History

/****************************************************************************
* arch/arm/src/lpc54xx/lpc54_dma.c
*
* Copyright (C) 2017 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include "arm_internal.h"
#include "arm_arch.h"
#include "hardware/lpc54_inputmux.h"
#include "hardware/lpc54_dma.h"
#include "lpc54_enableclk.h"
#include "lpc54_reset.h"
#include "lpc54_dma.h"
#ifdef CONFIG_LPC54_DMA
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure represents the state of one DMA channel */
struct lpc54_dmach_s
{
bool inuse; /* True: The channel is in use */
dma_callback_t callback; /* DMA completion callback function */
void *arg; /* Argument to pass to the callback function */
};
/* This structure represents the state of the LPC54 DMA block */
struct lpc54_dma_s
{
sem_t exclsem; /* For exclusive access to the DMA channel list */
/* This is the state of each DMA channel */
struct lpc54_dmach_s dmach[LPC54_DMA_NCHANNELS];
};
/****************************************************************************
* Private Data
****************************************************************************/
/* The state of the LPC54 DMA block */
static struct lpc54_dma_s g_dma;
/* The SRAMBASE register must be configured with an address (preferably in
* on-chip SRAM) where DMA descriptors will be stored. Each DMA channel has
* an entry for the channel descriptor in the SRAM table.
*/
static struct lpc54_dmachan_desc_s g_dma_desc[LPC54_DMA_NCHANNELS];
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: lpc54_dma_dispatch
*
* Description:
* Dispatch a DMA interrupt.
*
* Returned Value:
* None
*
****************************************************************************/
static void lpc54_dma_dispatch(int ch, int result)
{
struct lpc54_dmach_s *dmach;
/* Yes.. Is this channel assigned? Is there a callback function? */
dmach = &g_dma.dmach[ch];
if (dmach->inuse && dmach->callback != NULL)
{
/* Perform the callback */
dmach->callback(ch, dmach->arg, result);
}
/* Disable this channel, mask any further interrupts for this channel, and
* clear any pending interrupts.
*/
lpc54_dmastop(ch);
}
/****************************************************************************
* Name: lpc54_dma_interrupt
*
* Description:
* The common DMA interrupt handler.
*
* Returned Value:
* None
*
****************************************************************************/
static int lpc54_dma_interrupt(int irq, FAR void *context, FAR void *arg)
{
uint32_t pending;
uint32_t bitmask;
int ch;
/* Check for pending DMA channel error interrupts */
pending = getreg32(LPC54_DMA_ERRINT0);
putreg32(pending, LPC54_DMA_ERRINT0);
for (ch = 0; pending != 0 && ch < LPC54_DMA_NCHANNELS; ch++)
{
/* Check if there is a pending error on this channel */
bitmask = DMA_CHANNEL((uint32_t)ch);
if ((pending & bitmask) != 0)
{
/* Dispatch the DMA channel error event */
lpc54_dma_dispatch(ch, -EIO);
pending &= ~bitmask;
}
}
/* Check for pending DMA interrupt A events */
pending = getreg32(LPC54_DMA_INTA0);
putreg32(pending, LPC54_DMA_INTA0);
for (ch = 0; pending != 0 && ch < LPC54_DMA_NCHANNELS; ch++)
{
/* Check if there is a pending interrupt A on this channel */
bitmask = DMA_CHANNEL((uint32_t)ch);
if ((pending & bitmask) != 0)
{
/* Dispatch DMA channel interrupt A event */
lpc54_dma_dispatch(ch, OK);
pending &= ~bitmask;
}
}
#if 0 /* interrupt B is not used */
/* Check for pending DMA interrupt B events */
pending = getreg32(LPC54_DMA_INTB0);
putreg32(pending, LPC54_DMA_INTB0);
for (ch = 0; pending != 0 && ch < LPC54_DMA_NCHANNELS; ch++)
{
/* Check if there is a pending interrupt A on this channel */
bitmask = DMA_CHANNEL((uint32_t)ch);
if ((pending & bitmask) != 0)
{
/* Dispatch DMA channel interrupt B event */
lpc54_dma_dispatch(ch, OK);
pending &= ~bitmask;
}
}
#endif
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_dma_initialize
*
* Description:
* Initialize the DMA subsystem. Called from up_initialize() early in the
* boot-up sequence. Prototyped in arm_internal.h.
*
* Returned Value:
* None
*
****************************************************************************/
void weak_function up_dma_initialize(void)
{
int ret;
/* Enable clocking to the DMA block */
lpc54_dma_enableclk();
/* Reset the DMA peripheral */
lpc54_reset_dma();
/* Disable and clear all DMA interrupts */
putreg32(DMA_ALL_CHANNELS, LPC54_DMA_INTENCLR0);
putreg32(DMA_ALL_CHANNELS, LPC54_DMA_ERRINT0);
putreg32(DMA_ALL_CHANNELS, LPC54_DMA_INTA0);
putreg32(DMA_ALL_CHANNELS, LPC54_DMA_INTB0);
/* Initialize the DMA state structure */
nxsem_init(&g_dma.exclsem, 0, 1);
/* Set the SRAMBASE to the beginning a array of DMA descriptors, one for
* each DMA channel.
*/
putreg32((uint32_t)g_dma_desc, LPC54_DMA_SRAMBASE);
/* Attach and enable the DMA interrupt handler */
ret = irq_attach(LPC54_IRQ_DMA, lpc54_dma_interrupt, NULL);
if (ret == OK)
{
up_enable_irq(LPC54_IRQ_DMA);
}
/* Enable the DMA controller */
putreg32(DMA_CTRL_ENABLE, LPC54_DMA_CTRL);
}
/****************************************************************************
* Name: lpc54_dma_setup
*
* Description:
* Configure DMA for one transfer.
*
* Input Parameters:
* ch - DMA channel number
* cfg - The content of the DMA channel configuration register. See
* peripheral channel definitions in chip/lpc54_dma.h. The
* caller must provide all fields: PERIPHREQEN, TRIGPOL,
* TRIGTYPE, TRIGBURST, BURSTPOWER, SRCBURSTWRAP, DSTBURSTWRAP,
* and CHPRIORITY.
* xfrcfg - The content of the DMA channel configuration register. See
* peripheral channel definitions in chip/lpc54_dma.h. The
* caller must provide all fields: WIDTH, SRCINC, and DSTINC.\
* All of fields are managed by the DMA driver
* trigsrc - See input mux DMA trigger ITRIG_INMUX_* definitions in
* chip/lpc54_inputmux.h.
* srcaddr - Source address of the DMA transfer
* dstaddr - Destination address of the DMA transfer
* nbytes - Number of bytes to transfer
*
****************************************************************************/
int lpc54_dma_setup(int ch, uint32_t cfg, uint32_t xfrcfg, uint8_t trigsrc,
uintptr_t srcaddr, uintptr_t dstaddr, size_t nbytes)
{
struct lpc54_dmach_s *dmach;
uintptr_t base;
uintptr_t regaddr;
uint32_t nxfrs;
uint32_t width;
uint32_t incr;
int ret;
DEBUGASSERT((unsigned)ch < LPC54_DMA_NCHANNELS && nbytes < 4096);
dmach = &g_dma.dmach[ch];
/* Get exclusive access to the DMA data structures and interface */
ret = nxsem_wait(&g_dma.exclsem);
if (ret < 0)
{
return ret;
}
/* Make sure that the DMA channel is not in use */
DEBUGASSERT(!dmach->inuse);
if (dmach->inuse)
{
ret = -EBUSY;
goto errout_with_exclsem;
}
dmach->inuse = true;
/* Make sure that the trigger is not active */
base = LPC54_DMA_CHAN_BASE(ch);
putreg32(0, base + LPC54_DMA_CFG_OFFSET);
/* Number of transfers */
switch (xfrcfg & DMA_XFERCFG_WIDTH_MASK)
{
default:
case DMA_XFERCFG_WIDTH_8BIT:
width = 1;
nxfrs = nbytes;
break;
case DMA_XFERCFG_WIDTH_16BIT:
width = 2;
nxfrs = ((nbytes + 1) >> 1);
break;
case DMA_XFERCFG_WIDTH_32BIT:
width = 4;
nxfrs = ((nbytes + 3) >> 2);
break;
}
/* Check if the number of transfers can be performed */
if (nxfrs > LPC54_DMA_MAXXFRS)
{
return -E2BIG;
}
/* Set up the channel DMA descriptor */
g_dma_desc[ch].reserved = 0;
switch (cfg & DMA_XFERCFG_SRCINC_MASK)
{
default:
case DMA_XFERCFG_SRCINC_NONE:
incr = 0;
break;
case DMA_XFERCFG_SRCINC_1X:
incr = width;
break;
case DMA_XFERCFG_SRCINC_2X:
incr = width << 1;
break;
case DMA_XFERCFG_SRCINC_4X:
incr = width << 2;
incr = 0;
break;
}
g_dma_desc[ch].srcend = (uint32_t)srcaddr + nxfrs * incr;
switch (cfg & DMA_XFERCFG_DSTINC_MASK)
{
default:
case DMA_XFERCFG_DSTINC_NONE:
incr = 0;
break;
case DMA_XFERCFG_DSTINC_1X:
incr = width;
break;
case DMA_XFERCFG_DSTINC_2X:
incr = width << 1;
break;
case DMA_XFERCFG_DSTINC_4X:
incr = width << 2;
incr = 0;
break;
}
g_dma_desc[ch].dstend = (uint32_t)dstaddr + nxfrs * incr;
g_dma_desc[ch].link = 0;
/* Set the trigger source */
regaddr = LPC54_MUX_DMA_ITRIG_INMUX(ch);
putreg32(MUX_DMA_ITRIG_INMUX(trigsrc), regaddr);
/* Set the channel configuration register.
*
* PERIPHREQEN - Provided by caller
* TRIGPOL - Provided by caller
* TRIGTYPE - Provided by caller
* TRIGBURST - Provided by caller
* BURSTPOWER - Provided by caller
* SRCBURSTWRAP - Provided by caller
* DSTBURSTWRAP - Provided by caller
* CHPRIORITY - Provided by caller
*/
putreg32(cfg, base + LPC54_DMA_CFG_OFFSET);
/* Set the channel transfer configuration register
*
* CFGVALID - Current channel descriptor is valid.
* RELOAD - No reload
* SWTRIG - No software trigger
* CLRTRIG - Trigger cleared when descriptor is exhausted
* SETINTA - Use interrupt A
* SETINTB - Don't use interrupt B
* WIDTH - Provided by caller
* SRCINC - Provided by caller
* DSTINC - Provided by caller
* XFERCOUNT - Derived from with and nbytes
*/
xfrcfg &= ~(DMA_XFERCFG_RELOAD | DMA_XFERCFG_SWTRIG | DMA_XFERCFG_SETINTB |
DMA_XFERCFG_XFERCOUNT_MASK);
xfrcfg |= (DMA_XFERCFG_CFGVALID | DMA_XFERCFG_CLRTRIG | DMA_XFERCFG_SETINTA);
xfrcfg |= DMA_XFERCFG_XFERCOUNT(nxfrs);
putreg32(xfrcfg, base + LPC54_DMA_XFERCFG_OFFSET);
ret = OK;
errout_with_exclsem:
nxsem_post(&g_dma.exclsem);
return ret;
}
/****************************************************************************
* Name: lpc54_dmastart
*
* Description:
* Start the DMA transfer
*
****************************************************************************/
int lpc54_dmastart(int ch, dma_callback_t callback, void *arg)
{
struct lpc54_dmach_s *dmach;
uintptr_t regaddr;
uint32_t bitmask;
DEBUGASSERT((unsigned)ch < LPC54_DMA_NCHANNELS);
dmach = &g_dma.dmach[ch];
DEBUGASSERT(dmach->inuse && callback != NULL);
/* Save the callback information */
dmach->callback = callback;
dmach->arg = arg;
/* Clear any pending DMA interrupts */
bitmask = DMA_CHANNEL(ch);
putreg32(bitmask, LPC54_DMA_ERRINT0);
putreg32(bitmask, LPC54_DMA_INTA0);
putreg32(bitmask, LPC54_DMA_INTB0);
/* Enable the channel and enable interrupt A and error interrupts. */
putreg32(bitmask, LPC54_DMA_ENABLESET0); /* Enable the channel */
putreg32(bitmask, LPC54_DMA_INTENSET0); /* Enable channel interrupts */
/* Enable the trigger for this channel */
regaddr = LPC54_DMA_CTLSTAT(ch);
modifyreg32(regaddr, 0, DMA_CTLSTAT_TRIG);
return OK;
}
/****************************************************************************
* Name: lpc54_dmastop
*
* Description:
* Cancel the DMA. After lpc54_dmastop() is called, the DMA channel is
* reset and lpc54_dmasetup() must be called before lpc54_dmastart() can be
* called again
*
* This function will be called either by the user directly, by the user
* indirectly via lpc54_dmafree(), or from lpc54_dma_interrupt when the
* transfer completes.
*
****************************************************************************/
void lpc54_dmastop(int ch)
{
struct lpc54_dmach_s *dmach;
uint32_t bitmask;
DEBUGASSERT((unsigned)ch < LPC54_DMA_NCHANNELS);
dmach = &g_dma.dmach[ch];
DEBUGASSERT(dmach->inuse);
/* Disable this channel and mask any further interrupts from the channel.
* this channel.
*/
bitmask = DMA_CHANNEL(ch);
putreg32(bitmask, LPC54_DMA_INTENCLR0); /* Disable channel interrupts */
putreg32(bitmask, LPC54_DMA_ENABLECLR0); /* Disable the channel */
/* Clear any pending interrupts for this channel */
putreg32(bitmask, LPC54_DMA_ERRINT0);
putreg32(bitmask, LPC54_DMA_INTA0);
putreg32(bitmask, LPC54_DMA_INTB0);
/* This channel is no longer in use */
g_dma.dmach[ch].inuse = false;
}
/****************************************************************************
* Name: lpc54_dmasample
*
* Description:
* Sample DMA register contents
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA
void lpc54_dmasample(int ch, struct lpc54_dmaregs_s *regs)
{
uintptr_t base;
DEBUGASSERT((unsigned)ch < LPC54_DMA_NCHANNELS);
/* Sample the global DMA registers */
regs->gbl.ctrl = getreg32(LPC54_DMA_CTRL);
regs->gbl.intstat = getreg32(LPC54_DMA_INTSTAT);
regs->gbl.srambase = getreg32(LPC54_DMA_SRAMBASE);
regs->gbl.enableset0 = getreg32(LPC54_DMA_ENABLESET0);
regs->gbl.active0 = getreg32(LPC54_DMA_ACTIVE0);
regs->gbl.busy0 = getreg32(LPC54_DMA_BUSY0);
regs->gbl.errint0 = getreg32(LPC54_DMA_ERRINT0);
regs->gbl.intenset0 = getreg32(LPC54_DMA_INTENSET0);
regs->gbl.inta0 = getreg32(LPC54_DMA_INTA0);
regs->gbl.intb0 = getreg32(LPC54_DMA_INTB0);
/* Sample the DMA channel registers */
base = LPC54_DMA_CHAN_BASE(ch);
regs->ch.cfg = getreg32(base + LPC54_DMA_CFG_OFFSET);
regs->ch.ctlstat = getreg32(base + LPC54_DMA_CTLSTAT_OFFSET);
regs->ch.xfercfg = getreg32(base + LPC54_DMA_XFERCFG_OFFSET);
}
#endif /* CONFIG_DEBUG_DMA */
/****************************************************************************
* Name: lpc54_dmadump
*
* Description:
* Dump previously sampled DMA register contents
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA
void lpc54_dmadump(int ch, const struct lpc54_dmaregs_s *regs,
const char *msg)
{
uintptr_t base;
DEBUGASSERT((unsigned)ch < LPC54_DMA_NCHANNELS && regs != NULL && msg != NULL);
/* Dump the sampled global DMA registers */
dmainfo("Global DMA Registers: %s\n", msg);
dmainfo(" CTRL[%08x]: %08lx\n",
LPC54_DMA_CTRL, (unsigned long)regs->gbl.ctrl);
dmainfo(" INTSTAT[%08x]: %08lx\n",
LPC54_DMA_INTSTAT, (unsigned long)regs->gbl.intstat);
dmainfo(" SRAMBASE[%08x]: %08lx\n",
LPC54_DMA_SRAMBASE, (unsigned long)regs->gbl.srambase);
dmainfo(" ENABLESET0[%08x]: %08lx\n",
LPC54_DMA_ENABLESET0, (unsigned long)regs->gbl.enableset0);
dmainfo(" ACTIVE0[%08x]: %08lx\n",
LPC54_DMA_ACTIVE0, (unsigned long)regs->gbl.active0);
dmainfo(" BUSY0[%08x]: %08lx\n",
LPC54_DMA_BUSY0, (unsigned long)regs->gbl.busy0);
dmainfo(" ERRINT0[%08x]: %08lx\n",
LPC54_DMA_ERRINT0, (unsigned long)regs->gbl.errint0);
dmainfo(" INTENSET0[%08x]: %08lx\n",
LPC54_DMA_INTENSET0, (unsigned long)regs->gbl.intenset0);
dmainfo(" INTA0[%08x]: %08lx\n",
LPC54_DMA_INTA0, (unsigned long)regs->gbl.inta0);
dmainfo(" INTB0[%08x]: %08lx\n",
LPC54_DMA_INTB0, (unsigned long)regs->gbl.intb0);
/* Dump the DMA channel registers */
base = LPC54_DMA_CHAN_BASE(ch);
dmainfo("Channel DMA Registers: %d\n", ch);
dmainfo(" CFG[%08x]: %08lx\n",
base + LPC54_DMA_CFG_OFFSET, (unsigned long)regs->ch.cfg);
dmainfo(" CTLSTAT[%08x]: %08lx\n",
base + LPC54_DMA_CTLSTAT_OFFSET, (unsigned long)regs->ch.ctlstat);
dmainfo(" XFERCFG[%08x]: %08lx\n",
base + LPC54_DMA_XFERCFG_OFFSET, (unsigned long)regs->ch.xfercfg);
}
#endif /* CONFIG_DEBUG_DMA */
#endif /* CONFIG_LPC54_DMA */