/***************************************************************************** * arch/arm/src/efm32/efm32_dma.c * * Copyright (C) 2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #include #include #include #include #include #include #include #include "up_arch.h" #include "chip/efm32_cmu.h" #include "chip/efm32_dma.h" #include "efm32_dma.h" /***************************************************************************** * Pre-processor Definitions *****************************************************************************/ #define ALIGN_MASK(s) ((1 << s) - 1) #define ALIGN_DOWN(v,m) ((v) & ~m) #define ALIGN_UP(v,m) (((v) + (m)) & ~m) /***************************************************************************** * Public Types *****************************************************************************/ /* This structure describes one DMA channel */ struct dma_channel_s { uint8_t chan; /* DMA channel number (0-EFM32_DMA_NCHANNELS) */ bool inuse; /* TRUE: The DMA channel is in use */ dma_config_t config; /* Current configuration */ dma_callback_t callback; /* Callback invoked when the DMA completes */ void *arg; /* Argument passed to callback function */ }; /* This structure describes the state of the DMA controller */ struct dma_controller_s { sem_t exclsem; /* Protects channel table */ sem_t chansem; /* Count of free channels */ }; /***************************************************************************** * Private Data *****************************************************************************/ /* This is the overall state of the DMA controller */ static struct dma_controller_s g_dmac; /* This is the array of all DMA channels */ static struct dma_channel_s g_dmach[EFM32_DMA_NCHANNELS]; /* This array describes the available channel control data structures. Each * structure must be aligned to a 256 address boundary. The last 8 or 9 * bits of address are provided by the DMA controller: * * 8-Channels: * Bit 7: Selects the alternate descriptor list * Bits 4-6: The DMA channel (0-7) * Bits 2-3: Selects the descriptor field * Bits 0-1: Always zero * * 12-Channels: * Bit 8: Selects the alternate descriptor list * Bits 4-7: The DMA channel (0-11) * Bits 2-3: Selects the descriptor field * Bits 0-1: Always zero */ #if EFM32_DMA_NCHANNELS <= 8 # define DESC_TABLE_SIZE 8 # define DESC_TABLE_ALIGN 256 /* 2*8*16 */ #elif EFM32_DMA_NCHANNELS <= 16 # define DESC_TABLE_SIZE 16 # define DESC_TABLE_ALIGN 512 /* 2*16*16 */ #else # error Unknown descriptor table size #endif #ifdef CONFIG_EFM32_DMA_ALTDSEC static struct dma_descriptor_s g_descriptors[DESC_TABLE_SIZE + EFM32_DMA_NCHANNELS] __attribute__((aligned(DESC_TABLE_ALIGN))); #else static struct dma_descriptor_s g_descriptors[EFM32_DMA_NCHANNELS] __attribute__((aligned(DESC_TABLE_ALIGN))); #endif /***************************************************************************** * Public Data *****************************************************************************/ /***************************************************************************** * Private Functions *****************************************************************************/ /**************************************************************************** * Name: efm32_set_chctrl * * Description: * Set the channel control register * ****************************************************************************/ static void efm32_set_chctrl(struct dma_channel_s *dmach, dma_config_t config) { uintptr_t regaddr; uint32_t decoded; uint32_t regval; decoded = (uint32_t)(config & EFM32_DMA_SIGSEL_MASK) >> EFM32_DMA_SIGSEL_SHIFT; regval = (decoded << _DMA_CH_CTRL_SIGSEL_SHIFT); decoded = (uint32_t)(config & EFM32_DMA_SOURCSEL_MASK) >> EFM32_DMA_SOURCSEL_SHIFT; regval |= (decoded << _DMA_CH_CTRL_SOURCESEL_SHIFT); regaddr = EFM32_DMA_CHn_CTRL(dmach->chan); putreg32(regval, regaddr); } /**************************************************************************** * Name: efm32_align_shift * * Description: * Set the channel control register * ****************************************************************************/ static inline unsigned int efm32_align_shift(dma_config_t config) { unsigned int shift; shift = (config & EFM32_DMA_XFERSIZE_MASK) >> EFM32_DMA_XFERSIZE_SHIFT; DEBUGASSERT(shift != 3); return shift; } /**************************************************************************** * Name: efm32_get_descriptor * * Description: * Get the address of the primary or alternate descriptor assigned to the * DMA channel * ****************************************************************************/ static inline struct dma_descriptor_s * efm32_get_descriptor(struct dma_channel_s *dmach, bool alt) { uintptr_t base = alt ? getreg32(EFM32_DMA_ALTCTRLBASE) : getreg32(EFM32_DMA_CTRLBASE); return ((struct dma_descriptor_s *)base) + dmach->chan; } /**************************************************************************** * Name: efm32_dmac_interrupt * * Description: * DMA interrupt handler * ****************************************************************************/ static int efm32_dmac_interrupt(int irq, void *context) { struct dma_channel_s *dmach; unsigned int chndx; uint32_t pending; uint32_t bit; /* Get the set of pending, unmasked global XDMAC interrupts */ pending = getreg32(EFM32_DMA_IF) & getreg32(EFM32_DMA_IEN); putreg32(pending, EFM32_DMA_IFC); /* Check each bit to see which channel(s) have interrupted */ for (chndx = 0; chndx < EFM32_DMA_NCHANNELS && pending != 0; chndx++) { /* Are any interrupts pending for this channel? */ bit = 1 << chndx; if ((pending & bit) != 0) { dmach = &g_dmach[chndx]; /* Put the DMA channel in the stopped state */ efm32_dmastop((DMA_HANDLE)dmach); /* Call the DMA completion callback */ if (dmach->callback) { dmach->callback((DMA_HANDLE)dmach, OK, dmach->arg); dmach->callback = NULL; } dmach->arg = NULL; /* Clear the bit in the sampled set of pending interrupts */ pending &= !bit; } } return OK; } /***************************************************************************** * Public Functions *****************************************************************************/ /**************************************************************************** * Name: up_dmainitialize * * Description: * Initialize the DMA subsystem * * Returned Value: * None * ****************************************************************************/ void weak_function up_dmainitialize(void) { uint32_t regval; int i; dmallvdbg("Initialize XDMAC0\n"); /* Initialize the channel list */ sem_init(&g_dmac.exclsem, 0, 1); sem_init(&g_dmac.chansem, 0, EFM32_DMA_NCHANNELS); for (i = 0; i < EFM32_DMA_NCHANNELS; i++) { g_dmach[i].chan = i; } /* Enable clocking to the DMA module. DMA is clocked by HFCORECLK. */ regval = getreg32(EFM32_CMU_HFCORECLKEN0); regval |= CMU_HFCORECLKEN0_DMA; putreg32(regval, EFM32_CMU_HFCORECLKEN0); /* Set the control base addresses. Note: EFM32_DMA_ALTCTRLBASE * is a read-only register that provides the location where hardware * will obtain the alternative descriptors. */ putreg32((uint32_t)g_descriptors, EFM32_DMA_CTRLBASE); /* Attach DMA interrupt vector */ (void)irq_attach(EFM32_IRQ_DMA, efm32_dmac_interrupt); /* Enable the DMA controller */ putreg32(DMA_CONFIG_EN, EFM32_DMA_CONFIG); /* Enable the IRQ at the AIC (still disabled at the DMA controller) */ up_enable_irq(EFM32_IRQ_DMA); } /**************************************************************************** * Name: efm32_dmachannel * * Description: * Allocate a DMA channel. This function gives the caller mutually * exclusive access to a DMA channel. * * If no DMA channel is available, then efm32_dmachannel() will wait * until the holder of a channel relinquishes the channel by calling * efm32_dmafree(). * * Input parameters: * None * * Returned Value: * This function ALWAYS returns a non-NULL, void* DMA channel handle. * * Assumptions: * - The caller can wait for a DMA channel to be freed if it is not * available. * ****************************************************************************/ DMA_HANDLE efm32_dmachannel(void) { struct dma_channel_s *dmach; unsigned int chndx; uint32_t bit; /* Take a count from from the channel counting semaphore. We may block * if there are no free channels. When we get the count, then we can * be assured that a channel is available in the channel list and is * reserved for us. */ while (sem_wait(&g_dmac.chansem) < 0) { /* sem_wait should fail only if it is awakened by a a signal */ DEBUGASSERT(errno == EINTR); } /* Get exclusive access to the DMA channel list */ while (sem_wait(&g_dmac.exclsem) < 0) { /* sem_wait should fail only if it is awakened by a a signal */ DEBUGASSERT(errno == EINTR); } /* Search for an available DMA channel */ for (chndx = 0, dmach = NULL; chndx < EFM32_DMA_NCHANNELS; chndx++) { struct dma_channel_s *candidate = &g_dmach[chndx]; if (!candidate->inuse) { dmach = candidate; dmach->inuse = true; /* Clear any pending channel interrupts */ bit = 1 << chndx; putreg32(bit, EFM32_DMA_IFC); /* Disable the channel */ putreg32(bit, EFM32_DMA_CHENC); break; } } sem_post(&g_dmac.exclsem); /* Since we have reserved a DMA descriptor by taking a count from chansem, * it would be a serious logic failure if we could not find a free channel * for our use. */ DEBUGASSERT(dmach); return (DMA_HANDLE)dmach; } /**************************************************************************** * Name: efm32_dmafree * * Description: * Release a DMA channel. If another thread is waiting for this DMA channel * in a call to efm32_dmachannel, then this function will re-assign the * DMA channel to that thread and wake it up. NOTE: The 'handle' used * in this argument must NEVER be used again until efm32_dmachannel() is * called again to re-gain access to the channel. * * Returned Value: * None * * Assumptions: * - The caller holds the DMA channel. * - There is no DMA in progress * ****************************************************************************/ void efm32_dmafree(DMA_HANDLE handle) { struct dma_channel_s *dmach = (struct dma_channel_s *)handle; DEBUGASSERT(dmach != NULL && dmach->inuse); dmavdbg("DMA channel %d\n", dmach->chan); /* Disable the channel */ putreg32(1 << dmach->chan, EFM32_DMA_CHENC); /* Mark the channel no longer in use. Clearing the in-use flag is an atomic * operation and so should be safe. */ dmach->inuse = false; /* And increment the count of free channels... possibly waking up a * thread that may be waiting for a channel. */ sem_post(&g_dmac.chansem); } /**************************************************************************** * Name: efm32_rxdmasetup * * Description: * Configure an RX (peripheral-to-memory) DMA before starting the transfer. * * Input Parameters: * paddr - Peripheral address (source) * maddr - Memory address (destination) * nbytes - Number of bytes to transfer. Must be an even multiple of the * configured transfer size. * config - Channel configuration selections * ****************************************************************************/ void efm32_rxdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr, size_t nbytes, dma_config_t config) { struct dma_channel_s *dmach = (struct dma_channel_s *)handle; struct dma_descriptor_s *desc; unsigned int xfersize; unsigned int shift; uint32_t regval; uint32_t incr; uint32_t mask; DEBUGASSERT(dmach != NULL && dmach->inuse); /* Get the properly alignment shift and mask */ shift = efm32_align_shift(config); mask = ALIGN_MASK(shift); /* Make sure that the number of bytes we are asked to transfer is a multiple * of the transfer size. */ xfersize = (1 << shift); nbytes = ALIGN_DOWN(nbytes, mask); DEBUGASSERT(nbytes > 0); /* Save the configuration (for efm32_dmastart()). */ dmach->config = config; /* Configure for the selected peripheral */ efm32_set_chctrl(dmach, config); /* Configure the primary channel descriptor */ desc = efm32_get_descriptor(dmach, false); desc->srcend = (uint32_t *)paddr; desc->dstend = (uint32_t *)(maddr + nbytes - xfersize); /* No source increment, destination increments according to transfer size. * No privileges. Arbitrate after each transfer. Default priority. */ regval = DMA_CTRL_SRC_INC_NONE | DMA_CTRL_DST_PROT_NON_PRIVILEGED | DMA_CTRL_SRC_PROT_NON_PRIVILEGED | DMA_CTRL_R_POWER_1 | (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) | _DMA_CTRL_CYCLE_CTRL_BASIC; switch (shift) { default: case 0: /* Byte transfer */ regval |= DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE; incr = DMA_CTRL_DST_INC_BYTE; break; case 1: /* Half word transfer */ regval |= DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD; incr = DMA_CTRL_DST_INC_HALFWORD; break; case 2: /* Word transfer */ regval |= DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD; incr = DMA_CTRL_DST_INC_WORD; break; } /* Do we need to increment the memory address? */ if ((config & EFM32_DMA_MEMINCR_MASK) == EFM32_DMA_MEMINCR) { regval |= incr; } /* Set the number of transfers (minus 1) */ DEBUGASSERT((nbytes >> shift) < 1024); regval |= ((nbytes >> shift) - 1) << _DMA_CTRL_N_MINUS_1_SHIFT; desc->ctrl = regval; desc->user = 0; } /**************************************************************************** * Name: efm32_txdmasetup * * Description: * Configure an TX (memory-to-memory) DMA before starting the transfer. * * Input Parameters: * paddr - Peripheral address (destination) * maddr - Memory address (source) * nbytes - Number of bytes to transfer. Must be an even multiple of the * configured transfer size. * config - Channel configuration selections * ****************************************************************************/ void efm32_txdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr, size_t nbytes, dma_config_t config) { struct dma_channel_s *dmach = (struct dma_channel_s *)handle; struct dma_descriptor_s *desc; unsigned int xfersize; unsigned int shift; uint32_t regval; uint32_t incr; uint32_t mask; DEBUGASSERT(dmach != NULL && dmach->inuse); /* Get the properly alignment shift and mask */ shift = efm32_align_shift(config); mask = ALIGN_MASK(shift); /* Make sure that the number of bytes we are asked to transfer is a multiple * of the transfer size. */ xfersize = (1 << shift); nbytes = ALIGN_DOWN(nbytes, mask); DEBUGASSERT(nbytes > 0); /* Save the configuration (for efm32_dmastart()). */ dmach->config = config; /* Configure for the selected peripheral */ efm32_set_chctrl(dmach, config); /* Configure the primary channel descriptor */ desc = efm32_get_descriptor(dmach, false); desc->srcend = (uint32_t *)(maddr + nbytes - xfersize); desc->dstend = (uint32_t *)paddr; /* No destination increment, source increments according to transfer size. * No privileges. Arbitrate after each transfer. Default priority. */ regval = DMA_CTRL_DST_INC_NONE | DMA_CTRL_DST_PROT_NON_PRIVILEGED | DMA_CTRL_SRC_PROT_NON_PRIVILEGED | DMA_CTRL_R_POWER_1 | (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) | _DMA_CTRL_CYCLE_CTRL_BASIC; switch (shift) { default: case 0: /* Byte transfer */ regval |= DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE; incr = DMA_CTRL_SRC_INC_BYTE; break; case 1: /* Half word transfer */ regval |= DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD; incr = DMA_CTRL_SRC_INC_HALFWORD; break; case 2: /* Word transfer */ regval |= DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD; incr = DMA_CTRL_SRC_INC_WORD; break; } /* Do we need to increment the memory address? */ if ((config & EFM32_DMA_MEMINCR_MASK) == EFM32_DMA_MEMINCR) { regval |= incr; } /* Set the number of transfers (minus 1) */ DEBUGASSERT((nbytes >> shift) < 1024); regval |= ((nbytes >> shift) - 1) << _DMA_CTRL_N_MINUS_1_SHIFT; desc->ctrl = regval; desc->user = 0; } /**************************************************************************** * Name: efm32_dmastart * * Description: * Start the DMA transfer * * Assumptions: * - DMA handle allocated by efm32_dmachannel() * - No DMA in progress * ****************************************************************************/ void efm32_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg) { struct dma_channel_s *dmach = (struct dma_channel_s *)handle; irqstate_t flags; uint32_t regval; uint32_t bit; DEBUGASSERT(dmach && dmach->inuse && dmach->desc); /* Save the DMA complete callback info */ dmach->callback = callback; dmach->arg = arg; /* Finish configuring the channel */ bit = 1 << dmach->chan; if ((dmach->config & EFM32_DMA_SINGLE_MASK) == EFM32_DMA_BUFFER_FULL) { /* Disable the single requests for the channel (i.e. do not react to data * available, wait for buffer full) */ putreg32(bit, EFM32_DMA_CHUSEBURSTS); /* Enable buffer-full requests for the channel */ putreg32(bit, EFM32_DMA_CHREQMASKC); } else { /* Enable the single requests for the channel */ putreg32(bit, EFM32_DMA_CHUSEBURSTC); /* Disable buffer-full requests for the channel */ putreg32(bit, EFM32_DMA_CHREQMASKS); } /* Use the primary data structure for the channel */ putreg32(bit, EFM32_DMA_CHALTC); /* Enable DMA completion interrupts */ flags = irqsave(); regval = getreg32(EFM32_DMA_IEN); regval |= bit; putreg32(bit, EFM32_DMA_IEN); /* Enable the channel */ putreg32(bit, EFM32_DMA_CHENS); irqrestore(flags); } /**************************************************************************** * Name: efm32_dmastop * * Description: * Cancel the DMA. After efm32_dmastop() is called, the DMA channel is * reset and efm32_dmasetup() must be called before efm32_dmastart() can be * called again * * Assumptions: * - DMA handle allocated by efm32_dmachannel() * ****************************************************************************/ void efm32_dmastop(DMA_HANDLE handle) { struct dma_channel_s *dmach = (struct dma_channel_s *)handle; irqstate_t flags; uint32_t regval; uint32_t bit; DEBUGASSERT(dmach && dmach); bit = 1 << dmach->chan; /* Disable the channel */ flags = irqsave(); putreg32(bit, EFM32_DMA_CHENC); /* Disable Channel interrupts */ regval = getreg32(EFM32_DMA_IEN); regval |= bit; putreg32(bit, EFM32_DMA_IEN); irqrestore(flags); } /**************************************************************************** * Name: efm32_dmasample * * Description: * Sample DMA register contents * * Assumptions: * - DMA handle allocated by efm32_dmachannel() * ****************************************************************************/ #ifdef CONFIG_DEBUG_DMA void efm32_dmasample(DMA_HANDLE handle, struct efm32_dmaregs_s *regs) { struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle; uintptr_t regaddr; irqstate_t flags; /* Sample DMA registers. */ flags = irqsave(); regs->status = getreg32(EFM32_DMA_STATUS); regs->ctrlbase = getreg32(EFM32_DMA_CTRLBASE); regs->altctrlbase = getreg32(EFM32_DMA_ALTCTRLBASE); regs->chwaitstatus = getreg32(EFM32_DMA_CHWAITSTATUS); regs->chusebursts = getreg32(EFM32_DMA_CHUSEBURSTS); regs->chreqmasks = getreg32(EFM32_DMA_CHREQMASKS); regs->chens = getreg32(EFM32_DMA_CHENS); regs->chalts = getreg32(EFM32_DMA_CHALTS); regs->chpris = getreg32(EFM32_DMA_CHPRIS); regs->errorc = getreg32(EFM32_DMA_ERRORC); regs->chreqstatus = getreg32(EFM32_DMA_CHREQSTATUS); regs->chsreqstatus = getreg32(EFM32_DMA_CHSREQSTATUS); regs->ifr = getreg32(EFM32_DMA_IF); regs->ien = getreg32(EFM32_DMA_IEN); #if defined(CONFIG_EFM32_EFM32GG) regs->ctrl = getreg32(EFM32_DMA_CTRL); regs->rds = getreg32(EFM32_DMA_RDS); regs->loop0 = getreg32(EFM32_DMA_LOOP0); regs->loop1 = getreg32(EFM32_DMA_LOOP1); regs->rect0 = getreg32(EFM32_DMA_RECT0); #endif /* Sample channel control register */ regaddr = EFM32_DMA_CHn_CTRL(dmach->chan) regs->chnctrl = getreg32(regaddr); irqrestore(flags); } #endif /**************************************************************************** * Name: efm32_dmadump * * Description: * Dump previously sampled DMA register contents * * Assumptions: * - DMA handle allocated by efm32_dmachannel() * ****************************************************************************/ #ifdef CONFIG_DEBUG_DMA void efm32_dmadump(DMA_HANDLE handle, const struct efm32_dmaregs_s *regs, const char *msg) { struct sam_xdmach_s *xdmach = (struct sam_xdmach_s *)handle; struct sam_xdmac_s *xdmac = sam_controller(xdmach); dmadbg("%s\n", msg); dmadbg(" DMA Registers:\n"); dmadbg(" STATUS: %08x\n", regs->status); dmadbg(" CTRLBASE: %08x\n", regs->ctrlbase); dmadbg(" ALTCTRLBASE: %08x\n", regs->altctrlbase); dmadbg(" CHWAITSTATUS: %08x\n", regs->chwaitstatus); dmadbg(" CHUSEBURSTS: %08x\n", regs->chusebursts); dmadbg(" CHREQMASKS: %08x\n", regs->chreqmasks); dmadbg(" CHENS: %08x\n", regs->chens); dmadbg(" CHALTS: %08x\n", regs->chalts); dmadbg(" CHPRIS: %08x\n", regs->chpris); dmadbg(" ERRORC: %08x\n", regs->errorc); dmadbg(" CHREQSTATUS: %08x\n", regs->chreqstatus); dmadbg(" CHSREQSTATUS: %08x\n", regs->chsreqstatus); dmadbg(" IEN: %08x\n", regs->ien); #if defined(CONFIG_EFM32_EFM32GG) dmadbg(" CTRL: %08x\n", regs->ctrl); dmadbg(" RDS: %08x\n", regs->rds); dmadbg(" LOOP0: %08x\n", regs->loop0); dmadbg(" LOOP1: %08x\n", regs->loop1); dmadbg(" RECT0: %08x\n", regs->rect0); #endif dmadbg(" DMA Channel %d Registers:\n", dmach->chan); dmadbg(" CHCTRL: %08x\n", regs->chnctrl); } #endif