/**************************************************************************** * arch/arm/src/sama5/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 #include #include #include #include #include #include #include #include #include #include #include "arm_internal.h" #include "sched/sched.h" #include "chip.h" #include "sam_dmac.h" #include "sam_periphclks.h" #include "sam_memories.h" #include "hardware/sam_pmc.h" #include "hardware/sam_dmac.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ /* All of the currently supported SAMA5 chips support two DMA controllers * of 8 DMA Channels each. */ #if SAM_NDMAC < 1 # undef CONFIG_SAMA5_DMAC1 # undef CONFIG_SAMA5_DMAC0 #elif SAM_NDMAC < 2 # undef CONFIG_SAMA5_DMAC1 #endif /* Condition out the whole file unless DMA is selected in the configuration */ #if defined(CONFIG_SAMA5_DMAC0) || defined(CONFIG_SAMA5_DMAC1) /* If SAMA5 DMA support is enabled, then OS DMA support should also be * enabled */ #ifndef CONFIG_ARCH_DMA # warning "SAMA5 DMA enabled but CONFIG_ARCH_DMA disabled" #endif /* Check the number of link list descriptors to allocate */ #ifndef CONFIG_SAMA5_NLLDESC # define CONFIG_SAMA5_NLLDESC SAM_NDMACHAN #endif #if CONFIG_SAMA5_NLLDESC < SAM_NDMACHAN # warning "At least SAM_NDMACHAN descriptors must be allocated" # undef CONFIG_SAMA5_NLLDESC # define CONFIG_SAMA5_NLLDESC SAM_NDMACHAN #endif /* Register values **********************************************************/ #define DMAC_CH_CTRLB_BOTHDSCR \ (DMAC_CH_CTRLB_SRCDSCR | DMAC_CH_CTRLB_DSTDSCR) /**************************************************************************** * Private Types ****************************************************************************/ /* This structure maps a peripheral ID to an DMA channel */ struct sam_pidmap_s { uint8_t pid; /* Peripheral identifier */ uint8_t pchan; /* DMA channel */ }; /* This structure describes one DMA channel */ struct sam_dmach_s { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) uint8_t dmac; /* DMA controller number (0-1) */ #endif uint8_t chan; /* DMA channel number (0-6) */ bool inuse; /* TRUE: The DMA channel is in use */ bool rx; /* TRUE: Peripheral to memory transfer */ uint32_t flags; /* DMA channel flags */ uint32_t base; /* DMA register channel base address */ uint32_t cfg; /* Pre-calculated CFG register for transfer */ dma_callback_t callback; /* Callback invoked when the DMA completes */ void *arg; /* Argument passed to callback function */ uint32_t rxaddr; /* RX memory address */ size_t rxsize; /* Size of RX memory region */ struct dma_linklist_s *llhead; /* DMA link list head */ struct dma_linklist_s *lltail; /* DMA link list head */ }; /* This structure describes the state of one DMA controller */ struct sam_dmac_s { /* These mutex protect the DMA channel and descriptor tables */ mutex_t chlock; /* Protects channel table */ sem_t dsem; /* Protects descriptor table */ uint32_t base; /* DMA register channel base address */ /* This array describes the available link list descriptors */ struct dma_linklist_s *desc; /* This array describes each DMA channel */ struct sam_dmach_s *dmach; }; /**************************************************************************** * Private Data ****************************************************************************/ /* CTRLA field lookups */ static const uint32_t g_srcwidth[4] = { DMAC_CH_CTRLA_SRCWIDTH_BYTE, DMAC_CH_CTRLA_SRCWIDTH_HWORD, DMAC_CH_CTRLA_SRCWIDTH_WORD, DMAC_CH_CTRLA_SRCWIDTH_DWORD }; static const uint32_t g_destwidth[4] = { DMAC_CH_CTRLA_DSTWIDTH_BYTE, DMAC_CH_CTRLA_DSTWIDTH_HWORD, DMAC_CH_CTRLA_DSTWIDTH_WORD, DMAC_CH_CTRLA_DSTWIDTH_DWORD }; static const uint32_t g_fifocfg[3] = { DMAC_CH_CFG_FIFOCFG_ALAP, DMAC_CH_CFG_FIFOCFG_HALF, DMAC_CH_CFG_FIFOCFG_ASAP }; /* These tables map peripheral IDs to channels. A lookup is performed * before each DMA transfer in order to map the peripheral IDs to the * correct channel. This must be done because the channel can change with * the direction of the transfer. */ #ifdef CONFIG_SAMA5_DMAC0 /* DMA controller 0, RX DMA: */ static const struct sam_pidmap_s g_dmac0_rxchan[] = { { SAM_PID_HSMCI0, DMAC0_CH_HSMCI0 }, /* HSMCI0 Receive/transmit */ { SAM_PID_SPI0, DMAC0_CH_SPI0_RX }, /* SPI0 Receive */ { SAM_PID_USART0, DMAC0_CH_USART0_RX }, /* USART0 Receive */ { SAM_PID_USART1, DMAC0_CH_USART1_RX }, /* USART1 Receive */ { SAM_PID_TWI0, DMAC0_CH_TWI0_RX }, /* TWI0 Receive */ { SAM_PID_TWI1, DMAC0_CH_TWI1_RX }, /* TWI1 Receive */ { SAM_PID_UART0, DMAC0_CH_UART0_RX }, /* UART0 Receive */ { SAM_PID_SSC0, DMAC0_CH_SSC0_RX }, /* SSC0 Receive */ { SAM_PID_SMD, DMAC0_CH_SMD_RX }, /* SMD Receive */ }; #define NDMAC0_RXCHANNELS (sizeof(g_dmac0_rxchan) / sizeof(struct sam_pidmap_s)) /* DMA controller 0, TX DMA: */ static const struct sam_pidmap_s g_dmac0_txchan[] = { { SAM_PID_HSMCI0, DMAC0_CH_HSMCI0 }, /* HSMCI0 Receive/transmit */ { SAM_PID_SPI0, DMAC0_CH_SPI0_TX }, /* SPI0 Transmit */ { SAM_PID_USART0, DMAC0_CH_USART0_TX }, /* USART0 Transmit */ { SAM_PID_USART1, DMAC0_CH_USART1_TX }, /* USART1 Transmit */ { SAM_PID_TWI0, DMAC0_CH_TWI0_TX }, /* TWI0 Transmit */ { SAM_PID_TWI1, DMAC0_CH_TWI1_TX }, /* TWI1 Transmit */ { SAM_PID_UART0, DMAC0_CH_UART0_TX }, /* UART0 Transmit */ { SAM_PID_SSC0, DMAC0_CH_SSC0_TX }, /* SSC0 Transmit */ { SAM_PID_SMD, DMAC0_CH_SMD_TX }, /* SMD Transmit */ }; #define NDMAC0_TXCHANNELS (sizeof(g_dmac0_txchan) / sizeof(struct sam_pidmap_s)) #endif #ifdef CONFIG_SAMA5_DMAC1 /* DMA controller 1, RX DMA: */ static const struct sam_pidmap_s g_dmac1_rxchan[] = { { SAM_PID_HSMCI1, DMAC1_CH_HSMCI1 }, /* HSMCI1 Receive/transmit */ { SAM_PID_HSMCI2, DMAC1_CH_HSMCI2 }, /* HSMCI2 Receive/transmit */ { SAM_PID_ADC, DMAC1_CH_ADC_RX }, /* ADC Receive */ { SAM_PID_SSC1, DMAC1_CH_SSC1_RX }, /* SSC1 Receive */ { SAM_PID_UART1, DMAC1_CH_UART1_RX }, /* UART1 Receive */ { SAM_PID_USART2, DMAC1_CH_USART2_RX }, /* USART2 Receive */ { SAM_PID_USART3, DMAC1_CH_USART3_RX }, /* USART3 Receive */ { SAM_PID_TWI2, DMAC1_CH_TWI2_RX }, /* TWI2 Receive */ { SAM_PID_DBGU, DMAC1_CH_DBGU_RX }, /* DBGU Receive */ { SAM_PID_SPI1, DMAC1_CH_SPI1_RX }, /* SPI1 Receive */ { SAM_PID_AES, DMAC1_CH_AES_RX }, /* AES Receive */ { SAM_PID_TDES, DMAC1_CH_TDES_RX }, /* TDES Receive */ }; #define NDMAC1_RXCHANNELS (sizeof(g_dmac1_rxchan) / sizeof(struct sam_pidmap_s)) /* DMA controller 1, TX DMA: */ static const struct sam_pidmap_s g_dmac1_txchan[] = { { SAM_PID_HSMCI1, DMAC1_CH_HSMCI1 }, /* HSMCI1 Receive/transmit */ { SAM_PID_HSMCI2, DMAC1_CH_HSMCI2 }, /* HSMCI2 Receive/transmit */ { SAM_PID_SSC1, DMAC1_CH_SSC1_TX }, /* SSC1 Transmit */ { SAM_PID_UART1, DMAC1_CH_UART1_TX }, /* UART1 Transmit */ { SAM_PID_USART2, DMAC1_CH_USART2_TX }, /* USART2 Transmit */ { SAM_PID_USART3, DMAC1_CH_USART3_TX }, /* USART3 Transmit */ { SAM_PID_TWI2, DMAC1_CH_TWI2_TX }, /* TWI2 Transmit */ { SAM_PID_DBGU, DMAC1_CH_DBGU_TX }, /* DBGU Transmit */ { SAM_PID_SPI1, DMAC1_CH_SPI1_TX }, /* SPI1 Transmit */ { SAM_PID_SHA, DMAC1_CH_SHA_TX }, /* SHA Transmit */ { SAM_PID_AES, DMAC1_CH_AES_TX }, /* AES Transmit */ { SAM_PID_TDES, DMAC1_CH_TDES_TX }, /* TDES Transmit */ }; #define NDMAC1_TXCHANNELS (sizeof(g_dmac1_txchan) / sizeof(struct sam_pidmap_s)) #endif #ifdef CONFIG_SAMA5_DMAC0 /* This array describes the available link list descriptors */ struct dma_linklist_s g_desc0[CONFIG_SAMA5_NLLDESC]; /* This array describes the state of each DMAC0 channel 0 */ static struct sam_dmach_s g_dmach0[SAM_NDMACHAN] = { #if SAM_NDMACHAN > 0 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 0, .base = SAM_DMAC0_CH0_BASE, }, #endif #if SAM_NDMACHAN > 1 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 1, .base = SAM_DMAC0_CH1_BASE, }, #endif #if SAM_NDMACHAN > 2 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 2, .base = SAM_DMAC0_CH2_BASE, }, #endif #if SAM_NDMACHAN > 3 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 3, .base = SAM_DMAC0_CH3_BASE, }, #endif #if SAM_NDMACHAN > 4 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 4, .base = SAM_DMAC0_CH4_BASE, }, #endif #if SAM_NDMACHAN > 5 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 5, .base = SAM_DMAC0_CH5_BASE, }, #endif #if SAM_NDMACHAN > 6 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 6, .base = SAM_DMAC0_CH6_BASE, }, #endif #if SAM_NDMACHAN > 7 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 0, #endif .chan = 7, .base = SAM_DMAC0_CH7_BASE, } #endif }; /* This describes the overall state of DMA controller 0 */ static struct sam_dmac_s g_dmac0 = { .chlock = NXMUTEX_INITIALIZER, .dsem = SEM_INITIALIZER(SAM_NDMACHAN), /* DMAC 0 base address */ .base = SAM_DMAC0_VBASE, /* This array describes the available link list descriptors */ .desc = g_desc0, /* This array describes each DMA channel */ .dmach = g_dmach0, }; #endif /* CONFIG_SAMA5_DMAC0 */ /* This array describes the state of DMA controller 1 */ #ifdef CONFIG_SAMA5_DMAC1 /* This array describes the available link list descriptors */ struct dma_linklist_s g_desc1[CONFIG_SAMA5_NLLDESC]; static struct sam_dmach_s g_dmach1[SAM_NDMACHAN] = { #if SAM_NDMACHAN > 0 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 0, .base = SAM_DMAC1_CH0_BASE, }, #endif #if SAM_NDMACHAN > 1 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 1, .base = SAM_DMAC1_CH1_BASE, }, #endif #if SAM_NDMACHAN > 2 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 2, .base = SAM_DMAC1_CH2_BASE, }, #endif #if SAM_NDMACHAN > 3 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 3, .base = SAM_DMAC1_CH3_BASE, }, #endif #if SAM_NDMACHAN > 4 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 4, .base = SAM_DMAC1_CH4_BASE, }, #endif #if SAM_NDMACHAN > 5 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 5, .base = SAM_DMAC1_CH5_BASE, }, #endif #if SAM_NDMACHAN > 6 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 6, .base = SAM_DMAC1_CH6_BASE, }, #endif #if SAM_NDMACHAN > 7 { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) .dmac = 1, #endif .chan = 7, .base = SAM_DMAC1_CH7_BASE, } #endif }; /* This describes the overall state of DMA controller 1 */ static struct sam_dmac_s g_dmac1 = { .chlock = NXMUTEX_INITIALIZER, .dsem = SEM_INITIALIZER(SAM_NDMACHAN), /* DMAC 0 base address */ .base = SAM_DMAC1_VBASE, /* This array describes the available link list descriptors */ .desc = g_desc1, /* This array describes each DMA channel */ .dmach = g_dmach1, }; #endif /* CONFIG_SAMA5_DMAC1 */ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: sam_getdmac * * Description: * Read a global DMAC register * ****************************************************************************/ static inline uint32_t sam_getdmac(struct sam_dmac_s *dmac, unsigned int offset) { return getreg32(dmac->base + offset); } /**************************************************************************** * Name: sam_putdmac * * Description: * Write a value to a global DMAC register * ****************************************************************************/ static inline void sam_putdmac(struct sam_dmac_s *dmac, uint32_t value, unsigned int offset) { putreg32(value, dmac->base + offset); } /**************************************************************************** * Name: sam_getdmach * * Description: * Read a DMAC channel register * ****************************************************************************/ static inline uint32_t sam_getdmach(struct sam_dmach_s *dmach, unsigned int offset) { return getreg32(dmach->base + offset); } /**************************************************************************** * Name: sam_putdmach * * Description: * Write a value to a DMAC channel register * ****************************************************************************/ static inline void sam_putdmach(struct sam_dmach_s *dmach, uint32_t value, unsigned int offset) { putreg32(value, dmach->base + offset); } /**************************************************************************** * Name: sam_controller * * Description: * Given a DMA channel instance, return a pointer to the parent DMA * controller instance. * ****************************************************************************/ static inline struct sam_dmac_s *sam_controller(struct sam_dmach_s *dmach) { #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) return dmach->dmac ? &g_dmac1 : &g_dmac0; #elif defined(CONFIG_SAMA5_DMAC0) return &g_dmac0; #else return &g_dmac1; #endif } /**************************************************************************** * Name: sam_fifocfg * * Description: * Decode the FIFO config from the flags * ****************************************************************************/ static inline uint32_t sam_fifocfg(struct sam_dmach_s *dmach) { unsigned int ndx; ndx = (dmach->flags & DMACH_FLAG_FIFOCFG_MASK) >> DMACH_FLAG_FIFOCFG_SHIFT; DEBUGASSERT(ndx < 3); return g_fifocfg[ndx]; } /**************************************************************************** * Name: sam_channel, sam_source_channel, and sam_sink_channel * * Description: * Return the RX or TX channel associated with a PID. As a clarification: * * The source channel refers to the source of data for the DMA. This is, * either (1) memory for a TX DMA or (2) a peripheral register for an RX * DMA. * * The sink channel is the recipient of the DMA data. This is either * (1) memory for an RX DMA, or (2) a peripheral register for a TX DMA. * ****************************************************************************/ static uint8_t sam_channel(uint8_t pid, const struct sam_pidmap_s *table, unsigned int nentries) { int i; /* Search until either the entry with the matching PID is found or until * all of the table entries have been examined without finding the PID. */ for (i = 0; i < nentries; i++, table++) { if (table->pid == pid) { return table->pchan; } } dmaerr("ERROR: No channel found for pid %d\n", pid); DEBUGPANIC(); return 0x3f; } static uint32_t sam_source_channel(struct sam_dmach_s *dmach, uint8_t pid, bool isperiph) { const struct sam_pidmap_s *table; unsigned int nentries; if (!isperiph) { /* The source is memory, not a peripheral. */ return 0x3f; } else #ifdef CONFIG_SAMA5_DMAC0 #ifdef CONFIG_SAMA5_DMAC1 if (dmach->dmac == 0) #endif { /* Use the DMAC0 lookup table */ table = g_dmac0_rxchan; nentries = NDMAC0_RXCHANNELS; } #endif #ifdef CONFIG_SAMA5_DMAC1 #ifdef CONFIG_SAMA5_DMAC0 else #endif { /* Use the DMAC1 lookup table */ table = g_dmac1_rxchan; nentries = NDMAC1_RXCHANNELS; } #endif return (uint32_t)sam_channel(pid, table, nentries); } static uint32_t sam_sink_channel(struct sam_dmach_s *dmach, uint8_t pid, bool isperiph) { const struct sam_pidmap_s *table; unsigned int nentries; if (!isperiph) { /* The source is memory, not a peripheral. */ return 0x3f; } else #ifdef CONFIG_SAMA5_DMAC0 #ifdef CONFIG_SAMA5_DMAC1 if (dmach->dmac == 0) #endif { /* Use the DMAC0 lookup table */ table = g_dmac0_txchan; nentries = NDMAC0_TXCHANNELS; } #endif #ifdef CONFIG_SAMA5_DMAC1 #ifdef CONFIG_SAMA5_DMAC0 else #endif { /* Use the DMAC1 lookup table */ table = g_dmac1_txchan; nentries = NDMAC1_TXCHANNELS; } #endif return (uint32_t)sam_channel(pid, table, nentries); } /**************************************************************************** * Name: sam_txcfg * * Description: * Decode the flags to get the correct CFG register bit settings for * a transmit (memory to peripheral) transfer. * ****************************************************************************/ static inline uint32_t sam_txcfg(struct sam_dmach_s *dmach) { uint32_t regval; unsigned int pid; unsigned int pchan; bool isperiph; /* Set transfer (memory to peripheral) DMA channel configuration register */ regval = DMAC_CH_CFG_SOD; pid = (dmach->flags & DMACH_FLAG_MEMPID_MASK) >> DMACH_FLAG_MEMPID_SHIFT; isperiph = ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0); pchan = sam_source_channel(dmach, pid, isperiph); regval |= ((pchan & 0x0f) << DMAC_CH_CFG_SRCPER_SHIFT); regval |= ((pchan & 0x30) << (DMAC_CH_CFG_SRCPERMSB_SHIFT - 4)); regval |= (dmach->flags & DMACH_FLAG_MEMH2SEL) != 0 ? DMAC_CH_CFG_SRCH2SEL : 0; pid = (dmach->flags & DMACH_FLAG_PERIPHPID_MASK) >> DMACH_FLAG_PERIPHPID_SHIFT; isperiph = ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0); pchan = sam_sink_channel(dmach, pid, isperiph); regval |= ((pchan & 0x0f) << DMAC_CH_CFG_DSTPER_SHIFT); regval |= ((pchan & 0x30) << (DMAC_CH_CFG_DSTPERMSB_SHIFT - 4)); regval |= (dmach->flags & DMACH_FLAG_PERIPHH2SEL) != 0 ? DMAC_CH_CFG_DSTH2SEL : 0; regval |= sam_fifocfg(dmach); return regval; } /**************************************************************************** * Name: sam_rxcfg * * Description: * Decode the flags to get the correct CFG register bit settings for * a receive (peripheral to memory) transfer. * ****************************************************************************/ static inline uint32_t sam_rxcfg(struct sam_dmach_s *dmach) { uint32_t regval; unsigned int pid; unsigned int pchan; bool isperiph; /* Set received (peripheral to memory) DMA channel config */ regval = DMAC_CH_CFG_SOD; pid = (dmach->flags & DMACH_FLAG_PERIPHPID_MASK) >> DMACH_FLAG_PERIPHPID_SHIFT; isperiph = ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0); pchan = sam_source_channel(dmach, pid, isperiph); regval |= ((pchan & 0x0f) << DMAC_CH_CFG_SRCPER_SHIFT); regval |= ((pchan & 0x30) << (DMAC_CH_CFG_SRCPERMSB_SHIFT - 4)); regval |= (dmach->flags & DMACH_FLAG_PERIPHH2SEL) != 0 ? DMAC_CH_CFG_SRCH2SEL : 0; pid = (dmach->flags & DMACH_FLAG_MEMPID_MASK) >> DMACH_FLAG_MEMPID_SHIFT; isperiph = ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0); pchan = sam_sink_channel(dmach, pid, isperiph); regval |= ((pchan & 0x0f) << DMAC_CH_CFG_DSTPER_SHIFT); regval |= ((pchan & 0x30) << (DMAC_CH_CFG_DSTPERMSB_SHIFT - 4)); regval |= (dmach->flags & DMACH_FLAG_MEMH2SEL) != 0 ? DMAC_CH_CFG_DSTH2SEL : 0; regval |= sam_fifocfg(dmach); return regval; } /**************************************************************************** * Name: sam_txctrlabits * * Description: * Decode the flags to get the correct CTRLA register bit settings for * a transmit (memory to peripheral) transfer. These are only the "fixed" * CTRLA values and need to be updated with the actual transfer size before * being written to CTRLA sam_txctrla). * ****************************************************************************/ static inline uint32_t sam_txctrlabits(struct sam_dmach_s *dmach) { uint32_t regval; unsigned int ndx; unsigned int chunksize; DEBUGASSERT(dmach); /* Since this is a transmit, the source is described by the memory * selections. Set the source width (memory width). */ ndx = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK) >> DMACH_FLAG_MEMWIDTH_SHIFT; DEBUGASSERT(ndx < 4); regval = g_srcwidth[ndx]; /* Set the source chunk size (memory chunk size) */ chunksize = (dmach->flags & DMACH_FLAG_MEMCHUNKSIZE_MASK) >> DMACH_FLAG_MEMCHUNKSIZE_SHIFT; regval |= chunksize << DMAC_CH_CTRLA_SCSIZE_SHIFT; /* Since this is a transmit, the destination is described by the peripheral * selections. Set the destination width (peripheral width). */ ndx = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK) >> DMACH_FLAG_PERIPHWIDTH_SHIFT; DEBUGASSERT(ndx < 4); regval |= g_destwidth[ndx]; /* Set the destination chunk size (peripheral chunk size) */ chunksize = (dmach->flags & DMACH_FLAG_PERIPHCHUNKSIZE_MASK) >> DMACH_FLAG_PERIPHCHUNKSIZE_SHIFT; regval |= chunksize << DMAC_CH_CTRLA_DCSIZE_SHIFT; return regval; } /**************************************************************************** * Name: sam_maxtxtransfer * * Description: * Maximum number of bytes that can be sent in on transfer * ****************************************************************************/ static size_t sam_maxtxtransfer(struct sam_dmach_s *dmach) { unsigned int srcwidth; size_t maxtransfer; /* Get the maximum transfer size in bytes. BTSIZE is "the number of * transfers to be performed, that is, for writes it refers to the number * of source width transfers to perform when DMAC is flow controller. For * Reads, BTSIZE refers to the number of transfers completed on the Source * Interface. ..." */ srcwidth = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK) >> DMACH_FLAG_MEMWIDTH_SHIFT; switch (srcwidth) { default: case 0: /* 8 bits, 1 byte */ maxtransfer = DMAC_CH_CTRLA_BTSIZE_MAX; break; case 1: /* 16 bits, 2 bytes */ maxtransfer = 2 * DMAC_CH_CTRLA_BTSIZE_MAX; break; case 2: /* 32 bits 4 bytes */ maxtransfer = 4 * DMAC_CH_CTRLA_BTSIZE_MAX; break; case 3: /* 64 bits, 8 bytes */ maxtransfer = 8 * DMAC_CH_CTRLA_BTSIZE_MAX; break; } return maxtransfer; } /**************************************************************************** * Name: sam_ntxtransfers * * Description: * Number of TX transfers via DMA * ****************************************************************************/ static uint32_t sam_ntxtransfers(struct sam_dmach_s *dmach, uint32_t dmasize) { unsigned int srcwidth; /* Adjust the source transfer size for the source chunk size (memory * chunk size). BTSIZE is "the number of transfers to be performed, that * is, for writes it refers to the number of source width transfers * to perform when DMAC is flow controller. For Reads, BTSIZE refers to * the number of transfers completed on the Source Interface. ..." */ srcwidth = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK) >> DMACH_FLAG_MEMWIDTH_SHIFT; switch (srcwidth) { default: case 0: /* 8 bits, 1 byte */ break; case 1: /* 16 bits, 2 bytes */ dmasize = (dmasize + 1) >> 1; break; case 2: /* 32 bits, 4 bytes */ dmasize = (dmasize + 3) >> 2; break; case 3: /* 64 bits, 8 bytes */ dmasize = (dmasize + 7) >> 3; break; } return dmasize; } /**************************************************************************** * Name: sam_txctrla * * Description: * 'OR' in the variable CTRLA bits * ****************************************************************************/ static inline uint32_t sam_txctrla(struct sam_dmach_s *dmach, uint32_t ctrla, uint32_t dmasize) { uint32_t ntransfers; /* Set the buffer transfer size field. This is the number of transfers to * be performed, that is, the number of source width transfers to perform. */ ntransfers = sam_ntxtransfers(dmach, dmasize); DEBUGASSERT(ntransfers <= DMAC_CH_CTRLA_BTSIZE_MAX); return (ctrla & ~DMAC_CH_CTRLA_BTSIZE_MASK) | (ntransfers << DMAC_CH_CTRLA_BTSIZE_SHIFT); } /**************************************************************************** * Name: sam_rxctrlabits * * Description: * Decode the flags to get the correct CTRLA register bit settings for * a read (peripheral to memory) transfer. These are only the "fixed" CTRLA * values and need to be updated with the actual transfer size before being * written to CTRLA sam_rxctrla). * ****************************************************************************/ static inline uint32_t sam_rxctrlabits(struct sam_dmach_s *dmach) { uint32_t regval; unsigned int ndx; unsigned int chunksize; DEBUGASSERT(dmach); /* Since this is a receive, the source is described by the peripheral * selections. Set the source width (peripheral width). */ ndx = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK) >> DMACH_FLAG_PERIPHWIDTH_SHIFT; DEBUGASSERT(ndx < 4); regval = g_srcwidth[ndx]; /* Set the source chunk size (peripheral chunk size) */ chunksize = (dmach->flags & DMACH_FLAG_PERIPHCHUNKSIZE_MASK) >> DMACH_FLAG_PERIPHCHUNKSIZE_SHIFT; regval |= chunksize << DMAC_CH_CTRLA_SCSIZE_SHIFT; /* Since this is a receive, the destination is described by the memory * selections. Set the destination width (memory width). */ ndx = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK) >> DMACH_FLAG_MEMWIDTH_SHIFT; DEBUGASSERT(ndx < 4); regval |= g_destwidth[ndx]; /* Set the destination chunk size (memory chunk size) */ chunksize = (dmach->flags & DMACH_FLAG_MEMCHUNKSIZE_MASK) >> DMACH_FLAG_MEMCHUNKSIZE_SHIFT; regval |= chunksize << DMAC_CH_CTRLA_DCSIZE_SHIFT; return regval; } /**************************************************************************** * Name: sam_maxrxtransfer * * Description: * Maximum number of bytes that can be sent in on transfer * ****************************************************************************/ static size_t sam_maxrxtransfer(struct sam_dmach_s *dmach) { unsigned int srcwidth; size_t maxtransfer; /* Get the maximum transfer size in bytes. BTSIZE is "the number of * transfers to be performed, that is, for writes it refers to the number * of source width transfers to perform when DMAC is flow controller. For * Reads, BTSIZE refers to the number of transfers completed on the Source * Interface. ..." */ srcwidth = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK) >> DMACH_FLAG_PERIPHWIDTH_SHIFT; switch (srcwidth) { default: case 0: /* 8 bits, 1 byte */ maxtransfer = DMAC_CH_CTRLA_BTSIZE_MAX; break; case 1: /* 16 bits, 2 bytes */ maxtransfer = 2 * DMAC_CH_CTRLA_BTSIZE_MAX; break; case 2: /* 32 bits, 4 bytes */ maxtransfer = 4 * DMAC_CH_CTRLA_BTSIZE_MAX; break; case 3: /* 64 bits, 8 bytes */ maxtransfer = 8 * DMAC_CH_CTRLA_BTSIZE_MAX; break; } return maxtransfer; } /**************************************************************************** * Name: sam_nrxtransfers * * Description: * Number of RX transfers via DMA * ****************************************************************************/ static uint32_t sam_nrxtransfers(struct sam_dmach_s *dmach, uint32_t dmasize) { unsigned int srcwidth; /* Adjust the source transfer size for the source chunk size (peripheral * chunk size). BTSIZE is "the number of transfers to be performed, that * is, for writes it refers to the number of source width transfers * to perform when DMAC is flow controller. For Reads, BTSIZE refers to * the number of transfers completed on the Source Interface. ..." */ srcwidth = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK) >> DMACH_FLAG_PERIPHWIDTH_SHIFT; switch (srcwidth) { default: case 0: /* 8 bits, 1 byte */ break; case 1: /* 16 bits, 2 bytes */ dmasize = (dmasize + 1) >> 1; break; case 2: /* 32 bits, 4 bytes */ dmasize = (dmasize + 3) >> 2; break; case 3: /* 64 bits, 8 bytes */ dmasize = (dmasize + 7) >> 3; break; } return dmasize; } /**************************************************************************** * Name: sam_rxctrla * * Description: * 'OR' in the variable CTRLA bits * ****************************************************************************/ static inline uint32_t sam_rxctrla(struct sam_dmach_s *dmach, uint32_t ctrla, uint32_t dmasize) { uint32_t ntransfers; /* Set the buffer transfer size field. This is the number of transfers to * be performed, that is, the number of source width transfers to perform. */ ntransfers = sam_nrxtransfers(dmach, dmasize); DEBUGASSERT(ntransfers <= DMAC_CH_CTRLA_BTSIZE_MAX); return (ctrla & ~DMAC_CH_CTRLA_BTSIZE_MASK) | (ntransfers << DMAC_CH_CTRLA_BTSIZE_SHIFT); } /**************************************************************************** * Name: sam_txctrlb * * Description: * Decode the flags to get the correct CTRLB register bit settings for * a transmit (memory to peripheral) transfer. * ****************************************************************************/ static inline uint32_t sam_txctrlb(struct sam_dmach_s *dmach) { uint32_t regval; unsigned int ahbif; /* Assume that we will not be using the link list and disable the source * and destination descriptors. The default will be single transfer mode. */ regval = DMAC_CH_CTRLB_BOTHDSCR | DMAC_CH_CTRLB_IEN; /* Select flow control (even if the channel doesn't support it). The * naming convention from TX is memory to peripheral, but that is really * be determined by bits in the DMA flags. */ /* Is the memory source really a peripheral? */ if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0) { /* Yes.. is the peripheral destination also a peripheral? */ if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0) { /* Yes.. Use peripheral-to-peripheral flow control */ regval |= DMAC_CH_CTRLB_FC_P2P; } else { /* No.. Use peripheral-to-memory flow control */ regval |= DMAC_CH_CTRLB_FC_P2M; } } else { /* No, the source is memory. Is the peripheral destination a * peripheral */ if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0) { /* Yes.. Use memory-to-peripheral flow control */ regval |= DMAC_CH_CTRLB_FC_M2P; } else { /* No.. Use memory-to-memory flow control */ regval |= DMAC_CH_CTRLB_FC_M2M; } } /* Source ABH layer */ ahbif = (dmach->flags & DMACH_FLAG_MEMAHB_MASK) >> DMACH_FLAG_MEMAHB_SHIFT; regval |= (ahbif << DMAC_CH_CTRLB_SIF_SHIFT); /* Select source address incrementing */ if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) == 0) { regval |= DMAC_CH_CTRLB_SRCINCR_FIXED; } /* Destination ABH layer */ ahbif = (dmach->flags & DMACH_FLAG_PERIPHAHB_MASK) >> DMACH_FLAG_PERIPHAHB_SHIFT; regval |= (ahbif << DMAC_CH_CTRLB_DIF_SHIFT); /* Select destination address incrementing */ if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) == 0) { regval |= DMAC_CH_CTRLB_DSTINCR_FIXED; } return regval; } /**************************************************************************** * Name: sam_rxctrlb * * Description: * Decode the flags to get the correct CTRLB register bit settings for * a receive (peripheral to memory) transfer. * ****************************************************************************/ static inline uint32_t sam_rxctrlb(struct sam_dmach_s *dmach) { uint32_t regval; unsigned int ahbif; /* Assume that we will not be using the link list and disable the source * and destination descriptors. The default will be single transfer mode. */ regval = DMAC_CH_CTRLB_BOTHDSCR | DMAC_CH_CTRLB_IEN; /* Select flow control (even if the channel doesn't support it). The * naming convention from RX is peripheral to memory, but that is really * be determined by bits in the DMA flags. */ /* Is the peripheral source really a peripheral? */ if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0) { /* Yes.. is the memory destination also a peripheral? */ if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0) { /* Yes.. Use peripheral-to-peripheral flow control */ regval |= DMAC_CH_CTRLB_FC_P2P; } else { /* No.. Use peripheral-to-memory flow control */ regval |= DMAC_CH_CTRLB_FC_P2M; } } else { /* No, the peripheral source is memory. Is the memory destination * a peripheral */ if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0) { /* Yes.. Use memory-to-peripheral flow control */ regval |= DMAC_CH_CTRLB_FC_M2P; } else { /* No.. Use memory-to-memory flow control */ regval |= DMAC_CH_CTRLB_FC_M2M; } } /* Source ABH layer */ ahbif = (dmach->flags & DMACH_FLAG_PERIPHAHB_MASK) >> DMACH_FLAG_PERIPHAHB_SHIFT; regval |= (ahbif << DMAC_CH_CTRLB_SIF_SHIFT); /* Select source address incrementing */ if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) == 0) { regval |= DMAC_CH_CTRLB_SRCINCR_FIXED; } /* Destination ABH layer */ ahbif = (dmach->flags & DMACH_FLAG_MEMAHB_MASK) >> DMACH_FLAG_MEMAHB_SHIFT; regval |= (ahbif << DMAC_CH_CTRLB_DIF_SHIFT); /* Select address incrementing */ if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) == 0) { regval |= DMAC_CH_CTRLB_DSTINCR_FIXED; } return regval; } /**************************************************************************** * Name: sam_allocdesc * * Description: * Allocate and add one descriptor to the DMA channel's link list. * * NOTE: link 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_linklist_s * sam_allocdesc(struct sam_dmach_s *dmach, struct dma_linklist_s *prev, uint32_t saddr, uint32_t daddr, uint32_t ctrla, uint32_t ctrlb) { struct sam_dmac_s *dmac = sam_controller(dmach); struct dma_linklist_s *desc = NULL; int i; int ret; /* Sanity check -- saddr == 0 is the indication that the link is unused. * Obviously setting it to zero would break that usage. */ #ifdef CONFIG_DEBUG_FEATURES if (saddr != 0) #endif { /* Table a descriptor table semaphore count. When we get one, then * there is at least one free descriptor in the table and it is ours. */ ret = nxsem_wait_uninterruptible(&dmac->dsem); if (ret < 0) { return NULL; } /* Examine each link list entry to find an available one -- i.e., one * with saddr == 0. That saddr field is set to zero by the DMA * transfer complete interrupt handler. The following should be safe * because that is an atomic operation. */ ret = nxmutex_lock(&dmac->chlock); if (ret < 0) { nxsem_post(&dmac->dsem); return NULL; } for (i = 0; i < CONFIG_SAMA5_NLLDESC; i++) { if (dmac->desc[i].saddr == 0) { /* We have it. Initialize the new link list entry */ desc = &dmac->desc[i]; desc->saddr = saddr; /* Source address */ desc->daddr = daddr; /* Destination address */ desc->ctrla = ctrla; /* Control A value */ desc->ctrlb = ctrlb; /* Control B value */ desc->dscr = 0; /* Next descriptor address */ /* And then hook it at the tail of the link list */ if (!prev) { /* There is no previous link. This is the new head of * the list */ DEBUGASSERT(dmach->llhead == NULL && dmach->lltail == NULL); dmach->llhead = desc; } else { DEBUGASSERT(dmach->llhead != NULL && dmach->lltail == prev); /* When the second link is added to the list, that is the * cue that we are going to do the link list transfer. * * Enable the source and destination descriptor in the link * list entry just before this one. We assume that both * source and destination buffers are non-continuous, but * this should work even if that is not the case. */ prev->ctrlb &= ~DMAC_CH_CTRLB_BOTHDSCR; /* Link the previous tail to the new tail. * REVISIT: This assumes that the next description is * fetched via AHB IF0. */ prev->dscr = (uint32_t)sam_physramaddr((uintptr_t)desc); } /* In any event, this is the new tail of the list. The source * and destination descriptors must be disabled for the last * entry in the link list. */ desc->ctrlb |= DMAC_CH_CTRLB_BOTHDSCR; dmach->lltail = desc; /* Assume that we will be doing multiple buffer transfers and * that hardware will be accessing the descriptor via DMA. */ up_clean_dcache((uintptr_t)desc, (uintptr_t)desc + sizeof(struct dma_linklist_s)); break; } } /* Because we hold a count from the counting mutex, the above * search loop should always be successful. */ nxmutex_unlock(&dmac->chlock); DEBUGASSERT(desc != NULL); } return desc; } /**************************************************************************** * Name: sam_freelinklist * * Description: * Free all descriptors in the DMA channel's link list. * * NOTE: Called from the DMA interrupt handler. * ****************************************************************************/ static void sam_freelinklist(struct sam_dmach_s *dmach) { struct sam_dmac_s *dmac = sam_controller(dmach); struct dma_linklist_s *desc; uintptr_t paddr; /* Get the head of the link list and detach the link list from the DMA * channel */ desc = dmach->llhead; dmach->llhead = NULL; dmach->lltail = NULL; while (desc != NULL) { /* Valid, in-use descriptors never have saddr == 0 */ DEBUGASSERT(desc->saddr != 0); /* Get the physical address of the next descriptor in the list */ paddr = desc->dscr; /* Free the descriptor by simply nullifying it and bumping up the * semaphore count. */ memset(desc, 0, sizeof(struct dma_linklist_s)); nxsem_post(&dmac->dsem); /* Get the virtual address of the next descriptor in the list */ desc = (struct dma_linklist_s *)sam_virtramaddr(paddr); } } /**************************************************************************** * 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, size_t nbytes) { uint32_t regval; uint32_t ctrla; uint32_t ctrlb; /* If we are appending a buffer to a linklist, then re-use the CTRLA/B * values. Otherwise, create them from the properties of the transfer. */ if (dmach->llhead) { regval = dmach->llhead->ctrla; ctrlb = dmach->llhead->ctrlb; } else { regval = sam_txctrlabits(dmach); ctrlb = sam_txctrlb(dmach); } ctrla = sam_txctrla(dmach, regval, nbytes); /* Add the new link list entry */ if (!sam_allocdesc(dmach, dmach->lltail, maddr, paddr, ctrla, ctrlb)) { return -ENOMEM; } /* Pre-calculate the transmit CFG register setting (it won't be used until * the DMA is started). */ dmach->cfg = sam_txcfg(dmach); 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, size_t nbytes) { uint32_t regval; uint32_t ctrla; uint32_t ctrlb; /* If we are appending a buffer to a linklist, then re-use the CTRLA/B * values. Otherwise, create them from the properties of the transfer. */ if (dmach->llhead) { regval = dmach->llhead->ctrla; ctrlb = dmach->llhead->ctrlb; } else { regval = sam_rxctrlabits(dmach); ctrlb = sam_rxctrlb(dmach); } ctrla = sam_rxctrla(dmach, regval, nbytes); /* Add the new link list entry */ if (!sam_allocdesc(dmach, dmach->lltail, paddr, maddr, ctrla, ctrlb)) { return -ENOMEM; } /* Pre-calculate the receive CFG register setting (it won't be used until * the DMA is started). */ dmach->cfg = sam_rxcfg(dmach); return OK; } /**************************************************************************** * Name: sam_single * * Description: * Start a single buffer DMA. * ****************************************************************************/ static inline int sam_single(struct sam_dmach_s *dmach) { struct sam_dmac_s *dmac = sam_controller(dmach); struct dma_linklist_s *llhead = dmach->llhead; /* Clear any pending interrupts from any previous DMAC transfer by reading * the interrupt status register. */ sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET); /* Write the starting source address in the SADDR register */ DEBUGASSERT(llhead != NULL && llhead->saddr != 0); sam_putdmach(dmach, llhead->saddr, SAM_DMAC_CH_SADDR_OFFSET); /* Write the starting destination address in the DADDR register */ sam_putdmach(dmach, llhead->daddr, SAM_DMAC_CH_DADDR_OFFSET); /* Clear the next descriptor address */ sam_putdmach(dmach, 0, SAM_DMAC_CH_DSCR_OFFSET); /* Set up the CTRLA register */ sam_putdmach(dmach, llhead->ctrla, SAM_DMAC_CH_CTRLA_OFFSET); /* Set up the CTRLB register */ sam_putdmach(dmach, llhead->ctrlb, SAM_DMAC_CH_CTRLB_OFFSET); /* Both the DST and SRC DSCR bits should be '1' in CTRLB */ DEBUGASSERT((llhead->ctrlb & DMAC_CH_CTRLB_BOTHDSCR) == DMAC_CH_CTRLB_BOTHDSCR); /* Set up the CFG register */ sam_putdmach(dmach, dmach->cfg, SAM_DMAC_CH_CFG_OFFSET); /* Enable the channel by writing a 1 to the CHER enable bit */ sam_putdmac(dmac, DMAC_CHER_ENA(dmach->chan), SAM_DMAC_CHER_OFFSET); /* The DMA has been started. Once the transfer completes, hardware sets * the interrupts and disables the channel. We will receive buffer * complete and transfer complete interrupts. * * Enable error, buffer complete and transfer complete interrupts. * (Since there is only a single buffer, we don't need the buffer * complete interrupt). */ sam_putdmac(dmac, DMAC_EBC_CBTCINTS(dmach->chan), SAM_DMAC_EBCIER_OFFSET); return OK; } /**************************************************************************** * Name: sam_multiple * * Description: * Start a multiple buffer DMA. * ****************************************************************************/ static inline int sam_multiple(struct sam_dmach_s *dmach) { struct sam_dmac_s *dmac = sam_controller(dmach); struct dma_linklist_s *llhead = dmach->llhead; DEBUGASSERT(llhead != NULL && llhead->saddr != 0); /* Check the first and last CTRLB values */ DEBUGASSERT((llhead->ctrlb & DMAC_CH_CTRLB_BOTHDSCR) == 0); DEBUGASSERT((dmach->lltail->ctrlb & DMAC_CH_CTRLB_BOTHDSCR) == DMAC_CH_CTRLB_BOTHDSCR); /* Clear any pending interrupts from any previous DMAC transfer by reading * the status register */ sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET); /* Set up the initial CTRLA register */ sam_putdmach(dmach, llhead->ctrla, SAM_DMAC_CH_CTRLA_OFFSET); /* Set up the CTRLB register (will enable descriptors) */ sam_putdmach(dmach, llhead->ctrlb, SAM_DMAC_CH_CTRLB_OFFSET); /* Write the channel configuration information into the CFG register */ sam_putdmach(dmach, dmach->cfg, SAM_DMAC_CH_CFG_OFFSET); /* Program the DSCR register with the pointer to the firstlink list * entry. */ sam_putdmach(dmach, (uint32_t)llhead, SAM_DMAC_CH_DSCR_OFFSET); /* Finally, enable the channel by writing a 1 to the CHER enable */ sam_putdmac(dmac, DMAC_CHER_ENA(dmach->chan), SAM_DMAC_CHER_OFFSET); /* As each buffer of data is transferred, the CTRLA register is written * back into the link list entry. The CTRLA contains updated BTSIZE and * DONE bits. Additionally, the CTRLA DONE bit is asserted when the * buffer transfer has completed. * * The DMAC transfer continues until the CTRLB register disables the * descriptor (DSCR bits) registers at the final buffer transfer. * * Enable error, buffer complete and transfer complete interrupts. We * don't really need the buffer complete interrupts, but we will take them * just to handle stall conditions. */ sam_putdmac(dmac, DMAC_EBC_CHANINTS(dmach->chan), SAM_DMAC_EBCIER_OFFSET); return OK; } /**************************************************************************** * Name: sam_dmaterminate * * Description: * Terminate the DMA transfer and disable the DMA channel * ****************************************************************************/ static void sam_dmaterminate(struct sam_dmach_s *dmach, int result) { struct sam_dmac_s *dmac = sam_controller(dmach); /* Disable all channel interrupts */ sam_putdmac(dmac, DMAC_EBC_CHANINTS(dmach->chan), SAM_DMAC_EBCIDR_OFFSET); /* Disable the channel by writing one to the write-only channel disable * register. */ sam_putdmac(dmac, DMAC_CHDR_DIS(dmach->chan), SAM_DMAC_CHDR_OFFSET); /* Free the linklist */ sam_freelinklist(dmach); /* If this was an RX DMA (peripheral-to-memory), then invalidate the cache * to force reloads from memory. */ if (dmach->rx) { up_invalidate_dcache(dmach->rxaddr, dmach->rxaddr + dmach->rxsize); } /* Perform the DMA complete callback */ if (dmach->callback) { dmach->callback((DMA_HANDLE)dmach, dmach->arg, result); } dmach->callback = NULL; dmach->arg = NULL; } /**************************************************************************** * Name: sam_dmac_interrupt * * Description: * DMA interrupt handler * ****************************************************************************/ static int sam_dmac_interrupt(int irq, void *context, void *arg) { struct sam_dmac_s *dmac = (struct sam_dmac_s *)arg; struct sam_dmach_s *dmach; unsigned int chndx; uint32_t regval; DEBUGASSERT(dmac != NULL); /* Get the DMAC status register value. Ignore all masked interrupt * status bits. */ regval = sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET) & sam_getdmac(dmac, SAM_DMAC_EBCIMR_OFFSET); /* Check if the any transfer has completed or any errors have occurred. */ if (regval & DMAC_EBC_ALLCHANINTS) { /* Yes.. Check each bit to see which channel has interrupted */ for (chndx = 0; chndx < SAM_NDMACHAN; chndx++) { /* Are any interrupts pending for this channel? */ if ((regval & DMAC_EBC_CHANINTS(chndx)) != 0) { dmach = &dmac->dmach[chndx]; /* Yes.. Did an error occur? */ if ((regval & DMAC_EBC_ERR(chndx)) != 0) { /* Yes... Terminate the transfer with an error? */ dmaerr("ERROR: DMA failed: %08" PRIx32 "\n", regval); sam_dmaterminate(dmach, -EIO); } /* Is the transfer complete? */ else if ((regval & DMAC_EBC_CBTC(chndx)) != 0) { /* Yes.. Terminate the transfer with success */ sam_dmaterminate(dmach, OK); } /* Otherwise, this must be a Buffer Transfer Complete (BTC) * interrupt as part of a multiple buffer transfer. */ else /* if ((regval & DMAC_EBC_BTC(chndx)) != 0) */ { /* Write the KEEPON field to clear the STALL states */ sam_putdmac(dmac, DMAC_CHER_KEEP(dmach->chan), SAM_DMAC_CHER_OFFSET); } } } } return OK; } /**************************************************************************** * Name: sam_dmainitialize * * Description: * Initialize the DMA subsystem * * Returned Value: * None * ****************************************************************************/ void sam_dmainitialize(struct sam_dmac_s *dmac) { /* Disable all DMA interrupts */ sam_putdmac(dmac, DMAC_EBC_ALLINTS, SAM_DMAC_EBCIDR_OFFSET); /* Disable all DMA channels */ sam_putdmac(dmac, DMAC_CHDR_DIS_ALL, SAM_DMAC_CHDR_OFFSET); /* Enable the DMA controller */ sam_putdmac(dmac, DMAC_EN_ENABLE, SAM_DMAC_EN_OFFSET); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: arm_dma_initialize * * Description: * Initialize the DMA subsystem * * Returned Value: * None * ****************************************************************************/ void weak_function arm_dma_initialize(void) { #ifdef CONFIG_SAMA5_DMAC0 dmainfo("Initialize DMAC0\n"); /* Enable peripheral clock */ sam_dmac0_enableclk(); /* Attach DMA interrupt vector */ irq_attach(SAM_IRQ_DMAC0, sam_dmac_interrupt, &g_dmac0); /* Initialize the controller */ sam_dmainitialize(&g_dmac0); /* Enable the IRQ at the AIC (still disabled at the DMA controller) */ up_enable_irq(SAM_IRQ_DMAC0); #endif #ifdef CONFIG_SAMA5_DMAC1 dmainfo("Initialize DMAC1\n"); /* Enable peripheral clock */ sam_dmac1_enableclk(); /* Attach DMA interrupt vector */ irq_attach(SAM_IRQ_DMAC1, sam_dmac_interrupt, &g_dmac1); /* Initialize the controller */ sam_dmainitialize(&g_dmac1); /* Enable the IRQ at the AIC (still disabled at the DMA controller) */ up_enable_irq(SAM_IRQ_DMAC1); #endif } /**************************************************************************** * Name: sam_dmachannel * * Allocate a DMA channel. This function sets aside a DMA channel then * 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 is available, this function returns a non-NULL, void* * DMA channel handle. NULL is returned on any failure. * ****************************************************************************/ DMA_HANDLE sam_dmachannel(uint8_t dmacno, uint32_t chflags) { struct sam_dmac_s *dmac; struct sam_dmach_s *dmach; unsigned int chndx; int ret; /* Pick the DMA controller */ #ifdef CONFIG_SAMA5_DMAC0 if (dmacno == 0) { dmac = &g_dmac0; } else #endif #ifdef CONFIG_SAMA5_DMAC1 if (dmacno == 1) { dmac = &g_dmac1; } else #endif { dmaerr("ERROR: Bad DMAC number: %d\n", dmacno); DEBUGPANIC(); return NULL; } /* Search for an available DMA channel with at least the requested FIFO * size. */ dmach = NULL; ret = nxmutex_lock(&dmac->chlock); if (ret < 0) { return NULL; } for (chndx = 0; chndx < SAM_NDMACHAN; chndx++) { struct sam_dmach_s *candidate = &dmac->dmach[chndx]; if (!candidate->inuse) { dmach = candidate; dmach->inuse = true; /* Read the status register to clear any pending interrupts on the * channel */ sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET); /* Disable the channel by writing one to the write-only channel * disable register */ sam_putdmac(dmac, DMAC_CHDR_DIS(chndx), SAM_DMAC_CHDR_OFFSET); /* Set the DMA channel flags. */ dmach->flags = chflags; break; } } nxmutex_unlock(&dmac->chlock); /* Show the result of the allocation */ if (dmach) { dmainfo("DMAC%d CH%d: chflags: %08x returning dmach: %p\n", (int)dmacno, dmach->chan, (int)chflags, dmach); } else { dmaerr("ERROR: Failed allocate DMAC%d channel\n", (int)dmacno); } 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 used and sam_dmaconfig() is called before each * DMA to configure the DMA channel appropriately. * * Returned Value: * None * ****************************************************************************/ void sam_dmaconfig(DMA_HANDLE handle, uint32_t chflags) { struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle; /* Set the new DMA channel flags. */ dmach->flags = chflags; #if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1) dmainfo("DMAC%d CH%d: chflags: %08x\n", dmach->dmac, dmach->chan, (int)chflags); #elif defined(CONFIG_SAMA5_DMAC0) dmainfo("DMAC0 CH%d: chflags: %08x\n", dmach->chan, (int)chflags); #else dmainfo("DMAC1 CH%d: chflags: %08x\n", dmach->chan, (int)chflags); #endif } /**************************************************************************** * 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->inuse)); /* Mark the channel no longer in use. Clearing the inuse flag is an atomic * operation and so should be safe. */ dmach->flags = 0; dmach->inuse = false; /* No longer in use */ } /**************************************************************************** * 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; size_t maxtransfer; size_t remaining; int ret = OK; dmainfo("dmach: %p paddr: %08x maddr: %08x nbytes: %d\n", dmach, (int)paddr, (int)maddr, (int)nbytes); DEBUGASSERT(dmach); dmainfo("llhead: %p lltail: %p\n", dmach->llhead, dmach->lltail); /* The maximum transfer size in bytes depends upon the maximum number of * transfers and the number of bytes per transfer. */ maxtransfer = sam_maxtxtransfer(dmach); remaining = nbytes; /* If this is a large transfer, break it up into smaller buffers */ while (remaining > maxtransfer) { /* Set up the maximum size transfer */ ret = sam_txbuffer(dmach, paddr, maddr, maxtransfer); if (ret == OK) { /* Decrement the number of bytes left to transfer */ remaining -= maxtransfer; /* Increment the memory & peripheral address (if it is appropriate * to do so). */ if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) != 0) { paddr += maxtransfer; } if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0) { maddr += maxtransfer; } } } /* Then set up the final buffer transfer */ if (ret == OK && remaining > 0) { ret = sam_txbuffer(dmach, paddr, maddr, remaining); } /* Save an indication so that the DMA interrupt completion logic will know * that this was not an RX transfer. */ dmach->rx = false; /* Clean caches associated with the DMA memory */ up_clean_dcache(maddr, maddr + nbytes); 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; size_t maxtransfer; size_t remaining; int ret = OK; dmainfo("dmach: %p paddr: %08x maddr: %08x nbytes: %d\n", dmach, (int)paddr, (int)maddr, (int)nbytes); DEBUGASSERT(dmach); dmainfo("llhead: %p lltail: %p\n", dmach->llhead, dmach->lltail); /* The maximum transfer size in bytes depends upon the maximum number of * transfers and the number of bytes per transfer. */ maxtransfer = sam_maxrxtransfer(dmach); remaining = nbytes; /* If this is a large transfer, break it up into smaller buffers */ while (remaining > maxtransfer) { /* Set up the maximum size transfer */ ret = sam_rxbuffer(dmach, paddr, maddr, maxtransfer); if (ret == OK) { /* Decrement the number of bytes left to transfer */ remaining -= maxtransfer; /* Increment the memory & peripheral address (if it is appropriate * to do so). */ if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) != 0) { paddr += maxtransfer; } if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0) { maddr += maxtransfer; } } } /* Then set up the final buffer transfer */ if (ret == OK && remaining > 0) { ret = sam_rxbuffer(dmach, paddr, maddr, remaining); } /* Save an indication so that the DMA interrupt completion logic will know * that this was an RX transfer and will invalidate the cache. */ dmach->rx = true; dmach->rxaddr = maddr; dmach->rxsize = (dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0 ? nbytes : sizeof(uint32_t); /* Clean caches associated with the DMA memory */ up_clean_dcache(maddr, maddr + nbytes); 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; int ret = -EINVAL; dmainfo("dmach: %p callback: %p arg: %p\n", dmach, callback, arg); DEBUGASSERT(dmach != NULL); /* Verify that the DMA has been setup (i.e., at least one entry in the * link list). */ if (dmach->llhead) { /* Save the callback info. This will be invoked whent the DMA * completes */ dmach->callback = callback; dmach->arg = arg; /* Is this a single block transfer? Or a multiple block transfer? */ if (dmach->llhead == dmach->lltail) { ret = sam_single(dmach); } else { ret = sam_multiple(dmach); } } 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 void sam_dmasample(DMA_HANDLE handle, struct sam_dmaregs_s *regs) { struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle; struct sam_dmac_s *dmac = sam_controller(dmach); irqstate_t flags; /* Sample global registers. NOTE: reading EBCISR clears interrupts, but * that should be okay IF interrupts are enabled when this function is * called. But there is a race condition where this instrumentation could * cause lost interrupts. */ flags = enter_critical_section(); regs->gcfg = sam_getdmac(dmac, SAM_DMAC_GCFG_OFFSET); regs->en = sam_getdmac(dmac, SAM_DMAC_EN_OFFSET); regs->sreq = sam_getdmac(dmac, SAM_DMAC_SREQ_OFFSET); regs->creq = sam_getdmac(dmac, SAM_DMAC_CREQ_OFFSET); regs->last = sam_getdmac(dmac, SAM_DMAC_LAST_OFFSET); regs->ebcimr = sam_getdmac(dmac, SAM_DMAC_EBCIMR_OFFSET); regs->ebcisr = sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET); regs->chsr = sam_getdmac(dmac, SAM_DMAC_CHSR_OFFSET); regs->wpmr = sam_getdmac(dmac, SAM_DMAC_WPMR_OFFSET); regs->wpsr = sam_getdmac(dmac, SAM_DMAC_WPSR_OFFSET); /* Sample channel registers */ regs->saddr = sam_getdmach(dmach, SAM_DMAC_CH_SADDR_OFFSET); regs->daddr = sam_getdmach(dmach, SAM_DMAC_CH_DADDR_OFFSET); regs->dscr = sam_getdmach(dmach, SAM_DMAC_CH_DSCR_OFFSET); regs->ctrla = sam_getdmach(dmach, SAM_DMAC_CH_CTRLA_OFFSET); regs->ctrlb = sam_getdmach(dmach, SAM_DMAC_CH_CTRLB_OFFSET); regs->cfg = sam_getdmach(dmach, SAM_DMAC_CH_CFG_OFFSET); regs->spip = sam_getdmach(dmach, SAM_DMAC_CH_SPIP_OFFSET); regs->dpip = sam_getdmach(dmach, SAM_DMAC_CH_DPIP_OFFSET); leave_critical_section(flags); } #endif /* CONFIG_DEBUG_DMA */ /**************************************************************************** * Name: sam_dmadump * * Description: * Dump previously sampled DMA register contents * * Assumptions: * - DMA handle allocated by sam_dmachannel() * ****************************************************************************/ #ifdef CONFIG_DEBUG_DMA 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; struct sam_dmac_s *dmac = sam_controller(dmach); dmainfo("%s\n", msg); dmainfo(" DMA Global Registers:\n"); dmainfo(" GCFG[%08x]: %08x\n", dmac->base + SAM_DMAC_GCFG_OFFSET, regs->gcfg); dmainfo(" EN[%08x]: %08x\n", dmac->base + SAM_DMAC_EN_OFFSET, regs->en); dmainfo(" SREQ[%08x]: %08x\n", dmac->base + SAM_DMAC_SREQ_OFFSET, regs->sreq); dmainfo(" CREQ[%08x]: %08x\n", dmac->base + SAM_DMAC_CREQ_OFFSET, regs->creq); dmainfo(" LAST[%08x]: %08x\n", dmac->base + SAM_DMAC_LAST_OFFSET, regs->last); dmainfo(" EBCIMR[%08x]: %08x\n", dmac->base + SAM_DMAC_EBCIMR_OFFSET, regs->ebcimr); dmainfo(" EBCISR[%08x]: %08x\n", dmac->base + SAM_DMAC_EBCISR_OFFSET, regs->ebcisr); dmainfo(" CHSR[%08x]: %08x\n", dmac->base + SAM_DMAC_CHSR_OFFSET, regs->chsr); dmainfo(" WPMR[%08x]: %08x\n", dmac->base + SAM_DMAC_WPMR_OFFSET, regs->wpmr); dmainfo(" WPSR[%08x]: %08x\n", dmac->base + SAM_DMAC_WPSR_OFFSET, regs->wpsr); dmainfo(" DMA Channel Registers:\n"); dmainfo(" SADDR[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_SADDR_OFFSET, regs->saddr); dmainfo(" DADDR[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_DADDR_OFFSET, regs->daddr); dmainfo(" DSCR[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_DSCR_OFFSET, regs->dscr); dmainfo(" CTRLA[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_CTRLA_OFFSET, regs->ctrla); dmainfo(" CTRLB[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_CTRLB_OFFSET, regs->ctrlb); dmainfo(" CFG[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_CFG_OFFSET, regs->cfg); dmainfo(" SPIP[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_SPIP_OFFSET, regs->spip); dmainfo(" DPIP[%08x]: %08x\n", dmach->base + SAM_DMAC_CH_DPIP_OFFSET, regs->dpip); } #endif /* CONFIG_DEBUG_DMA */ #endif /* CONFIG_SAMA5_DMAC0 || CONFIG_SAMA5_DMAC1 */