/*
 * Copyright 2020-2025 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 {
	DEVICE_MMIO_NAMED_ROM(edma_mmio);
#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];
};

struct dma_mcux_channel_transfer_edma_settings {
	uint32_t source_data_size;
	uint32_t dest_data_size;
	uint32_t source_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 {
	DEVICE_MMIO_NAMED_RAM(edma_mmio);
	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 *)DEVICE_MMIO_NAMED_GET(dev, edma_mmio))

#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

#if defined(FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET) && FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET
#define EDMA_MMAP_ADDR(addr) MEMORY_ConvertMemoryMapAddress(addr, kMEMORY_Local2DMA)
#else
#define EDMA_MMAP_ADDR(addr) addr
#endif /* FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET */

#if DMA_MCUX_HAS_CHANNEL_GAP
/*
 * 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)
{
	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]);
}

static ALWAYS_INLINE uint32_t dma_mcux_edma_remove_channel_gap(const struct device *dev,
								uint32_t channel)
{
	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
#define dma_mcux_edma_add_channel_gap(dev, channel) channel
#define dma_mcux_edma_remove_channel_gap(dev, channel) channel
#endif /* DMA_MCUX_HAS_CHANNEL_GAP */

static bool data_size_valid(const size_t data_size)
{
	return IS_POWER_OF_TWO(data_size) &&
	       (data_size <= CONFIG_DMA_MCUX_MAX_DATA_SIZE);
}

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

#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
static void edma_configure_dmamux(const struct device *dev, uint32_t channel,
				   struct dma_config *config)
{
	uint8_t dmamux_channel = DEV_DMAMUX_CHANNEL(dev, channel);
	uint8_t dmamux_idx = DEV_DMAMUX_IDX(dev, channel);
	uint32_t slot = config->dma_slot;

#if DT_INST_PROP(0, nxp_a_on)
	struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
	edma_transfer_type_t transfer_type = data->transfer_settings.transfer_type;

	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 /* nxp_a_on */

	/* dam_imx_rt_set_channel_priority(dev, channel, config); */
	DMAMUX_EnableChannel(DEV_DMAMUX_BASE(dev, dmamux_idx), dmamux_channel);
}

static void edma_log_dmamux(const struct device *dev, uint32_t channel)
{
	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]);
}
#else
#define edma_configure_dmamux(...)
#define edma_log_dmamux(...)
#endif /* FSL_FEATURE_SOC_DMA_MUX_COUNT */

static inline void dma_mcux_edma_configure_muxes(const struct device *dev, uint32_t channel,
						 struct dma_config *config)
{
	edma_configure_dmamux(dev, channel, config);

#if defined(FSL_FEATURE_EDMA_HAS_CHANNEL_MUX) && FSL_FEATURE_EDMA_HAS_CHANNEL_MUX
	uint32_t slot = config->dma_slot;
	uint32_t hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);

	/* 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
}


static int dma_mcux_edma_configure_sg_loop(const struct device *dev,
					   uint32_t channel,
					   struct dma_config *config,
					   edma_transfer_type_t transfer_type)
{
	uint32_t hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);
	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;
	int ret = 0;
	edma_tcd_t *tcd = NULL;

	/* 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]);

		EDMA_TCD_SADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
			EDMA_MMAP_ADDR(block_config->source_address);
		EDMA_TCD_DADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) =
			EDMA_MMAP_ADDR(block_config->dest_address);
		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]);

	return ret;
}

static int dma_mcux_edma_configure_sg_dynamic(const struct device *dev,
					      uint32_t channel,
					      struct dma_config *config,
					      edma_transfer_type_t transfer_type)
{
	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;
	int ret = 0;

	/* 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;
	}

	return ret;
}

static int dma_mcux_edma_configure_basic(const struct device *dev,
					      uint32_t channel,
					      struct dma_config *config,
					      edma_transfer_type_t transfer_type)
{
	edma_handle_t *p_handle = DEV_EDMA_HANDLE(dev, channel);
	struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
	struct dma_mcux_channel_transfer_edma_settings *xfer_settings = &data->transfer_settings;
	struct dma_block_config *block_config = config->head_block;
	uint32_t hw_channel;
	int ret = 0;

	/* 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,
			     xfer_settings->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));

	return ret;
}

static int dma_mcux_edma_configure_hardware(const struct device *dev, uint32_t channel,
					     struct dma_config *config)
{
	struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
	edma_transfer_type_t transfer_type = data->transfer_settings.transfer_type;
	struct dma_block_config *block_config = config->head_block;
	bool sg_mode = block_config->source_gather_en || block_config->dest_scatter_en;
	uint32_t hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);

	dma_mcux_edma_configure_muxes(dev, channel, config);

#if defined(CONFIG_DMA_MCUX_EDMA_V3) && \
	(!defined(FSL_FEATURE_SOC_DMAMUX_COUNT) || (FSL_FEATURE_SOC_DMAMUX_COUNT == 0))
	if (transfer_type == kEDMA_MemoryToMemory && (sg_mode || config->block_count > 1)) {
		LOG_WRN("mem2mem xfer scatter gather not supported");
		return -ENOTSUP;
	}
#endif

	if (sg_mode && config->cyclic) {
		dma_mcux_edma_configure_sg_loop(dev, channel, config, transfer_type);
	} else if (sg_mode) {
		dma_mcux_edma_configure_sg_dynamic(dev, channel, config, transfer_type);
	} else {
		dma_mcux_edma_configure_basic(dev, channel, config, transfer_type);
	}

	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);
	}

	EDMA_EnableChannelInterrupts(DEV_BASE(dev), hw_channel, kEDMA_ErrorInterruptEnable);

	return 0;
}

static inline int dma_mcux_edma_validate_cfg(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;
	}

	struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
	struct dma_block_config *block_config = config->head_block;
	uint32_t slot = config->dma_slot;
	edma_transfer_type_t *type = &data->transfer_settings.transfer_type;

	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;
	}
	LOG_DBG("channel is %d", channel);

	data->transfer_settings.valid = false;

	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;
		}
	}

	switch (config->channel_direction) {
	case MEMORY_TO_MEMORY:
		*type = kEDMA_MemoryToMemory;
		break;
	case MEMORY_TO_PERIPHERAL:
		*type = kEDMA_MemoryToPeripheral;
		break;
	case PERIPHERAL_TO_MEMORY:
		*type = kEDMA_PeripheralToMemory;
		break;
	case PERIPHERAL_TO_PERIPHERAL:
		*type = kEDMA_PeripheralToPeripheral;
		break;
	default:
		LOG_ERR("not support transfer direction");
		return -EINVAL;
	}

	return 0;
}

/* fills out transfer settings struct based on api config */
static inline void dma_mcux_edma_set_xfer_settings(const struct device *dev, uint32_t channel,
						   struct dma_config *config)
{
	struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
	struct dma_mcux_channel_transfer_edma_settings *xfer_settings = &data->transfer_settings;

	xfer_settings->source_burst_length = config->source_burst_length;
#if defined(CONFIG_DMA_MCUX_EDMA_V3) && \
	(!defined(FSL_FEATURE_SOC_DMAMUX_COUNT) || (FSL_FEATURE_SOC_DMAMUX_COUNT == 0))
	struct dma_block_config *block_config = config->head_block;

	if (xfer_settings->transfer_type == kEDMA_MemoryToMemory) {
		xfer_settings->source_burst_length = block_config->block_size;

	}
#endif
	xfer_settings->source_data_size = config->source_data_size;
	xfer_settings->dest_data_size = config->dest_data_size;
	xfer_settings->direction = config->channel_direction;
	xfer_settings->valid = true;
	xfer_settings->cyclic = config->cyclic;
}

/* stops, resets, and creates new MCUX SDK handle for a channel */
static inline void dma_mcux_edma_reset_channel(const struct device *dev, uint32_t channel)
{
	struct call_back *data = DEV_CHANNEL_DATA(dev, channel);
	edma_handle_t *p_handle = DEV_EDMA_HANDLE(dev, channel);
	uint32_t hw_channel = dma_mcux_edma_add_channel_gap(dev, channel);

	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);
}


/* Configure a channel */
static int dma_mcux_edma_configure(const struct device *dev, uint32_t channel,
				   struct dma_config *config)
{
	unsigned int key;
	int ret = 0;

	ret = dma_mcux_edma_validate_cfg(dev, channel, config);
	if (ret) {
		return ret;
	}

	dma_mcux_edma_set_xfer_settings(dev, channel, config);

	/* Lock and page in the channel configuration */
	key = irq_lock();

	dma_mcux_edma_reset_channel(dev, channel);

	/* 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]));
	}

	ret = dma_mcux_edma_configure_hardware(dev, channel, config);
	if (ret) {
		return ret;
	}

	struct call_back *data = DEV_CHANNEL_DATA(dev, 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 0;
}

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");

	edma_log_dmamux(dev, channel);

#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));
#if defined(CONFIG_DMA_MCUX_EDMA_V3) && \
	(!defined(FSL_FEATURE_SOC_DMAMUX_COUNT) || (FSL_FEATURE_SOC_DMAMUX_COUNT == 0))
	struct dma_mcux_channel_transfer_edma_settings *xfer_settings = &data->transfer_settings;

	if (xfer_settings->transfer_type == kEDMA_MemoryToMemory) {
		EDMA_TriggerChannelStart(DEV_BASE(dev), channel);
	}
#endif
	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 defined(CONFIG_DMA_MCUX_EDMA_V3) && \
	(!defined(FSL_FEATURE_SOC_DMAMUX_COUNT) || (FSL_FEATURE_SOC_DMAMUX_COUNT == 0))
	struct dma_mcux_channel_transfer_edma_settings *xfer_settings = &data->transfer_settings;

	if (xfer_settings->transfer_type == kEDMA_MemoryToMemory) {
		/* can't suspend this transfer, effectively a not implemented function */
		return -ENOSYS;
	}
#endif

	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) = EDMA_MMAP_ADDR(src);
	EDMA_HW_TCD_DADDR(dev, channel) = EDMA_MMAP_ADDR(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 edma_reload_loop(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;

	if (data->transfer_settings.empty_tcds == 0) {
		LOG_ERR("TCD list is full in loop mode.");
		return -ENOBUFS;
	}

	/* 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]);

	EDMA_TCD_SADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) = EDMA_MMAP_ADDR(src);
	EDMA_TCD_DADDR(tcd, EDMA_TCD_TYPE((void *)DEV_BASE(dev))) = EDMA_MMAP_ADDR(dst);
	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);

	return 0;
}

static int edma_reload_dynamic(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);

	/* 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.");
		return -EBUSY;
	}

	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);
		return -EFAULT;
	}

	return 0;
}

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);
	int ret = 0;

	/* Lock the channel configuration */
	const unsigned int key = irq_lock();

	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) {
		ret = edma_reload_loop(dev, channel, src, dst, size);
	} else {
		ret = edma_reload_dynamic(dev, channel, src, dst, size);
	}

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;

	edma_log_dmamux(dev, channel);

#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);
	LOG_DBG("DMA TCD_SADDR 0x%x", DEV_BASE(dev)->TCD[hw_channel].SADDR);
	LOG_DBG("DMA TCD_DADDR 0x%x", DEV_BASE(dev)->TCD[hw_channel].DADDR);
#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");

	DEVICE_MMIO_NAMED_MAP(dev, edma_mmio, K_MEM_CACHE_NONE | K_MEM_DIRECT_MAP);

#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_IRQN_BY_IDX(n, idx),			\
			    DT_INST_IRQ_BY_IDX(n, idx, priority),		\
			    fn,							\
			    DEVICE_DT_INST_GET(n), 0);				\
			    irq_enable(DT_INST_IRQN_BY_IDX(n, idx));	\
	}

#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

/*
 * 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.
 */
#if !defined(CONFIG_CPU_HAS_DCACHE)
/* no cache means no worries */
#define EDMA_TCDPOOL_CACHE_ATTR
#elif defined(CONFIG_DMA_MCUX_USE_DTCM_FOR_DMA_DESCRIPTORS)
#define EDMA_TCDPOOL_CACHE_ATTR __dtcm_noinit_section
#elif defined(CONFIG_NOCACHE_MEMORY)
#define EDMA_TCDPOOL_CACHE_ATTR __nocache
#else
#error Unexpected or disallowed cache situation for dma descriptors
#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 = {		\
		DEVICE_MMIO_NAMED_ROM_INIT(edma_mmio, DT_DRV_INST(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)
