/*
 * Copyright (c) 2023 Renesas Electronics Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/irq.h>
#include <DA1469xAB.h>
#include <da1469x_config.h>
#include <system_DA1469x.h>
#include <da1469x_otp.h>
#include <zephyr/drivers/dma/dma_smartbond.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(dma_smartbond, CONFIG_DMA_LOG_LEVEL);

#define DT_DRV_COMPAT renesas_smartbond_dma

#define SMARTBOND_IRQN      DT_INST_IRQN(0)
#define SMARTBOND_IRQ_PRIO  DT_INST_IRQ(0, priority)

#define DMA_CHANNELS_COUNT   DT_PROP(DT_NODELABEL(dma), dma_channels)
#define DMA_BLOCK_COUNT     DT_PROP(DT_NODELABEL(dma), block_count)
#define DMA_SECURE_CHANNEL  7

#define DMA_CTRL_REG_SET_FIELD(_field, _var, _val) \
	(_var) = \
	(((_var) & ~DMA_DMA0_CTRL_REG_ ## _field ## _Msk) | \
	(((_val) << DMA_DMA0_CTRL_REG_ ## _field ## _Pos) & DMA_DMA0_CTRL_REG_ ## _field ## _Msk))

#define DMA_CTRL_REG_GET_FIELD(_field, _var) \
	(((_var) & DMA_DMA0_CTRL_REG_ ## _field ## _Msk) >> DMA_DMA0_CTRL_REG_ ## _field ## _Pos)

#define DMA_CHN2REG(_idx)   (&((struct channel_regs *)DMA)[(_idx)])

#define DMA_MUX_SHIFT(_idx)   (((_idx) >> 1) * 4)

#define DMA_REQ_MUX_REG_SET(_idx, _val) \
	DMA->DMA_REQ_MUX_REG = \
		(DMA->DMA_REQ_MUX_REG & ~(0xf << DMA_MUX_SHIFT((_idx)))) | \
		(((_val) & 0xf) << DMA_MUX_SHIFT((_idx)))

#define DMA_REQ_MUX_REG_GET(_idx) \
	((DMA->DMA_REQ_MUX_REG >> DMA_MUX_SHIFT((_idx))) & 0xf)

#define CRYPTO_KEYS_BUF_ADDR   0x30040100
#define CRYPTO_KEYS_BUF_SIZE   0x100
#define IS_AES_KEYS_BUF_RANGE(_a)  ((uint32_t)(_a) >= (uint32_t)(CRYPTO_KEYS_BUF_ADDR)) && \
	((uint32_t)(_a) < (uint32_t)(CRYPTO_KEYS_BUF_ADDR + CRYPTO_KEYS_BUF_SIZE))

/*
 * DMA channel priority level. The smaller the value the lower the priority granted to a channel
 * when two or more channels request the bus at the same time. For channels of same priority an
 * inherent mechanism is applied in which the lower the channel number the higher the priority.
 */
enum dma_smartbond_channel_prio {
	DMA_SMARTBOND_CHANNEL_PRIO_0 = 0x0,  /* Lowest channel priority */
	DMA_SMARTBOND_CHANNEL_PRIO_1,
	DMA_SMARTBOND_CHANNEL_PRIO_2,
	DMA_SMARTBOND_CHANNEL_PRIO_3,
	DMA_SMARTBOND_CHANNEL_PRIO_4,
	DMA_SMARTBOND_CHANNEL_PRIO_5,
	DMA_SMARTBOND_CHANNEL_PRIO_6,
	DMA_SMARTBOND_CHANNEL_PRIO_7,         /* Highest channel priority */
	DMA_SMARTBOND_CHANNEL_PRIO_MAX
};

enum dma_smartbond_channel {
	DMA_SMARTBOND_CHANNEL_0 = 0x0,
	DMA_SMARTBOND_CHANNEL_1,
	DMA_SMARTBOND_CHANNEL_2,
	DMA_SMARTBOND_CHANNEL_3,
	DMA_SMARTBOND_CHANNEL_4,
	DMA_SMARTBOND_CHANNEL_5,
	DMA_SMARTBOND_CHANNEL_6,
	DMA_SMARTBOND_CHANNEL_7,
	DMA_SMARTBOND_CHANNEL_MAX
};

enum dma_smartbond_burst_len {
	DMA_SMARTBOND_BURST_LEN_1B  = 0x1, /* Burst mode is disabled */
	DMA_SMARTBOND_BURST_LEN_4B  = 0x4, /* Perform bursts of 4 beats (INCR4) */
	DMA_SMARTBOND_BURST_LEN_8B  = 0x8  /* Perform bursts of 8 beats (INCR8) */
};

/*
 * DMA bus width indicating how many bytes are retrived/written per transfer.
 * Note that the bus width is the same for the source and destination.
 */
enum dma_smartbond_bus_width {
	DMA_SMARTBOND_BUS_WIDTH_1B = 0x1,
	DMA_SMARTBOND_BUS_WIDTH_2B = 0x2,
	DMA_SMARTBOND_BUS_WIDTH_4B = 0x4
};

enum dreq_mode {
	DREQ_MODE_SW = 0x0,
	DREQ_MODE_HW
};

enum burst_mode {
	BURST_MODE_0B = 0x0,
	BURST_MODE_4B = 0x1,
	BURST_MODE_8B = 0x2
};

enum bus_width {
	BUS_WIDTH_1B = 0x0,
	BUS_WIDTH_2B = 0x1,
	BUS_WIDTH_4B = 0x2
};

enum addr_adj {
	ADDR_ADJ_NO_CHANGE = 0x0,
	ADDR_ADJ_INCR
};

enum copy_mode {
	COPY_MODE_BLOCK = 0x0,
	COPY_MODE_INIT
};

enum req_sense {
	REQ_SENSE_LEVEL = 0x0,
	REQ_SENSE_EDGE
};

struct channel_regs {
	__IO uint32_t DMA_A_START;
	__IO uint32_t DMA_B_START;
	__IO uint32_t DMA_INT_REG;
	__IO uint32_t DMA_LEN_REG;
	__IO uint32_t DMA_CTRL_REG;

	__I uint32_t DMA_IDX_REG;
	__I uint32_t RESERVED[2];
};

struct dma_channel_data {
	dma_callback_t cb;
	void *user_data;
	enum dma_smartbond_bus_width bus_width;
	enum dma_smartbond_burst_len burst_len;
	enum dma_channel_direction dir;
	bool is_dma_configured;
};

struct dma_smartbond_data {
	/* Should be the first member of the driver data */
	struct dma_context dma_ctx;

	ATOMIC_DEFINE(channels_atomic, DMA_CHANNELS_COUNT);

	/* User callbacks and data to be stored per channel */
	struct dma_channel_data channel_data[DMA_CHANNELS_COUNT];
};

/* True if there is any DMA activity on any channel, false otheriwise. */
static bool dma_smartbond_is_dma_active(void)
{
	int idx;
	struct channel_regs *regs;

	for (idx = 0; idx < DMA_CHANNELS_COUNT; idx++) {
		regs = DMA_CHN2REG(idx);

		if (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) {
			return true;
		}
	}

	return false;
}

static void dma_smartbond_set_channel_status(uint32_t channel, bool status)
{
	unsigned int key;
	struct channel_regs *regs = DMA_CHN2REG(channel);

	key = irq_lock();

	if (status) {
		/* Make sure the status register for the requested channel is cleared. */
		DMA->DMA_CLEAR_INT_REG |= BIT(channel);
		/* Enable interrupts for the requested channel. */
		DMA->DMA_INT_MASK_REG |= BIT(channel);

		/* Check if this is the first attempt to enable DMA interrupts. */
		if (!irq_is_enabled(SMARTBOND_IRQN)) {
			irq_enable(SMARTBOND_IRQN);
		}

		DMA_CTRL_REG_SET_FIELD(DMA_ON, regs->DMA_CTRL_REG, 0x1);
	} else {
		DMA_CTRL_REG_SET_FIELD(DMA_ON, regs->DMA_CTRL_REG, 0x0);

		/*
		 * It might happen that DMA is already in progress. Make sure the current
		 * on-going transfer is complete (cannot be interrupted).
		 */
		while (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) {
		}

		/* Disable interrupts for the requested channel */
		DMA->DMA_INT_MASK_REG &= ~(BIT(channel));
		/* Clear the status register; the requested channel should be considered obsolete */
		DMA->DMA_CLEAR_INT_REG |= BIT(channel);

		/* DMA interrupts should be disabled only if all channels are disabled. */
		if (!dma_smartbond_is_dma_active()) {
			irq_disable(SMARTBOND_IRQN);
		}
	}

	irq_unlock(key);
}

static bool dma_channel_dst_addr_check_and_adjust(uint32_t channel, uint32_t *dst)
{
	uint32_t phy_address;
	uint32_t secure_boot_reg;
	bool is_aes_keys_protected, is_qspic_keys_protected;

	phy_address = black_orca_phy_addr(*dst);

	secure_boot_reg = CRG_TOP->SECURE_BOOT_REG;
	is_aes_keys_protected =
		(secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_AES_KEY_READ_Msk);
	is_qspic_keys_protected =
		(secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_QSPI_KEY_READ_Msk);

	/*
	 * If the destination address reflects the AES key buffer area and secure keys are protected
	 * then only the secure channel #7 can be used to transfer data to AES key buffer.
	 */
	if ((IS_AES_KEYS_BUF_RANGE(phy_address) &&
		(is_aes_keys_protected || is_qspic_keys_protected) &&
		(channel != DMA_SECURE_CHANNEL))) {
		LOG_ERR("Keys are protected. Only secure channel #7 can be employed.");
		return false;
	}

	if (IS_QSPIF_ADDRESS(phy_address) || IS_QSPIF_CACHED_ADDRESS(phy_address) ||
				IS_OTP_ADDRESS(phy_address) || IS_OTP_P_ADDRESS(phy_address)) {
		LOG_ERR("Invalid destination location.");
		return false;
	}

	*dst = phy_address;

	return true;
}

static bool dma_channel_src_addr_check_and_adjust(uint32_t channel, uint32_t *src)
{
	uint32_t phy_address;
	uint32_t secure_boot_reg;
	bool is_aes_keys_protected, is_qspic_keys_protected;

	/* DMA can only access physical addresses, not remapped. */
	phy_address = black_orca_phy_addr(*src);

	if (IS_QSPIF_CACHED_ADDRESS(phy_address)) {
		/*
		 * To achiebe max. perfomance, peripherals should not access the Flash memory
		 * through the instruction cache controller (avoid cache misses).
		 */
		phy_address += (MCU_QSPIF_M_BASE - MCU_QSPIF_M_CACHED_BASE);
	} else if (IS_OTP_ADDRESS(phy_address)) {
		/* Peripherals should access OTP through its peripheral address space. */
		phy_address += (MCU_OTP_M_P_BASE - MCU_OTP_M_BASE);
	}

	secure_boot_reg = CRG_TOP->SECURE_BOOT_REG;
	is_aes_keys_protected =
		(secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_AES_KEY_READ_Msk);
	is_qspic_keys_protected =
		(secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_QSPI_KEY_READ_Msk);

	/*
	 * If the source address reflects protected area in OTP then only the
	 * secure channel #7 can be used to fetch secure keys data.
	 */
	if (((IS_ADDRESS_USER_DATA_KEYS_SEGMENT(phy_address) && is_aes_keys_protected) ||
	     (IS_ADDRESS_QSPI_FW_KEYS_SEGMENT(phy_address) && is_qspic_keys_protected)) &&
							(channel != DMA_SECURE_CHANNEL)) {
		LOG_ERR("Keys are protected. Only secure channel #7 can be employed.");
		return false;
	}

	*src = phy_address;

	return true;
}

static bool dma_channel_update_dreq_mode(enum dma_channel_direction direction,
									uint32_t *dma_ctrl_reg)
{
	switch (direction) {
	case MEMORY_TO_HOST:
	case HOST_TO_MEMORY:
	case MEMORY_TO_MEMORY:
		/* DMA channel starts immediately */
		DMA_CTRL_REG_SET_FIELD(DREQ_MODE, *dma_ctrl_reg, DREQ_MODE_SW);
		break;
	case PERIPHERAL_TO_MEMORY:
	case PERIPHERAL_TO_PERIPHERAL:
		/* DMA channels starts by peripheral DMA req */
		DMA_CTRL_REG_SET_FIELD(DREQ_MODE, *dma_ctrl_reg, DREQ_MODE_HW);
		break;
	default:
		return false;
	};

	return true;
}

static bool dma_channel_update_src_addr_adj(enum dma_addr_adj addr_adj, uint32_t *dma_ctrl_reg)
{
	switch (addr_adj) {
	case DMA_ADDR_ADJ_NO_CHANGE:
		DMA_CTRL_REG_SET_FIELD(AINC, *dma_ctrl_reg, ADDR_ADJ_NO_CHANGE);
		break;
	case DMA_ADDR_ADJ_INCREMENT:
		DMA_CTRL_REG_SET_FIELD(AINC, *dma_ctrl_reg, ADDR_ADJ_INCR);
		break;
	default:
		return false;
	}

	return true;
}

static bool dma_channel_update_dst_addr_adj(enum dma_addr_adj addr_adj, uint32_t *dma_ctrl_reg)
{
	switch (addr_adj) {
	case DMA_ADDR_ADJ_NO_CHANGE:
		DMA_CTRL_REG_SET_FIELD(BINC, *dma_ctrl_reg, ADDR_ADJ_NO_CHANGE);
		break;
	case DMA_ADDR_ADJ_INCREMENT:
		DMA_CTRL_REG_SET_FIELD(BINC, *dma_ctrl_reg, ADDR_ADJ_INCR);
		break;
	default:
		return false;
	}

	return true;
}

static bool dma_channel_update_bus_width(uint16_t bw, uint32_t *dma_ctrl_reg)
{
	switch (bw) {
	case DMA_SMARTBOND_BUS_WIDTH_1B:
		DMA_CTRL_REG_SET_FIELD(BW, *dma_ctrl_reg, BUS_WIDTH_1B);
		break;
	case DMA_SMARTBOND_BUS_WIDTH_2B:
		DMA_CTRL_REG_SET_FIELD(BW, *dma_ctrl_reg, BUS_WIDTH_2B);
		break;
	case DMA_SMARTBOND_BUS_WIDTH_4B:
		DMA_CTRL_REG_SET_FIELD(BW, *dma_ctrl_reg, BUS_WIDTH_4B);
		break;
	default:
		return false;
	}

	return true;
}

static bool dma_channel_update_burst_mode(uint16_t burst, uint32_t *dma_ctrl_reg)
{
	switch (burst) {
	case DMA_SMARTBOND_BURST_LEN_1B:
		DMA_CTRL_REG_SET_FIELD(BURST_MODE, *dma_ctrl_reg, BURST_MODE_0B);
		break;
	case DMA_SMARTBOND_BURST_LEN_4B:
		DMA_CTRL_REG_SET_FIELD(BURST_MODE, *dma_ctrl_reg, BURST_MODE_4B);
		break;
	case DMA_SMARTBOND_BURST_LEN_8B:
		DMA_CTRL_REG_SET_FIELD(BURST_MODE, *dma_ctrl_reg, BURST_MODE_8B);
		break;
	default:
		return false;
	}

	return true;
}

static void dma_channel_update_req_sense(enum dma_smartbond_trig_mux trig_mux,
						uint32_t channel, uint32_t *dma_ctrl_reg)
{
	switch (trig_mux) {
	case DMA_SMARTBOND_TRIG_MUX_UART:
	case DMA_SMARTBOND_TRIG_MUX_UART2:
	case DMA_SMARTBOND_TRIG_MUX_UART3:
	case DMA_SMARTBOND_TRIG_MUX_I2C:
	case DMA_SMARTBOND_TRIG_MUX_I2C2:
	case DMA_SMARTBOND_TRIG_MUX_USB:
		/* Odd channel numbers should reflect TX path */
		if (channel & BIT(0)) {
			DMA_CTRL_REG_SET_FIELD(REQ_SENSE, *dma_ctrl_reg, REQ_SENSE_EDGE);
			break;
		}
	default:
		DMA_CTRL_REG_SET_FIELD(REQ_SENSE, *dma_ctrl_reg, REQ_SENSE_LEVEL);
	}
}

static void dma_set_mux_request(enum dma_smartbond_trig_mux trig_mux, uint32_t channel)
{
	unsigned int key;

	key = irq_lock();
	DMA_REQ_MUX_REG_SET(channel, trig_mux);

	/*
	 * Having same trigger for different channels can cause unpredictable results.
	 * The audio triggers (src and pcm) are an exception, as they use 2 pairs each
	 * for DMA access.
	 * The lesser significant selector has higher priority and will control
	 * the DMA acknowledge signal driven to the selected peripheral. Make sure
	 * the current selector does not match with selectors of
	 * higher priorities (dma channels of lower indexing). It's OK if a
	 * channel of higher indexing defines the same peripheral request source
	 * (should be ignored as it has lower priority).
	 */
	if (trig_mux != DMA_SMARTBOND_TRIG_MUX_NONE) {
		switch (channel) {
		case DMA_SMARTBOND_CHANNEL_7:
		case DMA_SMARTBOND_CHANNEL_6:
			if (DMA_REQ_MUX_REG_GET(DMA_SMARTBOND_CHANNEL_5) == trig_mux) {
				DMA_REQ_MUX_REG_SET(DMA_SMARTBOND_CHANNEL_5,
								DMA_SMARTBOND_TRIG_MUX_NONE);
			}
			/* fall-through */
		case DMA_SMARTBOND_CHANNEL_5:
		case DMA_SMARTBOND_CHANNEL_4:
			if (DMA_REQ_MUX_REG_GET(DMA_SMARTBOND_CHANNEL_3) == trig_mux) {
				DMA_REQ_MUX_REG_SET(DMA_SMARTBOND_CHANNEL_3,
								DMA_SMARTBOND_TRIG_MUX_NONE);
			}
			/* fall-through */
		case DMA_SMARTBOND_CHANNEL_3:
		case DMA_SMARTBOND_CHANNEL_2:
			if (DMA_REQ_MUX_REG_GET(DMA_SMARTBOND_CHANNEL_1) == trig_mux) {
				DMA_REQ_MUX_REG_SET(DMA_SMARTBOND_CHANNEL_1,
								DMA_SMARTBOND_TRIG_MUX_NONE);
			}
		case DMA_SMARTBOND_CHANNEL_1:
		case DMA_SMARTBOND_CHANNEL_0:
			break;
		}
	}

	irq_unlock(key);
}

static int dma_smartbond_config(const struct device *dev, uint32_t channel, struct dma_config *cfg)
{
	struct dma_smartbond_data *data = dev->data;
	struct channel_regs *regs;
	uint32_t dma_ctrl_reg;
	uint32_t src_dst_address;

	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}
	regs = DMA_CHN2REG(channel);

	dma_ctrl_reg = regs->DMA_CTRL_REG;

	if (DMA_CTRL_REG_GET_FIELD(DMA_ON, dma_ctrl_reg)) {
		LOG_ERR("Requested channel is enabled. It should first be disabled");
		return -EIO;
	}

	if (cfg == NULL || cfg->head_block == NULL) {
		LOG_ERR("Missing configuration structure");
		return -EINVAL;
	}

	/* Error handling is not supported; just warn user. */
	if (cfg->error_callback_en) {
		LOG_WRN("Error handling is not supported");
	}

	if (!cfg->complete_callback_en) {
		data->channel_data[channel].cb = cfg->dma_callback;
		data->channel_data[channel].user_data = cfg->user_data;
	} else {
		LOG_WRN("User callback can only be called at completion only and not per block.");

		/* Nulify pointers to indicate notifications are disabled. */
		data->channel_data[channel].cb = NULL;
		data->channel_data[channel].user_data = NULL;
	}

	data->channel_data[channel].dir = cfg->channel_direction;

	if (cfg->block_count > DMA_BLOCK_COUNT) {
		LOG_WRN("A single block is supported. The rest blocks will be discarded");
	}

	if (cfg->channel_priority >= DMA_SMARTBOND_CHANNEL_PRIO_MAX) {
		cfg->channel_priority = DMA_SMARTBOND_CHANNEL_PRIO_7;
		LOG_WRN("Channel priority exceeded max. Setting to highest valid level");
	}

	DMA_CTRL_REG_SET_FIELD(DMA_PRIO, dma_ctrl_reg, cfg->channel_priority);

	if (((cfg->source_burst_length != cfg->dest_burst_length) ||
		!dma_channel_update_burst_mode(cfg->source_burst_length, &dma_ctrl_reg))) {
		LOG_ERR("Invalid burst mode or source and destination mode mismatch");
		return -EINVAL;
	}

	data->channel_data[channel].burst_len = cfg->source_burst_length;

	if (cfg->source_data_size != cfg->dest_data_size ||
		!dma_channel_update_bus_width(cfg->source_data_size, &dma_ctrl_reg)) {
		LOG_ERR("Invalid bus width or source and destination bus width mismatch");
		return -EINVAL;
	}

	data->channel_data[channel].bus_width = cfg->source_data_size;

	if (cfg->source_chaining_en || cfg->dest_chaining_en ||
		cfg->head_block->source_gather_en || cfg->head_block->dest_scatter_en ||
		cfg->head_block->source_reload_en || cfg->head_block->dest_reload_en) {
		LOG_WRN("Chainning, scattering, gathering or reloading is not supported");
	}

	if (!dma_channel_update_src_addr_adj(cfg->head_block->source_addr_adj,
								&dma_ctrl_reg)) {
		LOG_ERR("Invalid source address adjustment");
		return -EINVAL;
	}

	if (!dma_channel_update_dst_addr_adj(cfg->head_block->dest_addr_adj, &dma_ctrl_reg)) {
		LOG_ERR("Invalid destination address adjustment");
		return -EINVAL;
	}

	if (!dma_channel_update_dreq_mode(cfg->channel_direction, &dma_ctrl_reg)) {
		LOG_ERR("Inavlid channel direction");
		return -EINVAL;
	}

	/* Cyclic is valid only when DREQ_MODE is set */
	if (cfg->cyclic && DMA_CTRL_REG_GET_FIELD(DREQ_MODE, dma_ctrl_reg) != DREQ_MODE_HW) {
		LOG_ERR("Circular mode is only supported for non memory-memory transfers");
		return -EINVAL;
	}

	DMA_CTRL_REG_SET_FIELD(CIRCULAR, dma_ctrl_reg, cfg->cyclic);

	if (DMA_CTRL_REG_GET_FIELD(DREQ_MODE, dma_ctrl_reg) == DREQ_MODE_SW &&
		DMA_CTRL_REG_GET_FIELD(AINC, dma_ctrl_reg) == ADDR_ADJ_NO_CHANGE &&
		DMA_CTRL_REG_GET_FIELD(BINC, dma_ctrl_reg) == ADDR_ADJ_INCR) {
		/*
		 * Valid for memory initialization to a specific value. This process
		 * cannot be interrupted by other DMA channels.
		 */
		DMA_CTRL_REG_SET_FIELD(DMA_INIT, dma_ctrl_reg, COPY_MODE_INIT);
	} else {
		DMA_CTRL_REG_SET_FIELD(DMA_INIT, dma_ctrl_reg, COPY_MODE_BLOCK);
	}

	dma_channel_update_req_sense(cfg->dma_slot, channel, &dma_ctrl_reg);

	regs->DMA_CTRL_REG = dma_ctrl_reg;

	/* Requested address might be changed */
	src_dst_address = cfg->head_block->source_address;
	if (!dma_channel_src_addr_check_and_adjust(channel, &src_dst_address)) {
		return -EINVAL;
	}

	if (src_dst_address % cfg->source_data_size) {
		LOG_ERR("Source address is not bus width aligned");
		return -EINVAL;
	}

	regs->DMA_A_START = src_dst_address;

	src_dst_address = cfg->head_block->dest_address;
	if (!dma_channel_dst_addr_check_and_adjust(channel, &src_dst_address)) {
		return -EINVAL;
	}

	if (src_dst_address % cfg->dest_data_size) {
		LOG_ERR("Destination address is not bus width aligned");
		return -EINVAL;
	}

	regs->DMA_B_START = src_dst_address;

	if (cfg->head_block->block_size % (cfg->source_data_size * cfg->source_burst_length)) {
		LOG_ERR("Requested data size is not multiple of bus width");
		return -EINVAL;
	}

	regs->DMA_LEN_REG = (cfg->head_block->block_size / cfg->source_data_size) - 1;

	/* Interrupt will be raised once all transfers are complete. */
	regs->DMA_INT_REG = (cfg->head_block->block_size / cfg->source_data_size) - 1;

	if ((cfg->source_handshake != cfg->dest_handshake) ||
		(cfg->source_handshake != 0)/*HW*/) {
		LOG_ERR("Source/destination handshakes mismatch or invalid");
		return -EINVAL;
	}

	dma_set_mux_request(cfg->dma_slot, channel);

	/* Designate that channel has been configured */
	data->channel_data[channel].is_dma_configured = true;

	return 0;
}


static int dma_smartbond_reload(const struct device *dev, uint32_t channel, uint32_t src,
									uint32_t dst, size_t size)
{
	struct dma_smartbond_data *data = dev->data;
	struct channel_regs *regs;

	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}
	regs = DMA_CHN2REG(channel);

	if (!data->channel_data[channel].is_dma_configured) {
		LOG_ERR("Requested DMA channel should first be configured");
		return -EINVAL;
	}

	if (size == 0) {
		LOG_ERR("Min. transfer size is one");
		return -EINVAL;
	}

	if (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) {
		LOG_ERR("Channel is busy, settings cannot be changed mid-transfer");
		return -EBUSY;
	}

	if (src % data->channel_data[channel].bus_width) {
		LOG_ERR("Source address is not bus width aligned");
		return -EINVAL;
	}

	if (!dma_channel_src_addr_check_and_adjust(channel, &src)) {
		return -EINVAL;
	}

	regs->DMA_A_START = src;

	if (dst % data->channel_data[channel].bus_width) {
		LOG_ERR("Destination address is not bus width aligned");
		return -EINVAL;
	}

	if (!dma_channel_dst_addr_check_and_adjust(channel, &dst)) {
		return -EINVAL;
	}

	regs->DMA_B_START = dst;

	if (size % (data->channel_data[channel].burst_len *
							data->channel_data[channel].bus_width)) {
		LOG_ERR("Requested data size is not multiple of bus width");
		return -EINVAL;
	}

	regs->DMA_LEN_REG = (size / data->channel_data[channel].bus_width) - 1;

	/* Interrupt will be raised once all transfers are complete. */
	regs->DMA_INT_REG = (size / data->channel_data[channel].bus_width) - 1;

	return 0;
}

static int dma_smartbond_start(const struct device *dev, uint32_t channel)
{
	struct channel_regs *regs;
	struct dma_smartbond_data *data = dev->data;

	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}
	regs = DMA_CHN2REG(channel);

	if (!data->channel_data[channel].is_dma_configured) {
		LOG_ERR("Requested DMA channel should first be configured");
		return -EINVAL;
	}

	/* Should return succss if the requested channel is already started. */
	if (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) {
		return 0;
	}

	dma_smartbond_set_channel_status(channel, true);

	return 0;
}

static int dma_smartbond_stop(const struct device *dev, uint32_t channel)
{
	struct channel_regs *regs;

	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}
	regs = DMA_CHN2REG(channel);

	/*
	 * In normal mode DMA_ON is cleared automatically. However we need to clear
	 * the corresponding register mask and disable NVIC if there is no other
	 * channel in use.
	 */
	dma_smartbond_set_channel_status(channel, false);

	return 0;
}

static int dma_smartbond_suspend(const struct device *dev, uint32_t channel)
{
	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}

	/*
	 * Freezing the DMA engine is valid for memory-to-memory operations.
	 * Valid memory locations are SYSRAM and/or PSRAM.
	 */
	LOG_WRN("DMA is freezed globally");

	/*
	 * Freezing the DMA engine can be done universally and not per channel!.
	 * An attempt to disable the channel would result in resetting the IDX
	 * register next time the channel was re-enabled.
	 */
	GPREG->SET_FREEZE_REG = GPREG_SET_FREEZE_REG_FRZ_DMA_Msk;

	return 0;
}

static int dma_smartbond_resume(const struct device *dev, uint32_t channel)
{
	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}

	LOG_WRN("DMA is unfreezed globally");

	/* Unfreezing the DMA engine can be done unviversally and not per channel! */
	GPREG->RESET_FREEZE_REG = GPREG_RESET_FREEZE_REG_FRZ_DMA_Msk;

	return 0;
}

static int dma_smartbond_get_status(const struct device *dev, uint32_t channel,
							struct dma_status *stat)
{
	struct channel_regs *regs;
	int key;
	struct dma_smartbond_data *data = dev->data;
	uint8_t bus_width;
	uint32_t dma_ctrl_reg, dma_idx_reg, dma_len_reg;

	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}

	if (stat == NULL) {
		LOG_ERR("User should provide a valid pointer to store the status info requested");
	}

	if (!data->channel_data[channel].is_dma_configured) {
		LOG_ERR("Requested DMA channel should first be configured");
		return -EINVAL;
	}

	regs = DMA_CHN2REG(channel);

	/*
	 * The DMA is running in parallel with CPU and so it might happen that an on-going transfer
	 * might be completed the moment user parses the status results. Disable interrupts globally
	 * so there is no chance for a new transfer to be initiated from within ISR and so changing
	 * the channel registers values.
	 */
	key = irq_lock();

	dma_ctrl_reg = regs->DMA_CTRL_REG;
	dma_idx_reg = regs->DMA_IDX_REG;
	dma_len_reg = regs->DMA_LEN_REG;

	/* Calculate how many byes each transfer consists of. */
	bus_width = DMA_CTRL_REG_GET_FIELD(BW, dma_ctrl_reg);
	if (bus_width == BUS_WIDTH_1B) {
		bus_width = 1;
	} else {
		bus_width <<= 1;
	}

	/* Convert transfers to bytes. */
	stat->total_copied = dma_idx_reg * bus_width;
	stat->pending_length = ((dma_len_reg + 1) - dma_idx_reg) * bus_width;
	stat->busy = DMA_CTRL_REG_GET_FIELD(DMA_ON, dma_ctrl_reg);
	stat->dir = data->channel_data[channel].dir;

	/* DMA does not support circular buffer functionality */
	stat->free = 0;
	stat->read_position = 0;
	stat->write_position = 0;

	irq_unlock(key);

	return 0;
}

static int dma_smartbond_get_attribute(const struct device *dev, uint32_t type, uint32_t *value)
{
	if (value == NULL) {
		LOG_ERR("User should provide a valid pointer to attribute value");
		return -EINVAL;
	}

	switch (type) {
	/*
	 * Source and destination addresses should be multiple of a channel's bus width.
	 * This info could be provided at runtime given that attributes of a specific
	 * channel could be requested.
	 */
	case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT:
	case DMA_ATTR_COPY_ALIGNMENT:
	/*
	 * Buffer size should be multiple of a channel's bus width multiplied by burst length.
	 * This info could be provided at runtime given that attributes of a specific channel
	 * could be requested.
	 */
	case DMA_ATTR_BUFFER_SIZE_ALIGNMENT:
		return -ENOSYS;
	case DMA_ATTR_MAX_BLOCK_COUNT:
		*value = DMA_BLOCK_COUNT;
		return 0;
	default:
		return -EINVAL;
	}
}

static bool dma_smartbond_chan_filter(const struct device *dev, int channel, void *filter_param)
{
	uint32_t requested_channel;

	if (channel >= DMA_CHANNELS_COUNT) {
		LOG_ERR("Inavlid DMA channel index");
		return -EINVAL;
	}

	/* If user does not provide any channel request explicitly, return true. */
	if (filter_param == NULL) {
		return true;
	}

	requested_channel = *(uint32_t *)filter_param;

	if (channel == requested_channel) {
		return true;
	}

	return false;
}

static struct dma_driver_api dma_smartbond_driver_api = {
	.config = dma_smartbond_config,
	.reload = dma_smartbond_reload,
	.start = dma_smartbond_start,
	.stop = dma_smartbond_stop,
	.suspend = dma_smartbond_suspend,
	.resume = dma_smartbond_resume,
	.get_status = dma_smartbond_get_status,
	.get_attribute = dma_smartbond_get_attribute,
	.chan_filter = dma_smartbond_chan_filter
};

static void smartbond_dma_isr(const void *arg)
{
	uint16_t dma_int_status_reg;
	int i;
	struct channel_regs *regs;
	struct dma_smartbond_data *data = ((const struct device *)arg)->data;

	/*
	 * A single interrupt line is generated for all channels and so each channel
	 * should be parsed separately.
	 */
	for (i = 0, dma_int_status_reg = DMA->DMA_INT_STATUS_REG;
		 i < DMA_CHANNELS_COUNT && dma_int_status_reg != 0; ++i, dma_int_status_reg >>= 1) {
		/* Check if the selected channel has raised the interrupt line */
		if (dma_int_status_reg & BIT(0)) {

			regs = DMA_CHN2REG(i);
			/*
			 * Should be valid if callbacks are explicitly enabled by users.
			 * Interrupt should be triggered only when the total size of
			 * bytes has been transferred. Bus errors cannot raise interrupts.
			 */
			if (data->channel_data[i].cb) {
				data->channel_data[i].cb((const struct device *)arg,
				data->channel_data[i].user_data, i, DMA_STATUS_COMPLETE);
			}
			/* Channel line should be cleared otherwise ISR will keep firing! */
			DMA->DMA_CLEAR_INT_REG = BIT(i);
		}
	}
}

static int dma_smartbond_init(const struct device *dev)
{
#ifdef CONFIG_DMA_64BIT
	LOG_ERR("64-bit addressing mode is not supported\n");
	return -ENOSYS;
#endif

	int idx;
	struct dma_smartbond_data *data;

	data = dev->data;
	data->dma_ctx.magic = DMA_MAGIC;
	data->dma_ctx.dma_channels = DMA_CHANNELS_COUNT;
	data->dma_ctx.atomic = data->channels_atomic;

	/* Make sure that all channels are disabled. */
	for (idx = 0; idx < DMA_CHANNELS_COUNT; idx++) {
		dma_smartbond_set_channel_status(idx, false);
		data->channel_data[idx].is_dma_configured = false;
	}

	IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_dma_isr,
								DEVICE_DT_INST_GET(0), 0);

	return 0;
}

#define SMARTBOND_DMA_INIT(inst) \
	BUILD_ASSERT((inst) == 0, "multiple instances are not supported"); \
	\
	static struct dma_smartbond_data dma_smartbond_data_ ## inst; \
	\
	DEVICE_DT_INST_DEFINE(0, dma_smartbond_init, NULL, \
		&dma_smartbond_data_ ## inst, NULL,	\
		POST_KERNEL, \
		CONFIG_DMA_INIT_PRIORITY, \
		&dma_smartbond_driver_api);

DT_INST_FOREACH_STATUS_OKAY(SMARTBOND_DMA_INIT)
