/*
 * Copyright (c) 2022 TOKITA Hiroshi <tokita.hiroshi@gmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/device.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/gd32.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/reset.h>
#include <zephyr/logging/log.h>

#include <gd32_dma.h>
#include <zephyr/irq.h>

#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
#define DT_DRV_COMPAT gd_gd32_dma_v1
#elif DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma)
#define DT_DRV_COMPAT gd_gd32_dma
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
#define CHXCTL_PERIEN_OFFSET	  ((uint32_t)25U)
#define GD32_DMA_CHXCTL_DIR	  BIT(6)
#define GD32_DMA_CHXCTL_M2M	  BIT(7)
#define GD32_DMA_INTERRUPT_ERRORS (DMA_CHXCTL_SDEIE | DMA_CHXCTL_TAEIE)
#define GD32_DMA_FLAG_ERRORS	  (DMA_FLAG_SDE | DMA_FLAG_TAE)
#else
#define GD32_DMA_CHXCTL_DIR	  BIT(4)
#define GD32_DMA_CHXCTL_M2M	  BIT(14)
#define GD32_DMA_INTERRUPT_ERRORS DMA_CHXCTL_ERRIE
#define GD32_DMA_FLAG_ERRORS	  DMA_FLAG_ERR
#endif

#ifdef CONFIG_SOC_SERIES_GD32F3X0
#undef DMA_INTF
#undef DMA_INTC
#undef DMA_CHCTL
#undef DMA_CHCNT
#undef DMA_CHPADDR
#undef DMA_CHMADDR

#define DMA_INTF(dma)	     REG32(dma + 0x00UL)
#define DMA_INTC(dma)	     REG32(dma + 0x04UL)
#define DMA_CHCTL(dma, ch)   REG32((dma + 0x08UL) + 0x14UL * (uint32_t)(ch))
#define DMA_CHCNT(dma, ch)   REG32((dma + 0x0CUL) + 0x14UL * (uint32_t)(ch))
#define DMA_CHPADDR(dma, ch) REG32((dma + 0x10UL) + 0x14UL * (uint32_t)(ch))
#define DMA_CHMADDR(dma, ch) REG32((dma + 0x14UL) + 0x14UL * (uint32_t)(ch))
#endif

#define GD32_DMA_INTF(dma)	  DMA_INTF(dma)
#define GD32_DMA_INTC(dma)	  DMA_INTC(dma)
#define GD32_DMA_CHCTL(dma, ch)	  DMA_CHCTL((dma), (ch))
#define GD32_DMA_CHCNT(dma, ch)	  DMA_CHCNT((dma), (ch))
#define GD32_DMA_CHPADDR(dma, ch) DMA_CHPADDR((dma), (ch))
#define GD32_DMA_CHMADDR(dma, ch) DMA_CHMADDR((dma), (ch))

LOG_MODULE_REGISTER(dma_gd32, CONFIG_DMA_LOG_LEVEL);

struct dma_gd32_config {
	uint32_t reg;
	uint32_t channels;
	uint16_t clkid;
	bool mem2mem;
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	struct reset_dt_spec reset;
#endif
	void (*irq_configure)(void);
};

struct dma_gd32_channel {
	dma_callback_t callback;
	void *user_data;
	uint32_t direction;
	bool busy;
};

struct dma_gd32_data {
	struct dma_context ctx;
	struct dma_gd32_channel *channels;
};

struct dma_gd32_srcdst_config {
	uint32_t addr;
	uint32_t adj;
	uint32_t width;
};

/*
 * Register access functions
 */

static inline void
gd32_dma_periph_increase_enable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_PNAGA;
}

static inline void
gd32_dma_periph_increase_disable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_PNAGA;
}

static inline void
gd32_dma_transfer_set_memory_to_memory(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) |= GD32_DMA_CHXCTL_M2M;
	GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_DIR;
}

static inline void
gd32_dma_transfer_set_memory_to_periph(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_M2M;
	GD32_DMA_CHCTL(reg, ch) |= GD32_DMA_CHXCTL_DIR;
}

static inline void
gd32_dma_transfer_set_periph_to_memory(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_M2M;
	GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_DIR;
}

static inline void
gd32_dma_memory_increase_enable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_MNAGA;
}

static inline void
gd32_dma_memory_increase_disable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_MNAGA;
}

static inline void
gd32_dma_circulation_enable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_CMEN;
}

static inline void
gd32_dma_circulation_disable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_CMEN;
}

static inline void gd32_dma_channel_enable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_CHEN;
}

static inline void gd32_dma_channel_disable(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_CHEN;
}

static inline void
gd32_dma_interrupt_enable(uint32_t reg, dma_channel_enum ch, uint32_t source)
{
	GD32_DMA_CHCTL(reg, ch) |= source;
}

static inline void
gd32_dma_interrupt_disable(uint32_t reg, dma_channel_enum ch, uint32_t source)
{
	GD32_DMA_CHCTL(reg, ch) &= ~source;
}

static inline void
gd32_dma_priority_config(uint32_t reg, dma_channel_enum ch, uint32_t priority)
{
	uint32_t ctl = GD32_DMA_CHCTL(reg, ch);

	GD32_DMA_CHCTL(reg, ch) = (ctl & (~DMA_CHXCTL_PRIO)) | priority;
}

static inline void
gd32_dma_memory_width_config(uint32_t reg, dma_channel_enum ch, uint32_t mwidth)
{
	uint32_t ctl = GD32_DMA_CHCTL(reg, ch);

	GD32_DMA_CHCTL(reg, ch) = (ctl & (~DMA_CHXCTL_MWIDTH)) | mwidth;
}

static inline void
gd32_dma_periph_width_config(uint32_t reg, dma_channel_enum ch, uint32_t pwidth)
{
	uint32_t ctl = GD32_DMA_CHCTL(reg, ch);

	GD32_DMA_CHCTL(reg, ch) = (ctl & (~DMA_CHXCTL_PWIDTH)) | pwidth;
}

#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
static inline void
gd32_dma_channel_subperipheral_select(uint32_t reg, dma_channel_enum ch,
				      dma_subperipheral_enum sub_periph)
{
	uint32_t ctl = GD32_DMA_CHCTL(reg, ch);

	GD32_DMA_CHCTL(reg, ch) =
		(ctl & (~DMA_CHXCTL_PERIEN)) |
		((uint32_t)sub_periph << CHXCTL_PERIEN_OFFSET);
}
#endif

static inline void
gd32_dma_periph_address_config(uint32_t reg, dma_channel_enum ch, uint32_t addr)
{
	GD32_DMA_CHPADDR(reg, ch) = addr;
}

static inline void
gd32_dma_memory_address_config(uint32_t reg, dma_channel_enum ch, uint32_t addr)
{
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	DMA_CHM0ADDR(reg, ch) = addr;
#else
	GD32_DMA_CHMADDR(reg, ch) = addr;
#endif
}

static inline void
gd32_dma_transfer_number_config(uint32_t reg, dma_channel_enum ch, uint32_t num)
{
	GD32_DMA_CHCNT(reg, ch) = (num & DMA_CHXCNT_CNT);
}

static inline uint32_t
gd32_dma_transfer_number_get(uint32_t reg, dma_channel_enum ch)
{
	return GD32_DMA_CHCNT(reg, ch);
}

static inline void
gd32_dma_interrupt_flag_clear(uint32_t reg, dma_channel_enum ch, uint32_t flag)
{
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	if (ch < DMA_CH4) {
		DMA_INTC0(reg) |= DMA_FLAG_ADD(flag, ch);
	} else {
		DMA_INTC1(reg) |= DMA_FLAG_ADD(flag, ch - DMA_CH4);
	}
#else
	GD32_DMA_INTC(reg) |= DMA_FLAG_ADD(flag, ch);
#endif
}

static inline void
gd32_dma_flag_clear(uint32_t reg, dma_channel_enum ch, uint32_t flag)
{
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	if (ch < DMA_CH4) {
		DMA_INTC0(reg) |= DMA_FLAG_ADD(flag, ch);
	} else {
		DMA_INTC1(reg) |= DMA_FLAG_ADD(flag, ch - DMA_CH4);
	}
#else
	GD32_DMA_INTC(reg) |= DMA_FLAG_ADD(flag, ch);
#endif
}

static inline uint32_t
gd32_dma_interrupt_flag_get(uint32_t reg, dma_channel_enum ch, uint32_t flag)
{
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	if (ch < DMA_CH4) {
		return (DMA_INTF0(reg) & DMA_FLAG_ADD(flag, ch));
	} else {
		return (DMA_INTF1(reg) & DMA_FLAG_ADD(flag, ch - DMA_CH4));
	}
#else
	return (GD32_DMA_INTF(reg) & DMA_FLAG_ADD(flag, ch));
#endif
}

static inline void gd32_dma_deinit(uint32_t reg, dma_channel_enum ch)
{
	GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_CHEN;

	GD32_DMA_CHCTL(reg, ch) = DMA_CHCTL_RESET_VALUE;
	GD32_DMA_CHCNT(reg, ch) = DMA_CHCNT_RESET_VALUE;
	GD32_DMA_CHPADDR(reg, ch) = DMA_CHPADDR_RESET_VALUE;
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	DMA_CHM0ADDR(reg, ch) = DMA_CHMADDR_RESET_VALUE;
	DMA_CHFCTL(reg, ch) = DMA_CHFCTL_RESET_VALUE;
	if (ch < DMA_CH4) {
		DMA_INTC0(reg) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, ch);
	} else {
		DMA_INTC1(reg) |=
			DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, ch - DMA_CH4);
	}
#else
	GD32_DMA_CHMADDR(reg, ch) = DMA_CHMADDR_RESET_VALUE;
	GD32_DMA_INTC(reg) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, ch);
#endif
}

/*
 * Utility functions
 */

static inline uint32_t dma_gd32_priority(uint32_t prio)
{
	return CHCTL_PRIO(prio);
}

static inline uint32_t dma_gd32_memory_width(uint32_t width)
{
	switch (width) {
	case 4:
		return CHCTL_MWIDTH(2);
	case 2:
		return CHCTL_MWIDTH(1);
	default:
		return CHCTL_MWIDTH(0);
	}
}

static inline uint32_t dma_gd32_periph_width(uint32_t width)
{
	switch (width) {
	case 4:
		return CHCTL_PWIDTH(2);
	case 2:
		return CHCTL_PWIDTH(1);
	default:
		return CHCTL_PWIDTH(0);
	}
}

/*
 * API functions
 */

static int dma_gd32_config(const struct device *dev, uint32_t channel,
			   struct dma_config *dma_cfg)
{
	const struct dma_gd32_config *cfg = dev->config;
	struct dma_gd32_data *data = dev->data;
	struct dma_gd32_srcdst_config src_cfg;
	struct dma_gd32_srcdst_config dst_cfg;
	struct dma_gd32_srcdst_config *memory_cfg = NULL;
	struct dma_gd32_srcdst_config *periph_cfg = NULL;

	if (channel >= cfg->channels) {
		LOG_ERR("channel must be < %" PRIu32 " (%" PRIu32 ")",
			cfg->channels, channel);
		return -EINVAL;
	}

	if (dma_cfg->block_count != 1) {
		LOG_ERR("chained block transfer not supported.");
		return -ENOTSUP;
	}

	if (dma_cfg->channel_priority > 3) {
		LOG_ERR("channel_priority must be < 4 (%" PRIu32 ")",
			dma_cfg->channel_priority);
		return -EINVAL;
	}

	if (dma_cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT) {
		LOG_ERR("source_addr_adj not supported DMA_ADDR_ADJ_DECREMENT");
		return -ENOTSUP;
	}

	if (dma_cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT) {
		LOG_ERR("dest_addr_adj not supported DMA_ADDR_ADJ_DECREMENT");
		return -ENOTSUP;
	}

	if (dma_cfg->head_block->source_addr_adj != DMA_ADDR_ADJ_INCREMENT &&
	    dma_cfg->head_block->source_addr_adj != DMA_ADDR_ADJ_NO_CHANGE) {
		LOG_ERR("invalid source_addr_adj %" PRIu16,
			dma_cfg->head_block->source_addr_adj);
		return -ENOTSUP;
	}
	if (dma_cfg->head_block->dest_addr_adj != DMA_ADDR_ADJ_INCREMENT &&
	    dma_cfg->head_block->dest_addr_adj != DMA_ADDR_ADJ_NO_CHANGE) {
		LOG_ERR("invalid dest_addr_adj %" PRIu16,
			dma_cfg->head_block->dest_addr_adj);
		return -ENOTSUP;
	}

	if (dma_cfg->source_data_size != 1 && dma_cfg->source_data_size != 2 &&
	    dma_cfg->source_data_size != 4) {
		LOG_ERR("source_data_size must be 1, 2, or 4 (%" PRIu32 ")",
			dma_cfg->source_data_size);
		return -EINVAL;
	}

	if (dma_cfg->dest_data_size != 1 && dma_cfg->dest_data_size != 2 &&
	    dma_cfg->dest_data_size != 4) {
		LOG_ERR("dest_data_size must be 1, 2, or 4 (%" PRIu32 ")",
			dma_cfg->dest_data_size);
		return -EINVAL;
	}

	if (dma_cfg->channel_direction > PERIPHERAL_TO_MEMORY) {
		LOG_ERR("channel_direction must be MEMORY_TO_MEMORY, "
			"MEMORY_TO_PERIPHERAL or PERIPHERAL_TO_MEMORY (%" PRIu32
			")",
			dma_cfg->channel_direction);
		return -ENOTSUP;
	}

	if (dma_cfg->channel_direction == MEMORY_TO_MEMORY && !cfg->mem2mem) {
		LOG_ERR("not supporting MEMORY_TO_MEMORY");
		return -ENOTSUP;
	}

#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	if (dma_cfg->dma_slot > 0xF) {
		LOG_ERR("dma_slot must be <7 (%" PRIu32 ")",
			dma_cfg->dma_slot);
		return -EINVAL;
	}
#endif

	gd32_dma_deinit(cfg->reg, channel);

	src_cfg.addr = dma_cfg->head_block->source_address;
	src_cfg.adj = dma_cfg->head_block->source_addr_adj;
	src_cfg.width = dma_cfg->source_data_size;

	dst_cfg.addr = dma_cfg->head_block->dest_address;
	dst_cfg.adj = dma_cfg->head_block->dest_addr_adj;
	dst_cfg.width = dma_cfg->dest_data_size;

	switch (dma_cfg->channel_direction) {
	case MEMORY_TO_MEMORY:
		gd32_dma_transfer_set_memory_to_memory(cfg->reg, channel);
		memory_cfg = &dst_cfg;
		periph_cfg = &src_cfg;
		break;
	case PERIPHERAL_TO_MEMORY:
		gd32_dma_transfer_set_periph_to_memory(cfg->reg, channel);
		memory_cfg = &dst_cfg;
		periph_cfg = &src_cfg;
		break;
	case MEMORY_TO_PERIPHERAL:
		gd32_dma_transfer_set_memory_to_periph(cfg->reg, channel);
		memory_cfg = &src_cfg;
		periph_cfg = &dst_cfg;
		break;
	}

	gd32_dma_memory_address_config(cfg->reg, channel, memory_cfg->addr);
	if (memory_cfg->adj == DMA_ADDR_ADJ_INCREMENT) {
		gd32_dma_memory_increase_enable(cfg->reg, channel);
	} else {
		gd32_dma_memory_increase_disable(cfg->reg, channel);
	}

	gd32_dma_periph_address_config(cfg->reg, channel, periph_cfg->addr);
	if (periph_cfg->adj == DMA_ADDR_ADJ_INCREMENT) {
		gd32_dma_periph_increase_enable(cfg->reg, channel);
	} else {
		gd32_dma_periph_increase_disable(cfg->reg, channel);
	}

	gd32_dma_transfer_number_config(cfg->reg, channel,
					dma_cfg->head_block->block_size);
	gd32_dma_priority_config(cfg->reg, channel,
				 dma_gd32_priority(dma_cfg->channel_priority));
	gd32_dma_memory_width_config(cfg->reg, channel,
				     dma_gd32_memory_width(memory_cfg->width));
	gd32_dma_periph_width_config(cfg->reg, channel,
				     dma_gd32_periph_width(periph_cfg->width));
	gd32_dma_circulation_disable(cfg->reg, channel);
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	if (dma_cfg->channel_direction != MEMORY_TO_MEMORY) {
		gd32_dma_channel_subperipheral_select(cfg->reg, channel,
						      dma_cfg->dma_slot);
	}
#endif

	data->channels[channel].callback = dma_cfg->dma_callback;
	data->channels[channel].user_data = dma_cfg->user_data;
	data->channels[channel].direction = dma_cfg->channel_direction;

	return 0;
}

static int dma_gd32_reload(const struct device *dev, uint32_t ch, uint32_t src,
			   uint32_t dst, size_t size)
{
	const struct dma_gd32_config *cfg = dev->config;
	struct dma_gd32_data *data = dev->data;

	if (ch >= cfg->channels) {
		LOG_ERR("reload channel must be < %" PRIu32 " (%" PRIu32 ")",
			cfg->channels, ch);
		return -EINVAL;
	}

	if (data->channels[ch].busy) {
		return -EBUSY;
	}

	gd32_dma_channel_disable(cfg->reg, ch);

	gd32_dma_transfer_number_config(cfg->reg, ch, size);

	switch (data->channels[ch].direction) {
	case MEMORY_TO_MEMORY:
	case PERIPHERAL_TO_MEMORY:
		gd32_dma_memory_address_config(cfg->reg, ch, dst);
		gd32_dma_periph_address_config(cfg->reg, ch, src);
		break;
	case MEMORY_TO_PERIPHERAL:
		gd32_dma_memory_address_config(cfg->reg, ch, src);
		gd32_dma_periph_address_config(cfg->reg, ch, dst);
		break;
	}

	gd32_dma_channel_enable(cfg->reg, ch);

	return 0;
}

static int dma_gd32_start(const struct device *dev, uint32_t ch)
{
	const struct dma_gd32_config *cfg = dev->config;
	struct dma_gd32_data *data = dev->data;

	if (ch >= cfg->channels) {
		LOG_ERR("start channel must be < %" PRIu32 " (%" PRIu32 ")",
			cfg->channels, ch);
		return -EINVAL;
	}

	gd32_dma_interrupt_enable(cfg->reg, ch,
				  DMA_CHXCTL_FTFIE | GD32_DMA_INTERRUPT_ERRORS);
	gd32_dma_channel_enable(cfg->reg, ch);
	data->channels[ch].busy = true;

	return 0;
}

static int dma_gd32_stop(const struct device *dev, uint32_t ch)
{
	const struct dma_gd32_config *cfg = dev->config;
	struct dma_gd32_data *data = dev->data;

	if (ch >= cfg->channels) {
		LOG_ERR("stop channel must be < %" PRIu32 " (%" PRIu32 ")",
			cfg->channels, ch);
		return -EINVAL;
	}

	gd32_dma_interrupt_disable(
		cfg->reg, ch, DMA_CHXCTL_FTFIE | GD32_DMA_INTERRUPT_ERRORS);
	gd32_dma_interrupt_flag_clear(cfg->reg, ch,
				      DMA_FLAG_FTF | GD32_DMA_FLAG_ERRORS);
	gd32_dma_channel_disable(cfg->reg, ch);
	data->channels[ch].busy = false;

	return 0;
}

static int dma_gd32_get_status(const struct device *dev, uint32_t ch,
			       struct dma_status *stat)
{
	const struct dma_gd32_config *cfg = dev->config;
	struct dma_gd32_data *data = dev->data;

	if (ch >= cfg->channels) {
		LOG_ERR("channel must be < %" PRIu32 " (%" PRIu32 ")",
			cfg->channels, ch);
		return -EINVAL;
	}

	stat->pending_length = gd32_dma_transfer_number_get(cfg->reg, ch);
	stat->dir = data->channels[ch].direction;
	stat->busy = data->channels[ch].busy;

	return 0;
}

static bool dma_gd32_api_chan_filter(const struct device *dev, int ch,
				     void *filter_param)
{
	uint32_t filter;

	if (!filter_param) {
		LOG_ERR("filter_param must not be NULL");
		return false;
	}

	filter = *((uint32_t *)filter_param);

	return (filter & BIT(ch));
}

static int dma_gd32_init(const struct device *dev)
{
	const struct dma_gd32_config *cfg = dev->config;

	(void)clock_control_on(GD32_CLOCK_CONTROLLER,
			       (clock_control_subsys_t)&cfg->clkid);

#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1)
	(void)reset_line_toggle_dt(&cfg->reset);
#endif

	for (uint32_t i = 0; i < cfg->channels; i++) {
		gd32_dma_interrupt_disable(cfg->reg, i,
			   DMA_CHXCTL_FTFIE | GD32_DMA_INTERRUPT_ERRORS);
		gd32_dma_deinit(cfg->reg, i);
	}

	cfg->irq_configure();

	return 0;
}

static void dma_gd32_isr(const struct device *dev)
{
	const struct dma_gd32_config *cfg = dev->config;
	struct dma_gd32_data *data = dev->data;
	uint32_t errflag, ftfflag;
	int err = 0;

	for (uint32_t i = 0; i < cfg->channels; i++) {
		errflag = gd32_dma_interrupt_flag_get(cfg->reg, i,
						      GD32_DMA_FLAG_ERRORS);
		ftfflag =
			gd32_dma_interrupt_flag_get(cfg->reg, i, DMA_FLAG_FTF);

		if (errflag == 0 && ftfflag == 0) {
			continue;
		}

		if (errflag) {
			err = -EIO;
		}

		gd32_dma_interrupt_flag_clear(
			cfg->reg, i, DMA_FLAG_FTF | GD32_DMA_FLAG_ERRORS);
		data->channels[i].busy = false;

		if (data->channels[i].callback) {
			data->channels[i].callback(
				dev, data->channels[i].user_data, i, err);
		}
	}
}

static DEVICE_API(dma, dma_gd32_driver_api) = {
	.config = dma_gd32_config,
	.reload = dma_gd32_reload,
	.start = dma_gd32_start,
	.stop = dma_gd32_stop,
	.get_status = dma_gd32_get_status,
	.chan_filter = dma_gd32_api_chan_filter,
};

#define IRQ_CONFIGURE(n, inst)                                                 \
	IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, n, irq),                          \
		    DT_INST_IRQ_BY_IDX(inst, n, priority), dma_gd32_isr,       \
		    DEVICE_DT_INST_GET(inst), 0);                              \
	irq_enable(DT_INST_IRQ_BY_IDX(inst, n, irq));

#define CONFIGURE_ALL_IRQS(inst, n) LISTIFY(n, IRQ_CONFIGURE, (), inst)

#define GD32_DMA_INIT(inst)                                                    \
	static void dma_gd32##inst##_irq_configure(void)                       \
	{                                                                      \
		CONFIGURE_ALL_IRQS(inst, DT_NUM_IRQS(DT_DRV_INST(inst)));      \
	}                                                                      \
	static const struct dma_gd32_config dma_gd32##inst##_config = {        \
		.reg = DT_INST_REG_ADDR(inst),                                 \
		.channels = DT_INST_PROP(inst, dma_channels),                  \
		.clkid = DT_INST_CLOCKS_CELL(inst, id),                        \
		.mem2mem = DT_INST_PROP(inst, gd_mem2mem),                     \
		IF_ENABLED(DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1),          \
			   (.reset = RESET_DT_SPEC_INST_GET(inst),))           \
		.irq_configure = dma_gd32##inst##_irq_configure,               \
	};                                                                     \
                                                                               \
	static struct dma_gd32_channel                                         \
		dma_gd32##inst##_channels[DT_INST_PROP(inst, dma_channels)];   \
	ATOMIC_DEFINE(dma_gd32_atomic##inst,                                   \
		      DT_INST_PROP(inst, dma_channels));                       \
	static struct dma_gd32_data dma_gd32##inst##_data = {                  \
		.ctx =  {                                                      \
			.magic = DMA_MAGIC,                                    \
			.atomic = dma_gd32_atomic##inst,                       \
			.dma_channels = DT_INST_PROP(inst, dma_channels),      \
		},                                                             \
		.channels = dma_gd32##inst##_channels,                         \
	};                                                                     \
                                                                               \
	DEVICE_DT_INST_DEFINE(inst, dma_gd32_init, NULL,                       \
			      &dma_gd32##inst##_data,                          \
			      &dma_gd32##inst##_config, POST_KERNEL,           \
			      CONFIG_DMA_INIT_PRIORITY, &dma_gd32_driver_api);

DT_INST_FOREACH_STATUS_OKAY(GD32_DMA_INIT)
