diff --git a/arch/arm/src/samdl/Kconfig b/arch/arm/src/samdl/Kconfig index 9813e7192d..fc76c2290b 100644 --- a/arch/arm/src/samdl/Kconfig +++ b/arch/arm/src/samdl/Kconfig @@ -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 + diff --git a/arch/arm/src/samdl/sam_dmac.c b/arch/arm/src/samdl/sam_dmac.c index cf4d495b3a..2f1f88dadb 100644 --- a/arch/arm/src/samdl/sam_dmac.c +++ b/arch/arm/src/samdl/sam_dmac.c @@ -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); diff --git a/arch/arm/src/samdl/sam_spi.c b/arch/arm/src/samdl/sam_spi.c index 9f398fd9e9..5ee4f10d31 100644 --- a/arch/arm/src/samdl/sam_spi.c +++ b/arch/arm/src/samdl/sam_spi.c @@ -69,6 +69,10 @@ #include "sam_sercom.h" #include "sam_spi.h" +#ifdef CONFIG_SAMDL_SPI_DMA +#include "sam_dmac.h" +#endif + #include #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();