| /**************************************************************************** |
| * arch/arm/src/sama5/sam_dmac.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 <nuttx/config.h> |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/mutex.h> |
| |
| #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 reuse 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 reuse 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 when 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 */ |