Merged in extent3d/nuttx/dma (pull request #597)

SAMDL DMA fixes and experimental SPI support

* SAMDL: Fix DMA controller support

* SAMDL: Added experimental DMA support to SPI driver. spi_exchange() uses a pair of DMA channels for TX and RX

Approved-by: Gregory Nutt <gnutt@nuttx.org>
This commit is contained in:
Matt Thompson 2018-02-11 22:07:18 +00:00 committed by Gregory Nutt
parent 3a029ed185
commit 10de81ebdf
3 changed files with 195 additions and 20 deletions

View File

@ -488,6 +488,20 @@ config SAMDL_DMAC
select ARCH_DMA
depends on SAMDL_HAVE_DMAC && EXPERIMENTAL
if SAMDL_DMAC && EXPERIMENTAL
menu "DMA options"
config SAMDL_SPI_DMA
bool "SPI DMA"
default n
depends on (SAMDL_HAVE_SPI)
---help---
Use DMA for SPI SERCOM peripherals.
endmenu
endif
config SAMDL_EVSYS
bool "Event System"
default n
@ -767,3 +781,4 @@ config SAMDL_USB_REGDEBUG
CONFIG_DEBUG_USB_INFO.
endif # SAMDL_HAVE_USB

View File

@ -145,15 +145,19 @@ static sem_t g_dsem;
static struct sam_dmach_s g_dmach[SAMDL_NDMACHAN];
/* DMA descriptor tables positioned in LPRAM. In this use case, it is
* acceptable for the writeback descriptors to overlap the base
* descriptors since the base descriptors are always initialized prior
* to starting each DMA transaction.
/*
* NOTE: Using the same address as the base descriptors for writeback descriptors
* causes TERR and FERR interrupts to be raised immediately after starting DMA.
* This was tested on SAMD21G18A, and it would appear that the writeback
* buffer must be located at a different memory address.
*
* - Matt Thompson
*/
static struct dma_desc_s g_base_desc[SAMDL_NDMACHAN]
__attribute__ ((section(".lpram"), aligned(16)));
#define g_writeback_desc g_base_desc
static struct dma_desc_s g_writeback_desc[SAMDL_NDMACHAN]
__attribute__ ((section(".lpram"), aligned(16)));
#if CONFIG_SAMDL_DMAC_NDESC > 0
/* Additional DMA descriptors for (optional) multi-block transfer support.
@ -291,9 +295,10 @@ static int sam_dmainterrupt(int irq, void *context, FAR void *arg)
unsigned int chndx;
uint16_t intpend;
/* Process all pending channel interrupts */
while (((intpend = getreg16(SAM_DMAC_INTPEND)) & DMAC_INTPEND_PEND) != 0)
while ((intpend = getreg16(SAM_DMAC_INTPEND)) != 0)
{
/* Get the channel that generated the interrupt */
@ -322,10 +327,17 @@ static int sam_dmainterrupt(int irq, void *context, FAR void *arg)
sam_dmaterminate(dmach, OK);
}
else if ((intpend & DMAC_INTPEND_TERR) != 0)
{
dmaerr("Invalid descriptor. Channel %d\n", chndx);
sam_dmaterminate(dmach, -EIO);
}
/* Check for channel suspend interrupt */
else if ((intpend & DMAC_INTPEND_SUSP) != 0)
{
dmaerr("Channel suspended. Channel %d\n", chndx);
/* REVISIT: Do we want to do anything here? */
}
}
@ -392,6 +404,7 @@ static struct dma_desc_s *sam_alloc_desc(struct sam_dmach_s *dmach)
/* Yes, return a pointer to the base descriptor */
desc->srcaddr = (uint32_t)-1; /* Any non-zero value */
return desc;
}
#if CONFIG_SAMDL_DMAC_NDESC > 0
@ -406,6 +419,7 @@ static struct dma_desc_s *sam_alloc_desc(struct sam_dmach_s *dmach)
sam_takedsem();
/* Examine each list entry to find an available one -- i.e., one
* with srcaddr == 0. That srcaddr field is set to zero by the DMA
* transfer complete interrupt handler. The following should be safe
@ -472,7 +486,7 @@ static struct dma_desc_s *sam_append_desc(struct sam_dmach_s *dmach,
/* Allocate a DMA descriptor */
desc = sam_alloc_desc(dmach);
if (desc == NULL)
if (desc != NULL)
{
/* We have it. Initialize the new descriptor list entry */
@ -509,6 +523,10 @@ static struct dma_desc_s *sam_append_desc(struct sam_dmach_s *dmach,
dmach->dc_tail = desc;
#endif
}
else
{
dmaerr("Failed to allocate descriptor\n");
}
return desc;
}
@ -556,9 +574,11 @@ static void sam_free_desc(struct sam_dmach_s *dmach)
next = (struct dma_desc_s *)desc->descaddr;
memset(desc, 0, sizeof(struct dma_desc_s));
sam_givedsem();
}
#endif
}
/****************************************************************************
@ -604,7 +624,7 @@ static uint16_t sam_bytes2beats(struct sam_dmach_s *dmach, size_t nbytes)
/* The number of beats is then the ceiling of the division */
mask = (1 < beatsize) - 1;
mask = (1 << beatsize) - 1;
nbeats = (nbytes + mask) >> beatsize;
DEBUGASSERT(nbeats <= UINT16_MAX);
return (uint16_t)nbeats;
@ -653,14 +673,25 @@ static int sam_txbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
tmp = (dmach->dc_flags & DMACH_FLAG_BEATSIZE_MASK) >> DMACH_FLAG_BEATSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_BEATSIZE_SHIFT;
/* See Addressing on page 264 of the datasheet.
* When increment is used, we have to adjust the address in the descriptor
* based on the beat count remaining in the block
*/
/* Set up the Block Transfer Count Register configuration */
btcnt = sam_bytes2beats(dmach, nbytes);
if ((dmach->dc_flags & DMACH_FLAG_MEM_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_SRCINC;
maddr += nbytes;
}
if ((dmach->dc_flags & DMACH_FLAG_PERIPH_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_DSTINC;
paddr += nbytes;
}
if ((dmach->dc_flags & DMACH_FLAG_STEPSEL) == DMACH_FLAG_STEPSEL_PERIPH)
@ -671,10 +702,6 @@ static int sam_txbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
tmp = (dmach->dc_flags & DMACH_FLAG_STEPSIZE_MASK) >> LPSRAM_BTCTRL_STEPSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_STEPSIZE_SHIFT;
/* Set up the Block Transfer Count Register configuration */
btcnt = sam_bytes2beats(dmach, nbytes);
/* Add the new descriptor list entry */
if (!sam_append_desc(dmach, btctrl, btcnt, maddr, paddr))
@ -729,14 +756,20 @@ static int sam_rxbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
tmp = (dmach->dc_flags & DMACH_FLAG_BEATSIZE_MASK) >> DMACH_FLAG_BEATSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_BEATSIZE_SHIFT;
/* Set up the Block Transfer Count Register configuration */
btcnt = sam_bytes2beats(dmach, nbytes);
if ((dmach->dc_flags & DMACH_FLAG_PERIPH_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_SRCINC;
paddr += nbytes;
}
if ((dmach->dc_flags & DMACH_FLAG_MEM_INCREMENT) != 0)
{
btctrl |= LPSRAM_BTCTRL_DSTINC;
maddr += nbytes;
}
if ((dmach->dc_flags & DMACH_FLAG_STEPSEL) == DMACH_FLAG_STEPSEL_MEM)
@ -747,10 +780,6 @@ static int sam_rxbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
tmp = (dmach->dc_flags & DMACH_FLAG_STEPSIZE_MASK) >> LPSRAM_BTCTRL_STEPSIZE_SHIFT;
btctrl |= tmp << LPSRAM_BTCTRL_STEPSIZE_SHIFT;
/* Set up the Block Transfer Count Register configuration */
btcnt = sam_bytes2beats(dmach, nbytes);
/* Add the new descriptor list entry */
if (!sam_append_desc(dmach, btctrl, btcnt, paddr, maddr))
@ -785,6 +814,7 @@ void weak_function up_dmainitialize(void)
/* Initialize global semaphores */
nxsem_init(&g_chsem, 0, 1);
#if CONFIG_SAMDL_DMAC_NDESC > 0
nxsem_init(&g_dsem, 0, CONFIG_SAMDL_DMAC_NDESC);
#endif
@ -1183,7 +1213,7 @@ int sam_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg)
}
chctrlb |= tmp << DMAC_CHCTRLB_TRIGSRC_SHIFT;
putreg8(chctrlb, SAM_DMAC_CHCTRLB);
putreg32(chctrlb, SAM_DMAC_CHCTRLB);
/* Setup the Quality of Service Control Register
*
@ -1330,7 +1360,7 @@ void sam_dmadump(DMA_HANDLE handle, const struct sam_dmaregs_s *regs,
regs->ctrl, regs->crcctrl, regs->crcdatain, regs->crcchksum);
dmainfo(" CRCSTATUS: %02x DBGCTRL: %02x QOSCTRL: %02x SWTRIGCTRL: %08x\n",
regs->crcstatus, regs->dbgctrl, regs->qosctrl, regs->swtrigctrl);
dmainfo(" PRICTRL0: %08x INTPEND: %04x INSTSTATUS: %08x BUSYCH: %08x\n",
dmainfo(" PRICTRL0: %08x INTPEND: %04x INTSTATUS: %08x BUSYCH: %08x\n",
regs->prictrl0, regs->intpend, regs->intstatus, regs->busych);
dmainfo(" PENDCH: %08x ACTIVE: %08x BASEADDR: %08x WRBADDR: %08x\n",
regs->pendch, regs->active, regs->baseaddr, regs->wrbaddr);

View File

@ -69,6 +69,10 @@
#include "sam_sercom.h"
#include "sam_spi.h"
#ifdef CONFIG_SAMDL_SPI_DMA
#include "sam_dmac.h"
#endif
#include <arch/board/board.h>
#ifdef SAMDL_HAVE_SPI
@ -118,6 +122,16 @@ struct sam_spidev_s
uint8_t mode; /* Mode 0,1,2,3 */
uint8_t nbits; /* Width of word in bits (8 to 16) */
#ifdef CONFIG_SAMDL_SPI_DMA
/* DMA */
uint8_t dma_tx_trig; /* DMA TX trigger source to use */
uint8_t dma_rx_trig; /* DMA RX trigger source to use */
DMA_HANDLE dma_tx; /* DMA TX channel handle */
DMA_HANDLE dma_rx; /* DMA RX channel handle */
sem_t dmasem; /* Transfer wait semaphore */
#endif
/* Debug stuff */
#ifdef CONFIG_SAMDL_SPI_REGDEBUG
@ -154,6 +168,10 @@ static uint32_t spi_getreg32(struct sam_spidev_s *priv,
static void spi_putreg32(struct sam_spidev_s *priv, uint32_t regval,
unsigned int offset);
#ifdef CONFIG_SAMDL_SPI_DMA
static void spi_dma_setup(struct sam_spidev_s *priv);
#endif
#ifdef CONFIG_DEBUG_SPI_INFO
static void spi_dumpregs(struct sam_spidev_s *priv, const char *msg);
#else
@ -237,6 +255,10 @@ static struct sam_spidev_s g_spi0dev =
.srcfreq = BOARD_SERCOM0_FREQUENCY,
.base = SAM_SERCOM0_BASE,
.spilock = SEM_INITIALIZER(1),
#ifdef CONFIG_SAMDL_SPI_DMA
.dma_tx_trig = DMAC_TRIGSRC_SERCOM0_TX,
.dma_rx_trig = DMAC_TRIGSRC_SERCOM0_RX,
#endif
};
#endif
@ -283,6 +305,10 @@ static struct sam_spidev_s g_spi1dev =
.srcfreq = BOARD_SERCOM1_FREQUENCY,
.base = SAM_SERCOM1_BASE,
.spilock = SEM_INITIALIZER(1),
#ifdef CONFIG_SAMDL_SPI_DMA
.dma_tx_trig = DMAC_TRIGSRC_SERCOM1_TX,
.dma_rx_trig = DMAC_TRIGSRC_SERCOM1_RX,
#endif
};
#endif
@ -329,6 +355,10 @@ static struct sam_spidev_s g_spi2dev =
.srcfreq = BOARD_SERCOM2_FREQUENCY,
.base = SAM_SERCOM2_BASE,
.spilock = SEM_INITIALIZER(1),
#ifdef CONFIG_SAMDL_SPI_DMA
.dma_tx_trig = DMAC_TRIGSRC_SERCOM2_TX,
.dma_rx_trig = DMAC_TRIGSRC_SERCOM2_RX,
#endif
};
#endif
@ -375,6 +405,10 @@ static struct sam_spidev_s g_spi3dev =
.srcfreq = BOARD_SERCOM3_FREQUENCY,
.base = SAM_SERCOM3_BASE,
.spilock = SEM_INITIALIZER(1),
#ifdef CONFIG_SAMDL_SPI_DMA
.dma_tx_trig = DMAC_TRIGSRC_SERCOM3_TX,
.dma_rx_trig = DMAC_TRIGSRC_SERCOM3_RX,
#endif
};
#endif
@ -421,6 +455,10 @@ static struct sam_spidev_s g_spi4dev =
.srcfreq = BOARD_SERCOM4_FREQUENCY,
.base = SAM_SERCOM4_BASE,
.spilock = SEM_INITIALIZER(1),
#ifdef CONFIG_SAMDL_SPI_DMA
.dma_tx_trig = DMAC_TRIGSRC_SERCOM4_TX,
.dma_rx_trig = DMAC_TRIGSRC_SERCOM4_RX,
#endif
};
#endif
@ -467,6 +505,10 @@ static struct sam_spidev_s g_spi5dev =
.srcfreq = BOARD_SERCOM5_FREQUENCY,
.base = SAM_SERCOM5_BASE,
.spilock = SEM_INITIALIZER(1),
#ifdef CONFIG_SAMDL_SPI_DMA
.dma_tx_trig = DMAC_TRIGSRC_SERCOM5_TX,
.dma_rx_trig = DMAC_TRIGSRC_SERCOM5_RX,
#endif
};
#endif
@ -1061,6 +1103,30 @@ static uint16_t spi_send(struct spi_dev_s *dev, uint16_t wd)
return (uint16_t)rxbyte;
}
#ifdef CONFIG_SAMDL_SPI_DMA
static void spi_dma_callback(DMA_HANDLE dma, void *arg, int result)
{
struct sam_spidev_s *priv = (struct sam_spidev_s *)arg;
if(dma == priv->dma_rx)
{
/* Notify the blocked spi_exchange() call that the transaction
* has completed by posting to the semaphore
*/
nxsem_post(&priv->dmasem);
}
else if(dma == priv->dma_tx)
{
if(result != OK)
{
spierr("DMA transmission failed\n");
}
}
}
#endif
/****************************************************************************
* Name: spi_exchange
*
@ -1089,14 +1155,51 @@ static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer,
void *rxbuffer, size_t nwords)
{
struct sam_spidev_s *priv = (struct sam_spidev_s *)dev;
spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
#ifdef CONFIG_SAMDL_SPI_DMA
int ret;
uint32_t regval;
/* Disable SPI while we configure new DMA descriptors */
regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET);
regval &= ~SPI_CTRLA_ENABLE;
spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET);
spi_wait_synchronization(priv);
/* Setup RX and TX DMA channels */
sam_dmatxsetup(priv->dma_tx, priv->base + SAM_SPI_DATA_OFFSET, txbuffer, nwords);
sam_dmarxsetup(priv->dma_rx, priv->base + SAM_SPI_DATA_OFFSET, rxbuffer, nwords);
/* Start RX and TX DMA channels */
sam_dmastart(priv->dma_tx, spi_dma_callback, (void *)priv);
sam_dmastart(priv->dma_rx, spi_dma_callback, (void *)priv);
/* Enable SPI to trigger the TX DMA channel */
regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET);
regval |= SPI_CTRLA_ENABLE;
spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET);
spi_wait_synchronization(priv);
do
{
/* Wait for the DMA callback to notify us that the transfer is complete */
ret = nxsem_wait(&priv->dmasem);
}
while (ret < 0 && ret == -EINTR);
#else
const uint16_t *ptx16;
const uint8_t *ptx8;
uint16_t *prx16;
uint8_t *prx8;
uint16_t data;
spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
/* Set up data receive and transmit pointers */
if (priv->nbits > 8)
@ -1203,6 +1306,7 @@ static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer,
*prx16++ = (uint16_t)data;
}
}
#endif
}
/****************************************************************************
@ -1312,6 +1416,28 @@ static void spi_pad_configure(struct sam_spidev_s *priv)
}
}
#ifdef CONFIG_SAMDL_SPI_DMA
static void spi_dma_setup(struct sam_spidev_s *priv)
{
/* Allocate a pair of DMA channels */
priv->dma_rx = sam_dmachannel(DMACH_FLAG_BEATSIZE_BYTE |
DMACH_FLAG_MEM_INCREMENT |
DMACH_FLAG_PERIPH_RXTRIG(priv->dma_rx_trig));
priv->dma_tx = sam_dmachannel(DMACH_FLAG_BEATSIZE_BYTE |
DMACH_FLAG_MEM_INCREMENT |
DMACH_FLAG_PERIPH_TXTRIG(priv->dma_tx_trig));
/* Initialize the samaphore used to notify when DMA is complete */
nxsem_init(&priv->dmasem, 0, 0);
nxsem_setprotocol(&priv->dmasem, SEM_PRIO_NONE);
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -1398,6 +1524,10 @@ struct spi_dev_s *sam_spibus_initialize(int port)
return NULL;
}
#ifdef CONFIG_SAMDL_SPI_DMA
spi_dma_setup(priv);
#endif
/* Enable clocking to the SERCOM module in PM */
flags = enter_critical_section();