1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 14:33:52 +01:00

sam0_common: Add DMA peripheral driver

This commit is contained in:
Koen Zandberg 2020-06-11 10:52:51 +02:00
parent a4889b4207
commit 6be1b27bbb
No known key found for this signature in database
GPG Key ID: 0895A893E6D2985B
3 changed files with 545 additions and 0 deletions

View File

@ -8,6 +8,7 @@
config CPU_COMMON_SAM0
bool
select HAS_PERIPH_CPUID
select HAS_PERIPH_DMA
select HAS_PERIPH_FLASHPAGE
select HAS_PERIPH_FLASHPAGE_RAW
select HAS_PERIPH_FLASHPAGE_RWEE

View File

@ -670,6 +670,218 @@ typedef struct {
*/
#define WDT_HAS_INIT (1)
/**
* The sam0 DMA peripheral has a number of channels. Each channel is a separate
* data stream, triggered by a configurable trigger when enabled, or triggered
* by software (not yet supported). In theory each DMA channel is equal and can
* have a configurable priority and can be triggered by the full set of triggers
* available.
*
* DMA descriptors, specifying a single transfer with size, source and
* destination, are kept in RAM and are read when the channel is enabled and
* triggered. On the SAML21 platform, these descriptors must reside in the LP
* SRAM.
*/
/**
* @brief Indicates that the peripheral doesn't utilize the DMA controller.
* Matches with the register configuration for software based triggers.
*/
#define DMA_TRIGGER_DISABLED 0
/**
* @brief Move the DMA descriptors to the LP SRAM. Required on the SAML21
*/
#if defined(CPU_FAM_SAML21)
#define DMA_DESCRIPTOR_IN_LPSRAM
#endif
#ifdef DMA_DESCRIPTOR_IN_LPSRAM
#define DMA_DESCRIPTOR_ATTRS __attribute__((section(".backup.bss")))
#else
#define DMA_DESCRIPTOR_ATTRS
#endif
/**
* @brief DMA channel type
*/
typedef unsigned dma_t;
/**
* @brief Available DMA address increment modes
*/
typedef enum {
DMA_INCR_NONE = 0, /**< Don't increment any addresses after a beat */
DMA_INCR_SRC = 1, /**< Increment the source address after a beat */
DMA_INCR_DEST = 2, /**< Increment destination address after a beat */
DMA_INCR_BOTH = 3, /**< Increment both addresses after a beat */
} dma_incr_t;
/**
* @brief Initialize DMA
*/
void dma_init(void);
/**
* @brief Acquire a DMA channel.
*
* A free DMA channel is marked as allocated and a reference is returned.
* DMA channels can be acquired for long periods of time, e.g. from the start to
* end of a number of transfers or directly at boot and never released.
*
* @returns A reference to the DMA channel
* @returns UINT8_MAX when no DMA channel is available
*/
dma_t dma_acquire_channel(void);
/**
* @brief Release a previously acquired DMA channel
*
* @param dma DMA channel to release
*/
void dma_release_channel(dma_t dma);
/**
* @brief Initialize a previously allocated DMA channel with one-time settings
*
* @param dma DMA channel reference
* @param trigger Trigger to use for this DMA channel
* @param prio Channel priority
* @param irq Whether to enable the interrupt handler for this channel
*/
void dma_setup(dma_t dma, unsigned trigger, uint8_t prio, bool irq);
/**
* @brief Prepare the DMA channel for an individual transfer.
*
* @param dma DMA channel reference
* @param width Transfer beat size to use
* @param src Source address for the transfer
* @param dst Destination address for the transfer
* @param len Number of beats to transfer
* @param incr Which of the addresses to increment after a beat
*/
void dma_prepare(dma_t dma, uint8_t width, void *src, void *dst, size_t len,
dma_incr_t incr);
/**
* @brief Prepare a transfer without modifying the destination address
* settings.
*
* Can be used when repeatedly using a dma channel to transfer to the same
* peripheral address, leaving the destination address and related settings
* untouched
*
* @note This only touches the source address, length and source increment
* settings. Be sure to initialize the full descriptor beforehand with
* @ref dma_prepare
*
* @param dma DMA channel reference
* @param src Source address for the transfer
* @param len Number of beats to transfer
* @param incr Whether to increment the source address after a beat
*/
void dma_prepare_src(dma_t dma, void *src, size_t len, bool incr);
/**
* @brief Prepare a transfer without modifying the source address
* settings.
*
* Can be used when repeatedly using a dma channel to transfer from the same
* peripheral address, leaving the source address and related settings
* untouched
*
* @note This only touches the destination address, length and destination
* increment settings. Be sure to initialize the full descriptor beforehand with
* @ref dma_prepare
*
* @param dma DMA channel reference
* @param dst Destination address for the transfer
* @param len Number of beats to transfer
* @param incr Whether to increment the destination address after a beat
*/
void dma_prepare_dst(dma_t dma, void *dst, size_t len, bool incr);
/**
* @brief Append a second transfer descriptor after the default channel
* descriptor.
*
* @note Only a single extra transfer descriptor is supported for now.
* @note @p descriptor must remain valid throughout the full transfer duration
*
* @param dma DMA channel reference to add the descriptor to
* @param descriptor Extra transfer descriptor to append
* @param width Transfer beat size to use
* @param src Source address for the transfer
* @param dst Destination address for the transfer
* @param len Number of beats to transfer
* @param incr Which of the addresses to increment after a beat
*/
void dma_append(dma_t dma, DmacDescriptor *descriptor, uint8_t width,
void *src, void *dst, size_t len, dma_incr_t incr);
/**
* @brief Append a second transfer descriptor after the default channel
* descriptor, copying destination and block size from the initial
* descriptor.
*
* @note Only a single extra transfer descriptor is supported for now.
* @note @p descriptor must remain valid throughout the full transfer duration
*
* @param dma DMA channel reference to add the descriptor to
* @param next Extra transfer descriptor to append
* @param src Source address for the transfer
* @param len Number of beats to transfer
* @param incr Whether to increment the source address after a beat
*/
void dma_append_src(dma_t dma, DmacDescriptor *next, void *src, size_t len,
bool incr);
/**
* @brief Append a second transfer descriptor after the default channel
* descriptor, copying source and block size from the initial
* descriptor.
*
* @note Only a single extra transfer descriptor is supported for now.
* @note @p descriptor must remain valid throughout the full transfer duration
*
* @param dma DMA channel reference to add the descriptor to
* @param next Extra transfer descriptor to append
* @param dst Destination address for the transfer
* @param len Number of beats to transfer
* @param incr Whether to increment the source address after a beat
*/
void dma_append_dst(dma_t dma, DmacDescriptor *next, void *dst, size_t len,
bool incr);
/**
* @brief Start a DMA transfer.
*
* @param dma DMA channel reference
*/
void dma_start(dma_t dma);
/**
* @brief Wait for a DMA channel to finish the transfer.
*
* This function uses a blocking mutex to wait for the transfer to finish
*
* @note Use only with DMA channels of which the interrupt is enabled
*
* @param dma DMA channel reference
*/
void dma_wait(dma_t dma);
/**
* @brief Cancel an active DMA transfer
*
* It is not harmful to call this on an inactive channel, but it will waste some
* processing time
*
* @param dma DMA channel reference
*/
void dma_cancel(dma_t dma);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,332 @@
/*
* Copyright (C) 2020 Koen Zandberg <koen@bergzand.net>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_sam0_common
* @{
*
* @file
* @brief Low-level DMA driver implementation
*
* @author Koen Zandberg <koen@bergzand.net>
*
* @}
*/
#include "periph_cpu.h"
#include "periph_conf.h"
#include "mutex.h"
#include "assert.h"
#include "bitarithm.h"
#include "pm_layered.h"
#include "thread_flags.h"
#include "periph/gpio.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
#ifndef CONFIG_DMA_NUMOF
#define CONFIG_DMA_NUMOF DMAC_CH_NUM
#endif
/* In memory DMA transfer descriptors */
static DmacDescriptor DMA_DESCRIPTOR_ATTRS descriptors[CONFIG_DMA_NUMOF];
static DmacDescriptor DMA_DESCRIPTOR_ATTRS writeback[CONFIG_DMA_NUMOF];
/* Bitmap of dma channels available */
static uint32_t channels_free = (1LLU << CONFIG_DMA_NUMOF) - 1;
struct dma_ctx {
mutex_t sync_lock;
};
struct dma_ctx dma_ctx[CONFIG_DMA_NUMOF];
static void _poweron(void)
{
#if defined(MCLK)
MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
#else
PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
#endif
}
void dma_init(void)
{
_poweron();
for (unsigned i = 0; i < CONFIG_DMA_NUMOF; i++) {
mutex_init(&dma_ctx[i].sync_lock);
}
/* Enable all priorities with RR scheduling */
DMAC->CTRL.reg = DMAC_CTRL_LVLEN0 |
DMAC_CTRL_LVLEN1 |
DMAC_CTRL_LVLEN2 |
DMAC_CTRL_LVLEN3;
DMAC->PRICTRL0.reg = DMAC_PRICTRL0_RRLVLEN0 |
DMAC_PRICTRL0_RRLVLEN1 |
DMAC_PRICTRL0_RRLVLEN2 |
DMAC_PRICTRL0_RRLVLEN3;
DMAC->BASEADDR.reg = (uint32_t)descriptors;
DMAC->WRBADDR.reg = (uint32_t)writeback;
#if defined(CPU_FAM_SAML11) || defined(CPU_FAM_SAML10)
NVIC_EnableIRQ(DMAC_0_IRQn);
NVIC_EnableIRQ(DMAC_1_IRQn);
NVIC_EnableIRQ(DMAC_2_IRQn);
NVIC_EnableIRQ(DMAC_3_IRQn);
#elif defined(CPU_FAM_SAMD5X)
NVIC_EnableIRQ(DMAC_0_IRQn);
NVIC_EnableIRQ(DMAC_1_IRQn);
NVIC_EnableIRQ(DMAC_2_IRQn);
NVIC_EnableIRQ(DMAC_3_IRQn);
NVIC_EnableIRQ(DMAC_4_IRQn);
#else
NVIC_EnableIRQ(DMAC_IRQn);
#endif
DMAC->CTRL.bit.DMAENABLE = 1;
}
dma_t dma_acquire_channel(void)
{
dma_t channel = UINT8_MAX;
unsigned state = irq_disable();
if (channels_free) {
channel = bitarithm_lsb(channels_free);
/* Clear channel bit */
channels_free &= ~(1 << channel);
/* ensure the sync lock is locked */
mutex_trylock(&dma_ctx[channel].sync_lock);
}
irq_restore(state);
return channel;
}
void dma_release_channel(dma_t dma)
{
unsigned state = irq_disable();
#ifdef REG_DMAC_CHID
DMAC->CHID.reg = dma;
/* Reset DMA channel */
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
#else
DMAC->Channel[dma].CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
#endif
channels_free |= 1 << dma;
irq_restore(state);
}
static inline void _set_source(DmacDescriptor *descr, void *src)
{
descr->SRCADDR.reg = (uint32_t)src;
}
static inline void _set_destination(DmacDescriptor *descr, void *dst)
{
descr->DSTADDR.reg = (uint32_t)dst;
}
static inline void _set_len(DmacDescriptor *descr, size_t len)
{
descr->BTCNT.reg = len;
}
static inline void _set_next_descriptor(DmacDescriptor *descr, void *next)
{
descr->DESCADDR.reg = (uint32_t)next;
}
void dma_setup(dma_t dma, unsigned trigger, uint8_t prio, bool irq)
{
#ifdef REG_DMAC_CHID
/* Ensure that this set of register writes is atomic */
unsigned state = irq_disable();
DMAC->CHID.reg = dma;
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_TRIGACT_BEAT |
(trigger << DMAC_CHCTRLB_TRIGSRC_Pos) |
(prio << DMAC_CHCTRLB_LVL_Pos);
/* Clear everything in case a previous user left it configured */
DMAC->CHINTENCLR.reg = 0xFF;
if (irq) {
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_TCMPL;
}
irq_restore(state);
#else
DMAC->Channel[dma].CHCTRLA.reg = DMAC_CHCTRLA_TRIGACT_BURST |
(trigger << DMAC_CHCTRLA_TRIGSRC_Pos);
DMAC->Channel[dma].CHPRILVL.reg = prio;
DMAC->Channel[dma].CHINTENCLR.reg = 0xFF;
if (irq) {
DMAC->Channel[dma].CHINTENSET.reg = DMAC_CHINTENSET_TCMPL;
}
#endif
}
void dma_prepare(dma_t dma, uint8_t width, void *src, void *dst, size_t len,
uint8_t incr)
{
DEBUG("[DMA]: Prepare %u, len: %u\n", dma, (unsigned)len);
DmacDescriptor *descr = &descriptors[dma];
_set_len(descr, len);
_set_source(descr, src);
_set_destination(descr, dst);
descr->DESCADDR.reg = (uint32_t)NULL;
descr->BTCTRL.reg = width << DMAC_BTCTRL_BEATSIZE_Pos |
incr << DMAC_BTCTRL_SRCINC_Pos |
DMAC_BTCTRL_VALID;
}
void dma_prepare_src(dma_t dma, void *src, size_t len,
bool incr)
{
DEBUG("[dma]: %u: prep src %p, %u, %u\n", dma, src, (unsigned)len, incr);
DmacDescriptor *descr = &descriptors[dma];
_set_len(descr, len);
_set_source(descr, src);
descr->BTCTRL.reg = (descr->BTCTRL.reg & ~DMAC_BTCTRL_SRCINC) |
(incr << DMAC_BTCTRL_SRCINC_Pos);
_set_next_descriptor(descr, NULL);
}
void dma_prepare_dst(dma_t dma, void *dst, size_t len,
bool incr)
{
DEBUG("[dma]: %u: prep dst %p, %u, %u\n", dma, dst, (unsigned)len, incr);
DmacDescriptor *descr = &descriptors[dma];
_set_len(descr, len);
_set_destination(descr, dst);
descr->BTCTRL.reg = (descr->BTCTRL.reg & ~DMAC_BTCTRL_DSTINC) |
(incr << DMAC_BTCTRL_DSTINC_Pos);
_set_next_descriptor(descr, NULL);
}
void _fmt_append(DmacDescriptor *descr, DmacDescriptor *next,
void *src, void *dst, size_t len)
{
/* Configure the full descriptor besides the BTCTRL data */
_set_next_descriptor(descr, next);
_set_next_descriptor(next, NULL);
_set_source(next, src);
_set_len(next, len);
_set_destination(next, dst);
}
void dma_append(dma_t dma, DmacDescriptor *next, uint8_t width,
void *src, void *dst, size_t len, dma_incr_t incr)
{
DmacDescriptor *descr = &descriptors[dma];
next->BTCTRL.reg = width << DMAC_BTCTRL_BEATSIZE_Pos |
incr << DMAC_BTCTRL_SRCINC_Pos |
DMAC_BTCTRL_VALID;
_fmt_append(descr, next, src, dst, len);
}
void dma_append_src(dma_t dma, DmacDescriptor *next, void *src, size_t len,
bool incr)
{
DmacDescriptor *descr = &descriptors[dma];
/* Copy the original descriptor config and modify the increment */
next->BTCTRL.reg = (descr->BTCTRL.reg & ~DMAC_BTCTRL_SRCINC) |
(incr << DMAC_BTCTRL_SRCINC_Pos);
_fmt_append(descr, next, src, (void *)descr->DSTADDR.reg, len);
}
void dma_append_dst(dma_t dma, DmacDescriptor *next, void *dst, size_t len,
bool incr)
{
DmacDescriptor *descr = &descriptors[dma];
/* Copy the original descriptor config and modify the increment */
next->BTCTRL.reg = (descr->BTCTRL.reg & ~DMAC_BTCTRL_DSTINC) |
(incr << DMAC_BTCTRL_DSTINC_Pos);
_fmt_append(descr, next, (void *)descr->SRCADDR.reg, dst, len);
}
void dma_start(dma_t dma)
{
DEBUG("[dma]: starting: %u\n", dma);
#ifdef REG_DMAC_CHID
unsigned state = irq_disable();
DMAC->CHID.bit.ID = dma;
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_ENABLE;
irq_restore(state);
#else
DMAC->Channel[dma].CHCTRLA.bit.ENABLE = 1;
#endif
}
void dma_wait(dma_t dma)
{
DEBUG("[DMA]: mutex lock: %u\n", dma);
mutex_lock(&dma_ctx[dma].sync_lock);
}
void dma_cancel(dma_t dma)
{
DEBUG("[DMA]: Cancelling active transfer: %u\n", dma);
#ifdef REG_DMAC_CHID
unsigned state = irq_disable();
DMAC->CHID.bit.ID = dma;
/* Write zero to the enable bit */
DMAC->CHCTRLA.reg = 0;
/* Wait until the active beat is finished */
while (DMAC->CHCTRLA.bit.ENABLE) {}
irq_restore(state);
#else
DMAC->Channel[dma].CHCTRLA.bit.ENABLE = 0;
while (DMAC->Channel[dma].CHCTRLA.bit.ENABLE) {}
#endif
}
void isr_dmac(void)
{
/* Always holds the interrupt status for the highest priority channel with
* pending interrupts */
uint16_t status = DMAC->INTPEND.reg;
dma_t dma = status & DMAC_INTPEND_ID_Msk;
/* Clear the pending interrupt flags for this channel by writing the
* channel ID together with the flags to clear */
DMAC->INTPEND.reg = status;
if (status & DMAC_INTPEND_TCMPL) {
mutex_unlock(&dma_ctx[dma].sync_lock);
}
DEBUG("[DMA] IRQ: %u: %x\n", dma, status);
cortexm_isr_end();
}
void isr_dmac0(void)
{
isr_dmac();
}
void isr_dmac1(void)
{
isr_dmac();
}
void isr_dmac2(void)
{
isr_dmac();
}
void isr_dmac3(void)
{
isr_dmac();
}
void isr_dmac4(void)
{
isr_dmac();
}