mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-03 19:39:21 +00:00
There is really no need for this header file at all besides if there is a requirement to be annoying. Signed-off-by: Declan Snyder <declan.snyder@nxp.com>
1062 lines
35 KiB
C
1062 lines
35 KiB
C
/*
|
|
* Copyright 2020-2024 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @brief Common part of DMA drivers for imx rt series.
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_mcux_edma
|
|
|
|
#include <errno.h>
|
|
#include <soc.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/sys/atomic.h>
|
|
#include <zephyr/drivers/dma.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/sys/barrier.h>
|
|
|
|
#include <fsl_common.h>
|
|
#include <fsl_edma.h>
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
#include <fsl_dmamux.h>
|
|
#endif
|
|
|
|
#if defined(FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET) && FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET
|
|
#include <fsl_memory.h>
|
|
#endif
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
|
|
LOG_MODULE_REGISTER(dma_mcux_edma, CONFIG_DMA_LOG_LEVEL);
|
|
|
|
#define HAS_CHANNEL_GAP(n) DT_INST_NODE_HAS_PROP(n, channel_gap) ||
|
|
#define DMA_MCUX_HAS_CHANNEL_GAP (DT_INST_FOREACH_STATUS_OKAY(HAS_CHANNEL_GAP) 0)
|
|
|
|
#if defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
typedef DMA5_Type DMAx_Type;
|
|
#else
|
|
typedef DMA_Type DMAx_Type;
|
|
#endif
|
|
|
|
struct dma_mcux_edma_config {
|
|
DMAx_Type *base;
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
DMAMUX_Type **dmamux_base;
|
|
#endif
|
|
uint8_t channels_per_mux;
|
|
uint8_t dmamux_reg_offset;
|
|
int dma_requests;
|
|
int dma_channels; /* number of channels */
|
|
#if DMA_MCUX_HAS_CHANNEL_GAP
|
|
uint32_t channel_gap[2];
|
|
#endif
|
|
void (*irq_config_func)(const struct device *dev);
|
|
edma_tcd_t (*tcdpool)[CONFIG_DMA_TCD_QUEUE_SIZE];
|
|
};
|
|
|
|
|
|
#if defined(CONFIG_HAS_MCUX_CACHE) || defined(CONFIG_HAS_MCUX_XCACHE)
|
|
|
|
#ifdef CONFIG_DMA_MCUX_USE_DTCM_FOR_DMA_DESCRIPTORS
|
|
|
|
#if DT_NODE_HAS_STATUS_OKAY(DT_CHOSEN(zephyr_dtcm))
|
|
#define EDMA_TCDPOOL_CACHE_ATTR __dtcm_noinit_section
|
|
#else /* DT_NODE_HAS_STATUS_OKAY(DT_CHOSEN(zephyr_dtcm)) */
|
|
#error Selected DTCM for MCUX DMA descriptors but no DTCM section.
|
|
#endif /* DT_NODE_HAS_STATUS_OKAY(DT_CHOSEN(zephyr_dtcm)) */
|
|
|
|
#elif defined(CONFIG_NOCACHE_MEMORY)
|
|
#define EDMA_TCDPOOL_CACHE_ATTR __nocache
|
|
#else
|
|
/*
|
|
* Note: the TCD pool *must* be in non cacheable memory. All of the NXP SOCs
|
|
* that support caching memory have their default SRAM regions defined as a
|
|
* non cached memory region, but if the default SRAM region is changed EDMA
|
|
* TCD pools would be moved to cacheable memory, resulting in DMA cache
|
|
* coherency issues.
|
|
*/
|
|
|
|
#define EDMA_TCDPOOL_CACHE_ATTR
|
|
|
|
#endif /* CONFIG_DMA_MCUX_USE_DTCM_FOR_DMA_DESCRIPTORS */
|
|
|
|
#else /* CONFIG_HAS_MCUX_CACHE */
|
|
|
|
#define EDMA_TCDPOOL_CACHE_ATTR
|
|
|
|
#endif /* CONFIG_HAS_MCUX_CACHE */
|
|
|
|
struct dma_mcux_channel_transfer_edma_settings {
|
|
uint32_t source_data_size;
|
|
uint32_t dest_data_size;
|
|
uint32_t source_burst_length;
|
|
uint32_t dest_burst_length;
|
|
enum dma_channel_direction direction;
|
|
edma_transfer_type_t transfer_type;
|
|
bool valid;
|
|
/* This var indicate it is dynamic SG mode or loop SG mode. */
|
|
bool cyclic;
|
|
/* These parameters are for cyclic mode only.
|
|
* Next empty TCD idx which can be used for transfer
|
|
*/
|
|
volatile uint8_t write_idx;
|
|
/* How many TCDs in TCD pool is emtpy(can be used to write transfer parameters) */
|
|
volatile uint8_t empty_tcds;
|
|
};
|
|
|
|
|
|
struct call_back {
|
|
edma_transfer_config_t transferConfig;
|
|
edma_handle_t edma_handle;
|
|
const struct device *dev;
|
|
void *user_data;
|
|
dma_callback_t dma_callback;
|
|
struct dma_mcux_channel_transfer_edma_settings transfer_settings;
|
|
bool busy;
|
|
};
|
|
|
|
struct dma_mcux_edma_data {
|
|
struct dma_context dma_ctx;
|
|
struct call_back *data_cb;
|
|
atomic_t *channels_atomic;
|
|
};
|
|
|
|
#define DEV_CFG(dev) \
|
|
((const struct dma_mcux_edma_config *const)dev->config)
|
|
#define DEV_DATA(dev) ((struct dma_mcux_edma_data *)dev->data)
|
|
#define DEV_BASE(dev) ((DMAx_Type *)DEV_CFG(dev)->base)
|
|
|
|
#define DEV_CHANNEL_DATA(dev, ch) \
|
|
((struct call_back *)(&(DEV_DATA(dev)->data_cb[ch])))
|
|
|
|
#define DEV_EDMA_HANDLE(dev, ch) \
|
|
((edma_handle_t *)(&(DEV_CHANNEL_DATA(dev, ch)->edma_handle)))
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
#define DEV_DMAMUX_BASE(dev, idx) ((DMAMUX_Type *)DEV_CFG(dev)->dmamux_base[idx])
|
|
#define DEV_DMAMUX_IDX(dev, ch) (ch / DEV_CFG(dev)->channels_per_mux)
|
|
|
|
#define DEV_DMAMUX_CHANNEL(dev, ch) \
|
|
(ch % DEV_CFG(dev)->channels_per_mux) ^ (DEV_CFG(dev)->dmamux_reg_offset)
|
|
#endif
|
|
|
|
/* Definations for SW TCD fields */
|
|
#if defined(CONFIG_DMA_MCUX_EDMA) || defined(CONFIG_DMA_MCUX_EDMA_V3)
|
|
#define EDMA_TCD_SADDR(tcd, flag) ((tcd)->SADDR)
|
|
#define EDMA_TCD_DADDR(tcd, flag) ((tcd)->DADDR)
|
|
#define EDMA_TCD_BITER(tcd, flag) ((tcd)->BITER)
|
|
#define EDMA_TCD_CITER(tcd, flag) ((tcd)->CITER)
|
|
#define EDMA_TCD_CSR(tcd, flag) ((tcd)->CSR)
|
|
#define EDMA_TCD_DLAST_SGA(tcd, flag) ((tcd)->DLAST_SGA)
|
|
#if defined(CONFIG_DMA_MCUX_EDMA_V3)
|
|
#define DMA_CSR_DREQ DMA_TCD_CSR_DREQ
|
|
#define EDMA_HW_TCD_CH_ACTIVE_MASK (DMA_CH_CSR_ACTIVE_MASK)
|
|
#else
|
|
#define EDMA_HW_TCD_CH_ACTIVE_MASK (DMA_CSR_ACTIVE_MASK)
|
|
#endif /* CONFIG_DMA_MCUX_EDMA_V3 */
|
|
#elif defined(CONFIG_DMA_MCUX_EDMA_V4) || defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
/* Above macros have been defined in fsl_edma_core.h */
|
|
#define EDMA_HW_TCD_CH_ACTIVE_MASK (DMA_CH_CSR_ACTIVE_MASK)
|
|
#endif
|
|
|
|
/* Definations for HW TCD fields */
|
|
#if defined(CONFIG_DMA_MCUX_EDMA) || defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
#define EDMA_HW_TCD_SADDR(dev, ch) (DEV_BASE(dev)->TCD[ch].SADDR)
|
|
#define EDMA_HW_TCD_DADDR(dev, ch) (DEV_BASE(dev)->TCD[ch].DADDR)
|
|
#define EDMA_HW_TCD_BITER(dev, ch) (DEV_BASE(dev)->TCD[ch].BITER_ELINKNO)
|
|
#define EDMA_HW_TCD_CITER(dev, ch) (DEV_BASE(dev)->TCD[ch].CITER_ELINKNO)
|
|
#define EDMA_HW_TCD_CSR(dev, ch) (DEV_BASE(dev)->TCD[ch].CSR)
|
|
#elif defined(CONFIG_DMA_MCUX_EDMA_V3) || defined(CONFIG_DMA_MCUX_EDMA_V4)
|
|
#define EDMA_HW_TCD_SADDR(dev, ch) (DEV_BASE(dev)->CH[ch].TCD_SADDR)
|
|
#define EDMA_HW_TCD_DADDR(dev, ch) (DEV_BASE(dev)->CH[ch].TCD_DADDR)
|
|
#define EDMA_HW_TCD_BITER(dev, ch) (DEV_BASE(dev)->CH[ch].TCD_BITER_ELINKNO)
|
|
#define EDMA_HW_TCD_CITER(dev, ch) (DEV_BASE(dev)->CH[ch].TCD_CITER_ELINKNO)
|
|
#define EDMA_HW_TCD_CSR(dev, ch) (DEV_BASE(dev)->CH[ch].TCD_CSR)
|
|
#endif
|
|
|
|
/*
|
|
* The hardware channel (takes the gap into account) is used when access DMA registers.
|
|
* For data structures in the shim driver still use the primitive channel.
|
|
*/
|
|
static ALWAYS_INLINE uint32_t dma_mcux_edma_add_channel_gap(const struct device *dev,
|
|
uint32_t channel)
|
|
{
|
|
#if DMA_MCUX_HAS_CHANNEL_GAP
|
|
const struct dma_mcux_edma_config *config = DEV_CFG(dev);
|
|
|
|
return (channel < config->channel_gap[0]) ? channel :
|
|
(channel + 1 + config->channel_gap[1] - config->channel_gap[0]);
|
|
#else
|
|
ARG_UNUSED(dev);
|
|
return channel;
|
|
#endif
|
|
}
|
|
|
|
static ALWAYS_INLINE uint32_t dma_mcux_edma_remove_channel_gap(const struct device *dev,
|
|
uint32_t channel)
|
|
{
|
|
#if DMA_MCUX_HAS_CHANNEL_GAP
|
|
const struct dma_mcux_edma_config *config = DEV_CFG(dev);
|
|
|
|
return (channel < config->channel_gap[0]) ? channel :
|
|
(channel + config->channel_gap[0] - config->channel_gap[1] - 1);
|
|
#else
|
|
ARG_UNUSED(dev);
|
|
return channel;
|
|
#endif
|
|
}
|
|
|
|
static bool data_size_valid(const size_t data_size)
|
|
{
|
|
return (data_size == 4U || data_size == 2U ||
|
|
data_size == 1U || data_size == 8U ||
|
|
data_size == 16U || data_size == 32U
|
|
#if defined(CONFIG_DMA_MCUX_EDMA_V3) || defined(CONFIG_DMA_MCUX_EDMA_V4) \
|
|
|| defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
|| data_size == 64U
|
|
#endif
|
|
);
|
|
}
|
|
|
|
static void nxp_edma_callback(edma_handle_t *handle, void *param, bool transferDone,
|
|
uint32_t tcds)
|
|
{
|
|
int ret = -EIO;
|
|
struct call_back *data = (struct call_back *)param;
|
|
uint32_t channel = dma_mcux_edma_remove_channel_gap(data->dev, handle->channel);
|
|
|
|
if (data->transfer_settings.cyclic) {
|
|
data->transfer_settings.empty_tcds++;
|
|
/*In loop mode, DMA is always busy*/
|
|
data->busy = 1;
|
|
ret = DMA_STATUS_COMPLETE;
|
|
} else if (transferDone) {
|
|
/* DMA is no longer busy when there are no remaining TCDs to transfer */
|
|
data->busy = (handle->tcdPool != NULL) && (handle->tcdUsed > 0);
|
|
ret = DMA_STATUS_COMPLETE;
|
|
}
|
|
LOG_DBG("transfer %d", tcds);
|
|
data->dma_callback(data->dev, data->user_data, channel, ret);
|
|
}
|
|
|
|
static void dma_mcux_edma_irq_handler(const struct device *dev, uint32_t channel)
|
|
{
|
|
uint32_t hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);
|
|
uint32_t flag = EDMA_GetChannelStatusFlags(DEV_BASE(dev), hw_channel);
|
|
|
|
if (flag & kEDMA_InterruptFlag) {
|
|
LOG_DBG("IRQ OCCURRED");
|
|
/* EDMA interrupt flag is cleared here */
|
|
EDMA_HandleIRQ(DEV_EDMA_HANDLE(dev, channel));
|
|
LOG_DBG("IRQ DONE");
|
|
}
|
|
|
|
#if DT_INST_PROP(0, no_error_irq)
|
|
/* Channel shares the same irq for error and transfer complete */
|
|
else if (flag & kEDMA_ErrorFlag) {
|
|
EDMA_ClearChannelStatusFlags(DEV_BASE(dev), channel, 0xFFFFFFFF);
|
|
EDMA_AbortTransfer(DEV_EDMA_HANDLE(dev, channel));
|
|
DEV_CHANNEL_DATA(dev, channel)->busy = false;
|
|
LOG_INF("channel %d error status is 0x%x", channel, flag);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !DT_INST_PROP(0, no_error_irq)
|
|
static void dma_mcux_edma_error_irq_handler(const struct device *dev)
|
|
{
|
|
int i = 0;
|
|
uint32_t hw_channel;
|
|
|
|
for (i = 0; i < DEV_CFG(dev)->dma_channels; i++) {
|
|
if (DEV_CHANNEL_DATA(dev, i)->busy) {
|
|
hw_channel = dma_mcux_edma_add_channel_gap(dev, i);
|
|
LOG_ERR("channel %d error status is 0x%x", hw_channel,
|
|
EDMA_GetErrorStatusFlags(DEV_BASE(dev)));
|
|
EDMA_AbortTransfer(DEV_EDMA_HANDLE(dev, i));
|
|
EDMA_ClearChannelStatusFlags(DEV_BASE(dev), hw_channel, 0xFFFFFFFF);
|
|
DEV_CHANNEL_DATA(dev, i)->busy = false;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_CPU_CORTEX_M4)
|
|
barrier_dsync_fence_full();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(channels_shared_irq_mask)
|
|
static void dma_mcux_edma_multi_channels_irq_handler(const struct device *dev, uint32_t idx,
|
|
uint32_t *buf, uint32_t mask_width)
|
|
{
|
|
uint32_t *num = &buf[mask_width * idx];
|
|
uint32_t count = 0;
|
|
|
|
for (int _i = 0; _i < mask_width; _i++) {
|
|
uint32_t value = (*num);
|
|
|
|
while (value > 0) {
|
|
if ((value & 0x1) == 1) {
|
|
dma_mcux_edma_irq_handler(dev, count);
|
|
}
|
|
value = value >> 1;
|
|
count++;
|
|
}
|
|
num++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Configure a channel */
|
|
static int dma_mcux_edma_configure(const struct device *dev, uint32_t channel,
|
|
struct dma_config *config)
|
|
{
|
|
/* Check for invalid parameters before dereferencing them. */
|
|
if (NULL == dev || NULL == config) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
edma_handle_t *p_handle = DEV_EDMA_HANDLE(dev, channel);
|
|
struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
|
|
struct dma_block_config *block_config = config->head_block;
|
|
uint32_t slot = config->dma_slot;
|
|
uint32_t hw_channel;
|
|
edma_transfer_type_t transfer_type;
|
|
unsigned int key;
|
|
int ret = 0;
|
|
edma_tcd_t *tcd = NULL;
|
|
|
|
if (slot >= DEV_CFG(dev)->dma_requests) {
|
|
LOG_ERR("source number is out of scope %d", slot);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (channel >= DEV_CFG(dev)->dma_channels) {
|
|
LOG_ERR("out of DMA channel %d", channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
uint8_t dmamux_idx, dmamux_channel;
|
|
|
|
dmamux_idx = DEV_DMAMUX_IDX(dev, channel);
|
|
dmamux_channel = DEV_DMAMUX_CHANNEL(dev, channel);
|
|
#endif
|
|
data->transfer_settings.valid = false;
|
|
|
|
switch (config->channel_direction) {
|
|
case MEMORY_TO_MEMORY:
|
|
transfer_type = kEDMA_MemoryToMemory;
|
|
break;
|
|
case MEMORY_TO_PERIPHERAL:
|
|
transfer_type = kEDMA_MemoryToPeripheral;
|
|
break;
|
|
case PERIPHERAL_TO_MEMORY:
|
|
transfer_type = kEDMA_PeripheralToMemory;
|
|
break;
|
|
case PERIPHERAL_TO_PERIPHERAL:
|
|
transfer_type = kEDMA_PeripheralToPeripheral;
|
|
break;
|
|
default:
|
|
LOG_ERR("not support transfer direction");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!data_size_valid(config->source_data_size)) {
|
|
LOG_ERR("Source unit size error, %d", config->source_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!data_size_valid(config->dest_data_size)) {
|
|
LOG_ERR("Dest unit size error, %d", config->dest_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (block_config->source_gather_en || block_config->dest_scatter_en) {
|
|
if (config->block_count > CONFIG_DMA_TCD_QUEUE_SIZE) {
|
|
LOG_ERR("please config DMA_TCD_QUEUE_SIZE as %d", config->block_count);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
data->transfer_settings.source_data_size = config->source_data_size;
|
|
data->transfer_settings.dest_data_size = config->dest_data_size;
|
|
data->transfer_settings.source_burst_length = config->source_burst_length;
|
|
data->transfer_settings.dest_burst_length = config->dest_burst_length;
|
|
data->transfer_settings.direction = config->channel_direction;
|
|
data->transfer_settings.transfer_type = transfer_type;
|
|
data->transfer_settings.valid = true;
|
|
data->transfer_settings.cyclic = config->cyclic;
|
|
|
|
/* Lock and page in the channel configuration */
|
|
key = irq_lock();
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
|
|
#if DT_INST_PROP(0, nxp_a_on)
|
|
if (config->source_handshake || config->dest_handshake ||
|
|
transfer_type == kEDMA_MemoryToMemory) {
|
|
/*software trigger make the channel always on*/
|
|
LOG_DBG("ALWAYS ON");
|
|
DMAMUX_EnableAlwaysOn(DEV_DMAMUX_BASE(dev, dmamux_idx), dmamux_channel, true);
|
|
} else {
|
|
DMAMUX_SetSource(DEV_DMAMUX_BASE(dev, dmamux_idx), dmamux_channel, slot);
|
|
}
|
|
#else
|
|
DMAMUX_SetSource(DEV_DMAMUX_BASE(dev, dmamux_idx), dmamux_channel, slot);
|
|
#endif
|
|
|
|
/* dam_imx_rt_set_channel_priority(dev, channel, config); */
|
|
DMAMUX_EnableChannel(DEV_DMAMUX_BASE(dev, dmamux_idx), dmamux_channel);
|
|
|
|
#endif
|
|
|
|
if (data->busy) {
|
|
EDMA_AbortTransfer(p_handle);
|
|
}
|
|
EDMA_ResetChannel(DEV_BASE(dev), hw_channel);
|
|
EDMA_CreateHandle(p_handle, DEV_BASE(dev), hw_channel);
|
|
EDMA_SetCallback(p_handle, nxp_edma_callback, (void *)data);
|
|
|
|
#if defined(FSL_FEATURE_EDMA_HAS_CHANNEL_MUX) && FSL_FEATURE_EDMA_HAS_CHANNEL_MUX
|
|
/* First release any peripheral previously associated with this channel */
|
|
EDMA_SetChannelMux(DEV_BASE(dev), hw_channel, 0);
|
|
EDMA_SetChannelMux(DEV_BASE(dev), hw_channel, slot);
|
|
#endif
|
|
|
|
LOG_DBG("channel is %d", channel);
|
|
EDMA_EnableChannelInterrupts(DEV_BASE(dev), hw_channel, kEDMA_ErrorInterruptEnable);
|
|
|
|
/* Initialize all TCD pool as 0*/
|
|
for (int i = 0; i < CONFIG_DMA_TCD_QUEUE_SIZE; i++) {
|
|
memset(&DEV_CFG(dev)->tcdpool[channel][i], 0,
|
|
sizeof(DEV_CFG(dev)->tcdpool[channel][i]));
|
|
}
|
|
|
|
if (block_config->source_gather_en || block_config->dest_scatter_en) {
|
|
if (config->cyclic) {
|
|
/* Loop SG mode */
|
|
data->transfer_settings.write_idx = 0;
|
|
data->transfer_settings.empty_tcds = CONFIG_DMA_TCD_QUEUE_SIZE;
|
|
|
|
EDMA_PrepareTransfer(
|
|
&data->transferConfig, (void *)block_config->source_address,
|
|
config->source_data_size, (void *)block_config->dest_address,
|
|
config->dest_data_size, config->source_burst_length,
|
|
block_config->block_size, transfer_type);
|
|
|
|
/* Init all TCDs with the para in transfer config and link them. */
|
|
for (int i = 0; i < CONFIG_DMA_TCD_QUEUE_SIZE; i++) {
|
|
#if defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
EDMA_TcdSetTransferConfigExt(DEV_BASE(dev),
|
|
&DEV_CFG(dev)->tcdpool[channel][i], &data->transferConfig,
|
|
&DEV_CFG(dev)->tcdpool[channel][(i + 1) %
|
|
CONFIG_DMA_TCD_QUEUE_SIZE]);
|
|
/* Enable Major loop interrupt.*/
|
|
EDMA_TcdEnableInterruptsExt(DEV_BASE(dev),
|
|
&DEV_CFG(dev)->tcdpool[channel][i],
|
|
kEDMA_MajorInterruptEnable);
|
|
#else
|
|
EDMA_TcdSetTransferConfig(&DEV_CFG(dev)->tcdpool[channel][i],
|
|
&data->transferConfig,
|
|
&DEV_CFG(dev)->tcdpool[channel][(i + 1) %
|
|
CONFIG_DMA_TCD_QUEUE_SIZE]);
|
|
EDMA_TcdEnableInterrupts(&DEV_CFG(dev)->tcdpool[channel][i],
|
|
kEDMA_MajorInterruptEnable);
|
|
#endif
|
|
}
|
|
|
|
/* Load valid transfer parameters */
|
|
while (block_config != NULL && data->transfer_settings.empty_tcds > 0) {
|
|
tcd = &(DEV_CFG(dev)->tcdpool[channel]
|
|
[data->transfer_settings.write_idx]);
|
|
|
|
#if defined(FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET) && FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET
|
|
EDMA_TCD_SADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
MEMORY_ConvertMemoryMapAddress(
|
|
(uint32_t)(block_config->source_address),
|
|
kMEMORY_Local2DMA);
|
|
EDMA_TCD_DADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
MEMORY_ConvertMemoryMapAddress(
|
|
(uint32_t)(block_config->dest_address),
|
|
kMEMORY_Local2DMA);
|
|
#else
|
|
|
|
EDMA_TCD_SADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
block_config->source_address;
|
|
EDMA_TCD_DADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
block_config->dest_address;
|
|
#endif
|
|
EDMA_TCD_BITER(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
block_config->block_size / config->source_data_size;
|
|
EDMA_TCD_CITER(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
block_config->block_size / config->source_data_size;
|
|
/*Enable auto stop for last transfer.*/
|
|
if (block_config->next_block == NULL) {
|
|
EDMA_TCD_CSR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) |=
|
|
DMA_CSR_DREQ(1U);
|
|
} else {
|
|
EDMA_TCD_CSR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) &=
|
|
~DMA_CSR_DREQ(1U);
|
|
}
|
|
|
|
data->transfer_settings.write_idx =
|
|
(data->transfer_settings.write_idx + 1) %
|
|
CONFIG_DMA_TCD_QUEUE_SIZE;
|
|
data->transfer_settings.empty_tcds--;
|
|
block_config = block_config->next_block;
|
|
}
|
|
|
|
if (block_config != NULL && data->transfer_settings.empty_tcds == 0) {
|
|
/* User input more blocks than TCD number, return error */
|
|
LOG_ERR("Too much request blocks,increase TCD buffer size!");
|
|
ret = -ENOBUFS;
|
|
}
|
|
/* Push the 1st TCD into HW */
|
|
EDMA_InstallTCD(p_handle->base, hw_channel,
|
|
&DEV_CFG(dev)->tcdpool[channel][0]);
|
|
|
|
} else {
|
|
/* Dynamic Scatter Gather mode */
|
|
EDMA_InstallTCDMemory(p_handle, DEV_CFG(dev)->tcdpool[channel],
|
|
CONFIG_DMA_TCD_QUEUE_SIZE);
|
|
|
|
while (block_config != NULL) {
|
|
EDMA_PrepareTransfer(&(data->transferConfig),
|
|
(void *)block_config->source_address,
|
|
config->source_data_size,
|
|
(void *)block_config->dest_address,
|
|
config->dest_data_size,
|
|
config->source_burst_length,
|
|
block_config->block_size, transfer_type);
|
|
|
|
const status_t submit_status =
|
|
EDMA_SubmitTransfer(p_handle, &(data->transferConfig));
|
|
if (submit_status != kStatus_Success) {
|
|
LOG_ERR("Error submitting EDMA Transfer: 0x%x",
|
|
submit_status);
|
|
ret = -EFAULT;
|
|
}
|
|
block_config = block_config->next_block;
|
|
}
|
|
}
|
|
} else {
|
|
/* block_count shall be 1 */
|
|
LOG_DBG("block size is: %d", block_config->block_size);
|
|
EDMA_PrepareTransfer(&(data->transferConfig),
|
|
(void *)block_config->source_address,
|
|
config->source_data_size,
|
|
(void *)block_config->dest_address,
|
|
config->dest_data_size,
|
|
config->source_burst_length,
|
|
block_config->block_size, transfer_type);
|
|
|
|
const status_t submit_status =
|
|
EDMA_SubmitTransfer(p_handle, &(data->transferConfig));
|
|
if (submit_status != kStatus_Success) {
|
|
LOG_ERR("Error submitting EDMA Transfer: 0x%x", submit_status);
|
|
ret = -EFAULT;
|
|
}
|
|
|
|
LOG_DBG("DMA TCD CSR 0x%x", EDMA_HW_TCD_CSR(dev, hw_channel));
|
|
}
|
|
|
|
if (config->dest_chaining_en) {
|
|
LOG_DBG("link major channel %d", config->linked_channel);
|
|
EDMA_SetChannelLink(DEV_BASE(dev), channel, kEDMA_MajorLink,
|
|
config->linked_channel);
|
|
}
|
|
if (config->source_chaining_en) {
|
|
LOG_DBG("link minor channel %d", config->linked_channel);
|
|
EDMA_SetChannelLink(DEV_BASE(dev), channel, kEDMA_MinorLink,
|
|
config->linked_channel);
|
|
}
|
|
|
|
data->busy = false;
|
|
if (config->dma_callback) {
|
|
LOG_DBG("INSTALL call back on channel %d", channel);
|
|
data->user_data = config->user_data;
|
|
data->dma_callback = config->dma_callback;
|
|
data->dev = dev;
|
|
}
|
|
|
|
irq_unlock(key);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dma_mcux_edma_start(const struct device *dev, uint32_t channel)
|
|
{
|
|
struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
|
|
|
|
LOG_DBG("START TRANSFER");
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
uint8_t dmamux_idx = DEV_DMAMUX_IDX(dev, channel);
|
|
uint8_t dmamux_channel = DEV_DMAMUX_CHANNEL(dev, channel);
|
|
|
|
LOG_DBG("DMAMUX CHCFG 0x%x", DEV_DMAMUX_BASE(dev, dmamux_idx)->CHCFG[dmamux_channel]);
|
|
#endif
|
|
|
|
#if !defined(CONFIG_DMA_MCUX_EDMA_V3) && !defined(CONFIG_DMA_MCUX_EDMA_V4) \
|
|
&& !defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
LOG_DBG("DMA CR 0x%x", DEV_BASE(dev)->CR);
|
|
#endif
|
|
data->busy = true;
|
|
EDMA_StartTransfer(DEV_EDMA_HANDLE(dev, channel));
|
|
return 0;
|
|
}
|
|
|
|
static int dma_mcux_edma_stop(const struct device *dev, uint32_t channel)
|
|
{
|
|
struct dma_mcux_edma_data *data = DEV_DATA(dev);
|
|
uint32_t hw_channel;
|
|
|
|
hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);
|
|
|
|
data->data_cb[channel].transfer_settings.valid = false;
|
|
|
|
if (!data->data_cb[channel].busy) {
|
|
return 0;
|
|
}
|
|
|
|
EDMA_AbortTransfer(DEV_EDMA_HANDLE(dev, channel));
|
|
EDMA_ClearChannelStatusFlags(DEV_BASE(dev), hw_channel,
|
|
kEDMA_DoneFlag | kEDMA_ErrorFlag |
|
|
kEDMA_InterruptFlag);
|
|
EDMA_ResetChannel(DEV_BASE(dev), hw_channel);
|
|
data->data_cb[channel].busy = false;
|
|
return 0;
|
|
}
|
|
|
|
static int dma_mcux_edma_suspend(const struct device *dev, uint32_t channel)
|
|
{
|
|
struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
|
|
|
|
if (!data->busy) {
|
|
return -EINVAL;
|
|
}
|
|
EDMA_StopTransfer(DEV_EDMA_HANDLE(dev, channel));
|
|
return 0;
|
|
}
|
|
|
|
static int dma_mcux_edma_resume(const struct device *dev, uint32_t channel)
|
|
{
|
|
struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
|
|
|
|
if (!data->busy) {
|
|
return -EINVAL;
|
|
}
|
|
EDMA_StartTransfer(DEV_EDMA_HANDLE(dev, channel));
|
|
return 0;
|
|
}
|
|
|
|
static void dma_mcux_edma_update_hw_tcd(const struct device *dev, uint32_t channel, uint32_t src,
|
|
uint32_t dst, size_t size)
|
|
{
|
|
EDMA_HW_TCD_SADDR(dev, channel) = src;
|
|
EDMA_HW_TCD_DADDR(dev, channel) = dst;
|
|
EDMA_HW_TCD_BITER(dev, channel) = size;
|
|
EDMA_HW_TCD_CITER(dev, channel) = size;
|
|
EDMA_HW_TCD_CSR(dev, channel) |= DMA_CSR_DREQ(1U);
|
|
}
|
|
|
|
static int dma_mcux_edma_reload(const struct device *dev, uint32_t channel,
|
|
uint32_t src, uint32_t dst, size_t size)
|
|
{
|
|
struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
|
|
edma_tcd_t *tcd = NULL;
|
|
edma_tcd_t *pre_tcd = NULL;
|
|
uint32_t hw_id, sw_id;
|
|
uint8_t pre_idx;
|
|
|
|
/* Lock the channel configuration */
|
|
const unsigned int key = irq_lock();
|
|
int ret = 0;
|
|
|
|
if (!data->transfer_settings.valid) {
|
|
LOG_ERR("Invalid EDMA settings on initial config. Configure DMA before reload.");
|
|
ret = -EFAULT;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (data->transfer_settings.cyclic) {
|
|
if (data->transfer_settings.empty_tcds == 0) {
|
|
LOG_ERR("TCD list is full in loop mode.");
|
|
ret = -ENOBUFS;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Convert size into major loop count */
|
|
size = size / data->transfer_settings.dest_data_size;
|
|
|
|
/* Previous TCD index in circular list */
|
|
pre_idx = data->transfer_settings.write_idx - 1;
|
|
if (pre_idx >= CONFIG_DMA_TCD_QUEUE_SIZE) {
|
|
pre_idx = CONFIG_DMA_TCD_QUEUE_SIZE - 1;
|
|
}
|
|
|
|
/* Configure a TCD for the transfer */
|
|
tcd = &(DEV_CFG(dev)->tcdpool[channel][data->transfer_settings.write_idx]);
|
|
pre_tcd = &(DEV_CFG(dev)->tcdpool[channel][pre_idx]);
|
|
|
|
#if defined(FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET) && FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET
|
|
EDMA_TCD_SADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
MEMORY_ConvertMemoryMapAddress(src, kMEMORY_Local2DMA);
|
|
EDMA_TCD_DADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
|
|
MEMORY_ConvertMemoryMapAddress(dst, kMEMORY_Local2DMA);
|
|
#else
|
|
EDMA_TCD_SADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) = src;
|
|
EDMA_TCD_DADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) = dst;
|
|
#endif
|
|
EDMA_TCD_BITER(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) = size;
|
|
EDMA_TCD_CITER(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) = size;
|
|
/* Enable automatically stop */
|
|
EDMA_TCD_CSR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) |= DMA_CSR_DREQ(1U);
|
|
sw_id = EDMA_TCD_DLAST_SGA(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev)));
|
|
|
|
/* Block the peripheral's hardware request trigger to prevent
|
|
* starting the DMA before updating the TCDs. Make sure the
|
|
* code between EDMA_DisableChannelRequest() and
|
|
* EDMA_EnableChannelRequest() is minimum.
|
|
*/
|
|
EDMA_DisableChannelRequest(DEV_BASE(dev), channel);
|
|
|
|
/* Wait for the DMA to be inactive before updating the TCDs.
|
|
* The CSR[ACTIVE] bit will deassert quickly after the EDMA's
|
|
* minor loop burst completes.
|
|
*/
|
|
while (EDMA_HW_TCD_CSR(dev, channel) & EDMA_HW_TCD_CH_ACTIVE_MASK) {
|
|
;
|
|
}
|
|
|
|
/* Identify the current active TCD. Use DLAST_SGA as the HW ID */
|
|
hw_id = EDMA_GetNextTCDAddress(DEV_EDMA_HANDLE(dev, channel));
|
|
if (data->transfer_settings.empty_tcds >= CONFIG_DMA_TCD_QUEUE_SIZE ||
|
|
hw_id == sw_id) {
|
|
/* All transfers have been done.DMA is stopped automatically,
|
|
* invalid TCD has been loaded into the HW, update HW.
|
|
*/
|
|
dma_mcux_edma_update_hw_tcd(dev, channel, src, dst, size);
|
|
LOG_DBG("Transfer done,auto stop");
|
|
|
|
} else {
|
|
/* Previous TCD can automatically start this TCD.
|
|
* Enable the peripheral DMA request in the previous TCD
|
|
*/
|
|
EDMA_TCD_CSR(pre_tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) &=
|
|
~DMA_CSR_DREQ(1U);
|
|
|
|
if (data->transfer_settings.empty_tcds == CONFIG_DMA_TCD_QUEUE_SIZE - 1 ||
|
|
hw_id == (uint32_t)tcd) {
|
|
/* DMA is running on last transfer. HW has loaded the last one,
|
|
* we need ensure it's DREQ is cleared.
|
|
*/
|
|
EDMA_EnableAutoStopRequest(DEV_BASE(dev), channel, false);
|
|
LOG_DBG("Last transfer.");
|
|
}
|
|
LOG_DBG("Manu stop");
|
|
}
|
|
|
|
#ifdef CONFIG_DMA_MCUX_EDMA
|
|
/* It seems that there is HW issue which may cause ESG bit is cleared.
|
|
* This is a workaround. Clear the DONE bit before setting ESG bit.
|
|
*/
|
|
EDMA_ClearChannelStatusFlags(DEV_BASE(dev), channel, kEDMA_DoneFlag);
|
|
EDMA_HW_TCD_CSR(dev, channel) |= DMA_CSR_ESG_MASK;
|
|
#elif (CONFIG_DMA_MCUX_EDMA_V3 || CONFIG_DMA_MCUX_EDMA_V4 || CONFIG_DMA_MCUX_EDMA_V5)
|
|
/*We have not verified if this issue exist on V3/V4 HW, jut place a holder here. */
|
|
#endif
|
|
/* TCDs are configured. Resume DMA */
|
|
EDMA_EnableChannelRequest(DEV_BASE(dev), channel);
|
|
|
|
/* Update the write index and available TCD numbers. */
|
|
data->transfer_settings.write_idx =
|
|
(data->transfer_settings.write_idx + 1) % CONFIG_DMA_TCD_QUEUE_SIZE;
|
|
data->transfer_settings.empty_tcds--;
|
|
|
|
LOG_DBG("w_idx:%d no:%d(ch:%d)", data->transfer_settings.write_idx,
|
|
data->transfer_settings.empty_tcds, channel);
|
|
|
|
} else {
|
|
/* Dynamice Scatter/Gather mode:
|
|
* If the tcdPool is not in use (no s/g) then only a single TCD
|
|
* can be active at once.
|
|
*/
|
|
if (data->busy && data->edma_handle.tcdPool == NULL) {
|
|
LOG_ERR("EDMA busy. Wait until the transfer completes before reloading.");
|
|
ret = -EBUSY;
|
|
goto cleanup;
|
|
}
|
|
|
|
EDMA_PrepareTransfer(&(data->transferConfig), (void *)src,
|
|
data->transfer_settings.source_data_size, (void *)dst,
|
|
data->transfer_settings.dest_data_size,
|
|
data->transfer_settings.source_burst_length, size,
|
|
data->transfer_settings.transfer_type);
|
|
|
|
const status_t submit_status =
|
|
EDMA_SubmitTransfer(DEV_EDMA_HANDLE(dev, channel), &(data->transferConfig));
|
|
|
|
if (submit_status != kStatus_Success) {
|
|
LOG_ERR("Error submitting EDMA Transfer: 0x%x", submit_status);
|
|
ret = -EFAULT;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
irq_unlock(key);
|
|
return ret;
|
|
}
|
|
|
|
static int dma_mcux_edma_get_status(const struct device *dev, uint32_t channel,
|
|
struct dma_status *status)
|
|
{
|
|
uint32_t hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);
|
|
|
|
if (DEV_CHANNEL_DATA(dev, channel)->busy) {
|
|
status->busy = true;
|
|
/* pending_length is in bytes. Multiply remaining major loop
|
|
* count by NBYTES for each minor loop
|
|
*/
|
|
status->pending_length =
|
|
EDMA_GetRemainingMajorLoopCount(DEV_BASE(dev), hw_channel) *
|
|
DEV_CHANNEL_DATA(dev, channel)->transfer_settings.source_data_size;
|
|
} else {
|
|
status->busy = false;
|
|
status->pending_length = 0;
|
|
}
|
|
status->dir = DEV_CHANNEL_DATA(dev, channel)->transfer_settings.direction;
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
uint8_t dmamux_idx = DEV_DMAMUX_IDX(dev, channel);
|
|
uint8_t dmamux_channel = DEV_DMAMUX_CHANNEL(dev, channel);
|
|
|
|
LOG_DBG("DMAMUX CHCFG 0x%x", DEV_DMAMUX_BASE(dev, dmamux_idx)->CHCFG[dmamux_channel]);
|
|
#endif
|
|
|
|
#if defined(CONFIG_DMA_MCUX_EDMA_V3) || defined(CONFIG_DMA_MCUX_EDMA_V4)
|
|
LOG_DBG("DMA MP_CSR 0x%x", DEV_BASE(dev)->MP_CSR);
|
|
LOG_DBG("DMA MP_ES 0x%x", DEV_BASE(dev)->MP_ES);
|
|
LOG_DBG("DMA CHx_ES 0x%x", DEV_BASE(dev)->CH[hw_channel].CH_ES);
|
|
LOG_DBG("DMA CHx_CSR 0x%x", DEV_BASE(dev)->CH[hw_channel].CH_CSR);
|
|
LOG_DBG("DMA CHx_ES 0x%x", DEV_BASE(dev)->CH[hw_channel].CH_ES);
|
|
LOG_DBG("DMA CHx_INT 0x%x", DEV_BASE(dev)->CH[hw_channel].CH_INT);
|
|
LOG_DBG("DMA TCD_CSR 0x%x", DEV_BASE(dev)->CH[hw_channel].TCD_CSR);
|
|
#elif defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
LOG_DBG("DMA MP_CSR 0x%x", DEV_BASE(dev)->MP_CSR);
|
|
LOG_DBG("DMA MP_ES 0x%x", DEV_BASE(dev)->MP_ES);
|
|
LOG_DBG("DMA CHx_ES 0x%x", DEV_BASE(dev)->TCD[hw_channel].CH_ES);
|
|
LOG_DBG("DMA CHx_CSR 0x%x", DEV_BASE(dev)->TCD[hw_channel].CH_CSR);
|
|
LOG_DBG("DMA CHx_ES 0x%x", DEV_BASE(dev)->TCD[hw_channel].CH_ES);
|
|
LOG_DBG("DMA CHx_INT 0x%x", DEV_BASE(dev)->TCD[hw_channel].CH_INT);
|
|
LOG_DBG("DMA TCD_CSR 0x%x", DEV_BASE(dev)->TCD[hw_channel].CSR);
|
|
#else
|
|
LOG_DBG("DMA CR 0x%x", DEV_BASE(dev)->CR);
|
|
LOG_DBG("DMA INT 0x%x", DEV_BASE(dev)->INT);
|
|
LOG_DBG("DMA ERQ 0x%x", DEV_BASE(dev)->ERQ);
|
|
LOG_DBG("DMA ES 0x%x", DEV_BASE(dev)->ES);
|
|
LOG_DBG("DMA ERR 0x%x", DEV_BASE(dev)->ERR);
|
|
LOG_DBG("DMA HRS 0x%x", DEV_BASE(dev)->HRS);
|
|
LOG_DBG("data csr is 0x%x", DEV_BASE(dev)->TCD[hw_channel].CSR);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static bool dma_mcux_edma_channel_filter(const struct device *dev,
|
|
int channel_id, void *param)
|
|
{
|
|
enum dma_channel_filter *filter = (enum dma_channel_filter *)param;
|
|
|
|
if (filter && *filter == DMA_CHANNEL_PERIODIC) {
|
|
if (channel_id > 3) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static DEVICE_API(dma, dma_mcux_edma_api) = {
|
|
.reload = dma_mcux_edma_reload,
|
|
.config = dma_mcux_edma_configure,
|
|
.start = dma_mcux_edma_start,
|
|
.stop = dma_mcux_edma_stop,
|
|
.suspend = dma_mcux_edma_suspend,
|
|
.resume = dma_mcux_edma_resume,
|
|
.get_status = dma_mcux_edma_get_status,
|
|
.chan_filter = dma_mcux_edma_channel_filter,
|
|
};
|
|
|
|
static int dma_mcux_edma_init(const struct device *dev)
|
|
{
|
|
const struct dma_mcux_edma_config *config = dev->config;
|
|
struct dma_mcux_edma_data *data = dev->data;
|
|
|
|
edma_config_t userConfig = { 0 };
|
|
|
|
LOG_DBG("INIT NXP EDMA");
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < config->dma_channels / config->channels_per_mux; i++) {
|
|
DMAMUX_Init(DEV_DMAMUX_BASE(dev, i));
|
|
}
|
|
#endif
|
|
|
|
EDMA_GetDefaultConfig(&userConfig);
|
|
EDMA_Init(DEV_BASE(dev), &userConfig);
|
|
#ifdef CONFIG_DMA_MCUX_EDMA_V3
|
|
/* Channel linking available and will be controlled by each channel's link settings */
|
|
EDMA_EnableAllChannelLink(DEV_BASE(dev), true);
|
|
#endif
|
|
config->irq_config_func(dev);
|
|
data->dma_ctx.magic = DMA_MAGIC;
|
|
data->dma_ctx.dma_channels = config->dma_channels;
|
|
data->dma_ctx.atomic = data->channels_atomic;
|
|
return 0;
|
|
}
|
|
|
|
/* The shared error interrupt (if have) must be declared as the last element in devicetree */
|
|
#if !DT_INST_PROP(0, no_error_irq)
|
|
#define NUM_IRQS_WITHOUT_ERROR_IRQ(n) UTIL_DEC(DT_NUM_IRQS(DT_DRV_INST(n)))
|
|
#else
|
|
#define NUM_IRQS_WITHOUT_ERROR_IRQ(n) DT_NUM_IRQS(DT_DRV_INST(n))
|
|
#endif
|
|
|
|
#define IRQ_CONFIG(n, idx, fn) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, idx, irq), \
|
|
DT_INST_IRQ_BY_IDX(n, idx, priority), \
|
|
fn, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQ_BY_IDX(n, idx, irq)); \
|
|
}
|
|
|
|
#define EDMA_CHANNELS_MASK(n) static uint32_t edma_channel_mask_##n[] = \
|
|
DT_PROP(DT_DRV_INST(n), channels_shared_irq_mask);
|
|
|
|
#define GET_EDMA_CHANNEL_SHARED_IRQ_MASK_WIDTH(n) \
|
|
(DT_INST_PROP(n, dma_channels) / 32)
|
|
|
|
#define EDMA_CHANNELS_SHARED_REGISTER_IN_IRQ(dev, idx, n) \
|
|
dma_mcux_edma_multi_channels_irq_handler(dev, idx, edma_channel_mask_##n, \
|
|
GET_EDMA_CHANNEL_SHARED_IRQ_MASK_WIDTH(n));
|
|
|
|
#define DMA_MCUX_EDMA_IRQ_DEFINE(idx, n) \
|
|
static void dma_mcux_edma_##n##_irq_##idx(const struct device *dev) \
|
|
{ \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, channels_shared_irq_mask), \
|
|
(EDMA_CHANNELS_SHARED_REGISTER_IN_IRQ(dev, idx, n)), \
|
|
(dma_mcux_edma_irq_handler(dev, idx);)) \
|
|
\
|
|
IF_ENABLED(UTIL_BOOL(DT_INST_PROP(n, irq_shared_offset)), \
|
|
(dma_mcux_edma_irq_handler(dev, \
|
|
idx + DT_INST_PROP(n, irq_shared_offset));)) \
|
|
\
|
|
IF_ENABLED(CONFIG_CPU_CORTEX_M4, (barrier_dsync_fence_full();)) \
|
|
}
|
|
|
|
#define DMA_MCUX_EDMA_IRQ_CONFIG(idx, n) \
|
|
IRQ_CONFIG(n, idx, dma_mcux_edma_##n##_irq_##idx)
|
|
|
|
#define DMA_MCUX_EDMA_CONFIG_FUNC(n) \
|
|
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, channels_shared_irq_mask), \
|
|
(EDMA_CHANNELS_MASK(n))) \
|
|
LISTIFY(NUM_IRQS_WITHOUT_ERROR_IRQ(n), DMA_MCUX_EDMA_IRQ_DEFINE, (), n) \
|
|
static void dma_imx_config_func_##n(const struct device *dev) \
|
|
{ \
|
|
ARG_UNUSED(dev); \
|
|
\
|
|
LISTIFY(NUM_IRQS_WITHOUT_ERROR_IRQ(n), \
|
|
DMA_MCUX_EDMA_IRQ_CONFIG, (;), n) \
|
|
\
|
|
COND_CODE_1(DT_INST_PROP(n, no_error_irq), (), \
|
|
(IRQ_CONFIG(n, NUM_IRQS_WITHOUT_ERROR_IRQ(n), \
|
|
dma_mcux_edma_error_irq_handler))) \
|
|
\
|
|
LOG_DBG("install irq done"); \
|
|
}
|
|
|
|
#if DMA_MCUX_HAS_CHANNEL_GAP
|
|
#define DMA_MCUX_EDMA_CHANNEL_GAP(n) \
|
|
.channel_gap = DT_INST_PROP_OR(n, channel_gap, \
|
|
{[0 ... 1] = DT_INST_PROP(n, dma_channels)}),
|
|
#else
|
|
#define DMA_MCUX_EDMA_CHANNEL_GAP(n)
|
|
#endif
|
|
|
|
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
|
|
#define DMA_MCUX_EDMA_MUX(idx, n) \
|
|
(DMAMUX_Type *)DT_INST_REG_ADDR_BY_IDX(n, UTIL_INC(idx))
|
|
|
|
#define DMAMUX_BASE_INIT_DEFINE(n) \
|
|
static DMAMUX_Type *dmamux_base_##n[] = { \
|
|
LISTIFY(UTIL_DEC(DT_NUM_REGS(DT_DRV_INST(n))), \
|
|
DMA_MCUX_EDMA_MUX, (,), n) \
|
|
};
|
|
|
|
#define DMAMUX_BASE_INIT(n) .dmamux_base = &dmamux_base_##n[0],
|
|
#define CHANNELS_PER_MUX(n) .channels_per_mux = DT_INST_PROP(n, dma_channels) / \
|
|
ARRAY_SIZE(dmamux_base_##n),
|
|
|
|
#else
|
|
#define DMAMUX_BASE_INIT_DEFINE(n)
|
|
#define DMAMUX_BASE_INIT(n)
|
|
#define CHANNELS_PER_MUX(n)
|
|
#endif
|
|
|
|
#if defined(CONFIG_DMA_MCUX_EDMA_V5)
|
|
#define DMA_TCD_ALIGN_SIZE 64
|
|
#else
|
|
#define DMA_TCD_ALIGN_SIZE 32
|
|
#endif
|
|
|
|
/*
|
|
* define the dma
|
|
*/
|
|
#define DMA_INIT(n) \
|
|
DMAMUX_BASE_INIT_DEFINE(n) \
|
|
static void dma_imx_config_func_##n(const struct device *dev); \
|
|
static __aligned(DMA_TCD_ALIGN_SIZE) EDMA_TCDPOOL_CACHE_ATTR edma_tcd_t \
|
|
dma_tcdpool##n[DT_INST_PROP(n, dma_channels)][CONFIG_DMA_TCD_QUEUE_SIZE];\
|
|
static const struct dma_mcux_edma_config dma_config_##n = { \
|
|
.base = (DMAx_Type *)DT_INST_REG_ADDR(n), \
|
|
DMAMUX_BASE_INIT(n) \
|
|
.dma_requests = DT_INST_PROP(n, dma_requests), \
|
|
.dma_channels = DT_INST_PROP(n, dma_channels), \
|
|
CHANNELS_PER_MUX(n) \
|
|
.irq_config_func = dma_imx_config_func_##n, \
|
|
.dmamux_reg_offset = DT_INST_PROP(n, dmamux_reg_offset), \
|
|
DMA_MCUX_EDMA_CHANNEL_GAP(n) \
|
|
.tcdpool = dma_tcdpool##n, \
|
|
}; \
|
|
\
|
|
static struct call_back \
|
|
dma_data_callback_##n[DT_INST_PROP(n, dma_channels)]; \
|
|
static ATOMIC_DEFINE( \
|
|
dma_channels_atomic_##n, DT_INST_PROP(n, dma_channels)); \
|
|
static struct dma_mcux_edma_data dma_data_##n = { \
|
|
.data_cb = dma_data_callback_##n, \
|
|
.channels_atomic = dma_channels_atomic_##n, \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, \
|
|
dma_mcux_edma_init, NULL, \
|
|
&dma_data_##n, &dma_config_##n, \
|
|
PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, \
|
|
&dma_mcux_edma_api); \
|
|
\
|
|
DMA_MCUX_EDMA_CONFIG_FUNC(n);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DMA_INIT)
|