/*
 * Copyright (c) 2023 Microchip Technology Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT microchip_xec_dmac

#include <soc.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
#include <zephyr/dt-bindings/interrupt-controller/mchp-xec-ecia.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/util_macro.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(dma_mchp_xec, CONFIG_DMA_LOG_LEVEL);

#define XEC_DMA_DEBUG				1
#ifdef XEC_DMA_DEBUG
#include <string.h>
#endif

#define XEC_DMA_ABORT_WAIT_LOOPS		32

#define XEC_DMA_MAIN_REGS_SIZE			0x40
#define XEC_DMA_CHAN_REGS_SIZE			0x40

#define XEC_DMA_CHAN_REGS_ADDR(base, channel)	\
	(((uintptr_t)(base) + (XEC_DMA_MAIN_REGS_SIZE)) + \
	 ((uintptr_t)(channel) * XEC_DMA_CHAN_REGS_SIZE))

/* main control */
#define XEC_DMA_MAIN_CTRL_REG_MSK		0x3u
#define XEC_DMA_MAIN_CTRL_EN_POS		0
#define XEC_DMA_MAIN_CTRL_SRST_POS		1

/* channel activate register */
#define XEC_DMA_CHAN_ACTV_EN_POS		0
/* channel control register */
#define XEC_DMA_CHAN_CTRL_REG_MSK		0x037fff27u
#define XEC_DMA_CHAN_CTRL_HWFL_RUN_POS		0
#define XEC_DMA_CHAN_CTRL_REQ_POS		1
#define XEC_DMA_CHAN_CTRL_DONE_POS		2
#define XEC_DMA_CHAN_CTRL_BUSY_POS		5
#define XEC_DMA_CHAN_CTRL_M2D_POS		8
#define XEC_DMA_CHAN_CTRL_HWFL_DEV_POS		9
#define XEC_DMA_CHAN_CTRL_HWFL_DEV_MSK		0xfe00u
#define XEC_DMA_CHAN_CTRL_HWFL_DEV_MSK0		0x7fu
#define XEC_DMA_CHAN_CTRL_INCR_MEM_POS		16
#define XEC_DMA_CHAN_CTRL_INCR_DEV_POS		17
#define XEC_DMA_CHAN_CTRL_LOCK_ARB_POS		18
#define XEC_DMA_CHAN_CTRL_DIS_HWFL_POS		19
#define XEC_DMA_CHAN_CTRL_XFR_UNIT_POS		20
#define XEC_DMA_CHAN_CTRL_XFR_UNIT_MSK		0x700000u
#define XEC_DMA_CHAN_CTRL_XFR_UNIT_MSK0		0x7u
#define XEC_DMA_CHAN_CTRL_SWFL_GO_POS		24
#define XEC_DMA_CHAN_CTRL_ABORT_POS		25
/* channel interrupt status and enable registers */
#define XEC_DMA_CHAN_IES_REG_MSK		0xfu
#define XEC_DMA_CHAN_IES_BERR_POS		0
#define XEC_DMA_CHAN_IES_OVFL_ERR_POS		1
#define XEC_DMA_CHAN_IES_DONE_POS		2
#define XEC_DMA_CHAN_IES_DEV_TERM_POS		3
/* channel fsm (RO) */
#define XEC_DMA_CHAN_FSM_REG_MSK		0xffffu
#define XEC_DMA_CHAN_FSM_ARB_STATE_POS		0
#define XEC_DMA_CHAN_FSM_ARB_STATE_MSK		0xffu
#define XEC_DMA_CHAN_FSM_CTRL_STATE_POS		8
#define XEC_DMA_CHAN_FSM_CTRL_STATE_MSK		0xff00u
#define XEC_DMA_CHAN_FSM_CTRL_STATE_IDLE	0
#define XEC_DMA_CHAN_FSM_CTRL_STATE_ARB_REQ	0x100u
#define XEC_DMA_CHAN_FSM_CTRL_STATE_RD_ACT	0x200u
#define XEC_DMA_CHAN_FSM_CTRL_STATE_WR_ACT	0x300u
#define XEC_DMA_CHAN_FSM_CTRL_STATE_WAIT_DONE	0x400u

#define XEC_DMA_HWFL_DEV_VAL(d)			\
	(((uint32_t)(d) & XEC_DMA_CHAN_CTRL_HWFL_DEV_MSK0) << XEC_DMA_CHAN_CTRL_HWFL_DEV_POS)

#define XEC_DMA_CHAN_CTRL_UNIT_VAL(u)		\
	(((uint32_t)(u) & XEC_DMA_CHAN_CTRL_XFR_UNIT_MSK0) << XEC_DMA_CHAN_CTRL_XFR_UNIT_POS)

struct dma_xec_chan_regs {
	volatile uint32_t actv;
	volatile uint32_t mem_addr;
	volatile uint32_t mem_addr_end;
	volatile uint32_t dev_addr;
	volatile uint32_t control;
	volatile uint32_t istatus;
	volatile uint32_t ienable;
	volatile uint32_t fsm;
	uint32_t rsvd_20_3f[8];
};

struct dma_xec_regs {
	volatile uint32_t mctrl;
	volatile uint32_t mpkt;
	uint32_t rsvd_08_3f[14];
};

struct dma_xec_irq_info {
	uint8_t gid;	/* GIRQ id [8, 26] */
	uint8_t gpos;	/* bit position in GIRQ [0, 31] */
	uint8_t anid;	/* aggregated external NVIC input */
	uint8_t dnid;	/* direct NVIC input */
};

struct dma_xec_config {
	struct dma_xec_regs *regs;
	uint8_t dma_channels;
	uint8_t dma_requests;
	uint8_t pcr_idx;
	uint8_t pcr_pos;
	int irq_info_size;
	const struct dma_xec_irq_info *irq_info_list;
	void (*irq_connect)(void);
};

struct dma_xec_channel {
	uint32_t control;
	uint32_t mstart;
	uint32_t mend;
	uint32_t dstart;
	uint32_t isr_hw_status;
	uint32_t block_count;
	uint8_t unit_size;
	uint8_t dir;
	uint8_t flags;
	uint8_t rsvd[1];
	struct dma_block_config *head;
	struct dma_block_config *curr;
	dma_callback_t cb;
	void *user_data;
	uint32_t total_req_xfr_len;
	uint32_t total_curr_xfr_len;
};

#define DMA_XEC_CHAN_FLAGS_CB_EOB_POS		0
#define DMA_XEC_CHAN_FLAGS_CB_ERR_DIS_POS	1

struct dma_xec_data {
	struct dma_context ctx;
	struct dma_xec_channel *channels;
};

#ifdef XEC_DMA_DEBUG
static void xec_dma_debug_clean(void);
#endif

static inline struct dma_xec_chan_regs *xec_chan_regs(struct dma_xec_regs *regs, uint32_t chan)
{
	uint8_t *pregs = (uint8_t *)regs + XEC_DMA_MAIN_REGS_SIZE;

	pregs += (chan * (XEC_DMA_CHAN_REGS_SIZE));

	return (struct dma_xec_chan_regs *)pregs;
}

static inline
struct dma_xec_irq_info const *xec_chan_irq_info(const struct dma_xec_config *devcfg,
						 uint32_t channel)
{
	return &devcfg->irq_info_list[channel];
}

static int is_dma_data_size_valid(uint32_t datasz)
{
	if ((datasz == 1U) || (datasz == 2U) || (datasz == 4U)) {
		return 1;
	}

	return 0;
}

/* HW requires if unit size is 2 or 4 bytes the source/destination addresses
 * to be aligned >= 2 or 4 bytes.
 */
static int is_data_aligned(uint32_t src, uint32_t dest, uint32_t unitsz)
{
	if (unitsz == 1) {
		return 1;
	}

	if ((src | dest) & (unitsz - 1U)) {
		return 0;
	}

	return 1;
}

static void xec_dma_chan_clr(struct dma_xec_chan_regs * const chregs,
			     const struct dma_xec_irq_info *info)
{
	chregs->actv = 0;
	chregs->control = 0;
	chregs->mem_addr = 0;
	chregs->mem_addr_end = 0;
	chregs->dev_addr = 0;
	chregs->control = 0;
	chregs->ienable = 0;
	chregs->istatus = 0xffu;
	mchp_xec_ecia_girq_src_clr(info->gid, info->gpos);
}

static int is_dma_config_valid(const struct device *dev, struct dma_config *config)
{
	const struct dma_xec_config * const devcfg = dev->config;

	if (config->dma_slot >= (uint32_t)devcfg->dma_requests) {
		LOG_ERR("XEC DMA config dma slot > exceeds number of request lines");
		return 0;
	}

	if (config->source_data_size != config->dest_data_size) {
		LOG_ERR("XEC DMA requires source and dest data size identical");
		return 0;
	}

	if (!((config->channel_direction == MEMORY_TO_MEMORY) ||
	      (config->channel_direction == MEMORY_TO_PERIPHERAL) ||
	      (config->channel_direction == PERIPHERAL_TO_MEMORY))) {
		LOG_ERR("XEC DMA only support M2M, M2P, P2M");
		return 0;
	}

	if (!is_dma_data_size_valid(config->source_data_size)) {
		LOG_ERR("XEC DMA requires xfr unit size of 1, 2 or 4 bytes");
		return 0;
	}

	if (config->block_count != 1) {
		LOG_ERR("XEC DMA block count != 1");
		return 0;
	}

	return 1;
}

static int check_blocks(struct dma_xec_channel *chdata, struct dma_block_config *block,
			uint32_t block_count, uint32_t unit_size)
{
	if (!block || !chdata) {
		LOG_ERR("bad pointer");
		return -EINVAL;
	}

	chdata->total_req_xfr_len = 0;

	for (uint32_t i = 0; i < block_count; i++) {
		if ((block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT) ||
		    (block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT)) {
			LOG_ERR("XEC DMA HW does not support address decrement. Block index %u", i);
			return -EINVAL;
		}

		if (!is_data_aligned(block->source_address, block->dest_address, unit_size)) {
			LOG_ERR("XEC DMA block at index %u violates source/dest unit size", i);
			return -EINVAL;
		}

		chdata->total_req_xfr_len += block->block_size;
	}

	return 0;
}

/*
 * struct dma_config flags
 * dma_slot - peripheral source/target ID. Not used for Mem2Mem
 * channel_direction - HW supports Mem2Mem, Mem2Periph, and Periph2Mem
 * complete_callback_en - if true invoke callback on completion (no error)
 * error_callback_en - if true invoke callback on error
 * source_handshake - 0=HW, 1=SW
 * dest_handshake - 0=HW, 1=SW
 * channel_priority - 4-bit field. HW implements round-robin only.
 * source_chaining_en - Chaining channel together
 * dest_chaining_en - HW does not support channel chaining.
 * linked_channel - HW does not support
 * cyclic - HW does not support cyclic buffer. Would have to emulate with SW.
 * source_data_size - unit size of source data. HW supports 1, 2, or 4 bytes
 * dest_data_size - unit size of dest data. HW requires same as source_data_size
 * source_burst_length - HW does not support
 * dest_burst_length - HW does not support
 * block_count -
 * user_data -
 * dma_callback -
 * head_block - pointer to struct dma_block_config
 *
 * struct dma_block_config
 * source_address -
 * source_gather_interval - N/A
 * dest_address -
 * dest_scatter_interval - N/A
 * dest_scatter_count - N/A
 * source_gather_count - N/A
 * block_size
 * config - flags
 *	source_gather_en - N/A
 *	dest_scatter_en - N/A
 *	source_addr_adj - 0(increment), 1(decrement), 2(no change)
 *	dest_addr_adj - 0(increment), 1(decrement), 2(no change)
 *	source_reload_en - reload source address at end of block
 *	dest_reload_en - reload destination address at end of block
 *	fifo_mode_control - N/A
 *	flow_control_mode - 0(source req service on data available) HW does this
 *			    1(source req postposed until dest req happens) N/A
 *
 *
 * DMA channel implements memory start address, memory end address,
 * and peripheral address registers. No peripheral end address.
 * Transfer ends when memory start address increments and reaches
 * memory end address.
 *
 * Memory to Memory: copy from source_address to dest_address
 *	chan direction = Mem2Dev. chan.control b[8]=1
 *	chan mem_addr = source_address
 *	chan mem_addr_end = source_address + block_size
 *	chan dev_addr = dest_address
 *
 * Memory to Peripheral: copy from source_address(memory) to dest_address(peripheral)
 *	chan direction = Mem2Dev. chan.control b[8]=1
 *	chan mem_addr = source_address
 *	chan mem_addr_end = chan mem_addr + block_size
 *	chan dev_addr = dest_address
 *
 * Peripheral to Memory:
 *	chan direction = Dev2Mem. chan.contronl b[8]=1
 *	chan mem_addr = dest_address
 *	chan mem_addr_end = chan mem_addr + block_size
 *	chan dev_addr = source_address
 */
static int dma_xec_configure(const struct device *dev, uint32_t channel,
			     struct dma_config *config)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_regs * const regs = devcfg->regs;
	struct dma_xec_data * const data = dev->data;
	uint32_t ctrl, mstart, mend, dstart, unit_size;
	int ret;

	if (!config || (channel >= (uint32_t)devcfg->dma_channels)) {
		return -EINVAL;
	}

#ifdef XEC_DMA_DEBUG
	xec_dma_debug_clean();
#endif

	const struct dma_xec_irq_info *info = xec_chan_irq_info(devcfg, channel);
	struct dma_xec_chan_regs * const chregs = xec_chan_regs(regs, channel);
	struct dma_xec_channel *chdata = &data->channels[channel];

	chdata->total_req_xfr_len = 0;
	chdata->total_curr_xfr_len = 0;

	xec_dma_chan_clr(chregs, info);

	if (!is_dma_config_valid(dev, config)) {
		return -EINVAL;
	}

	struct dma_block_config *block = config->head_block;

	ret = check_blocks(chdata, block, config->block_count, config->source_data_size);
	if (ret) {
		return ret;
	}

	unit_size = config->source_data_size;
	chdata->unit_size = unit_size;
	chdata->head = block;
	chdata->curr = block;
	chdata->block_count = config->block_count;
	chdata->dir = config->channel_direction;

	chdata->flags = 0;
	chdata->cb = config->dma_callback;
	chdata->user_data = config->user_data;

	/* invoke callback on completion of each block instead of all blocks ? */
	if (config->complete_callback_en) {
		chdata->flags |= BIT(DMA_XEC_CHAN_FLAGS_CB_EOB_POS);
	}
	if (config->error_callback_en) { /* disable callback on errors ? */
		chdata->flags |= BIT(DMA_XEC_CHAN_FLAGS_CB_ERR_DIS_POS);
	}

	/* Use the control member of struct dma_xec_channel to
	 * store control register value containing fields invariant
	 * for all buffers: HW flow control device, direction, unit size, ...
	 * derived from struct dma_config
	 */
	ctrl = XEC_DMA_CHAN_CTRL_UNIT_VAL(unit_size);
	if (config->channel_direction == MEMORY_TO_MEMORY) {
		ctrl |= BIT(XEC_DMA_CHAN_CTRL_DIS_HWFL_POS);
	} else {
		ctrl |= XEC_DMA_HWFL_DEV_VAL(config->dma_slot);
	}

	if (config->channel_direction == PERIPHERAL_TO_MEMORY) {
		mstart = block->dest_address;
		mend = block->dest_address + block->block_size;
		dstart = block->source_address;
		if (block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
			ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_DEV_POS);
		}
		if (block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
			ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_MEM_POS);
		}
	} else {
		mstart = block->source_address;
		mend = block->source_address + block->block_size;
		dstart = block->dest_address;
		ctrl |= BIT(XEC_DMA_CHAN_CTRL_M2D_POS);
		if (block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
			ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_MEM_POS);
		}
		if (block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
			ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_DEV_POS);
		}
	}

	chdata->control = ctrl;
	chdata->mstart = mstart;
	chdata->mend = mend;
	chdata->dstart = dstart;

	chregs->actv &= ~BIT(XEC_DMA_CHAN_ACTV_EN_POS);
	chregs->mem_addr = mstart;
	chregs->mem_addr_end = mend;
	chregs->dev_addr = dstart;

	chregs->control = ctrl;
	chregs->ienable = BIT(XEC_DMA_CHAN_IES_BERR_POS) | BIT(XEC_DMA_CHAN_IES_DONE_POS);
	chregs->actv |= BIT(XEC_DMA_CHAN_ACTV_EN_POS);

	return 0;
}

/* Update previously configured DMA channel with new data source address,
 * data destination address, and size in bytes.
 * src = source address for DMA transfer
 * dst = destination address for DMA transfer
 * size = size of DMA transfer. Assume this is in bytes.
 * We assume the caller will pass src, dst, and size that matches
 * the unit size from the previous configure call.
 */
static int dma_xec_reload(const struct device *dev, uint32_t channel,
			  uint32_t src, uint32_t dst, size_t size)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_data * const data = dev->data;
	struct dma_xec_regs * const regs = devcfg->regs;
	uint32_t ctrl;

	if (channel >= (uint32_t)devcfg->dma_channels) {
		return -EINVAL;
	}

	struct dma_xec_channel *chdata = &data->channels[channel];
	struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel);

	if (chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) {
		return -EBUSY;
	}

	ctrl = chregs->control & ~(BIT(XEC_DMA_CHAN_CTRL_HWFL_RUN_POS)
				   | BIT(XEC_DMA_CHAN_CTRL_SWFL_GO_POS));
	chregs->ienable = 0;
	chregs->control = 0;
	chregs->istatus = 0xffu;

	if (ctrl & BIT(XEC_DMA_CHAN_CTRL_M2D_POS)) { /* Memory to Device */
		chdata->mstart = src;
		chdata->dstart = dst;
	} else {
		chdata->mstart = dst;
		chdata->dstart = src;
	}

	chdata->mend = chdata->mstart + size;
	chdata->total_req_xfr_len = size;
	chdata->total_curr_xfr_len = 0;

	chregs->mem_addr = chdata->mstart;
	chregs->mem_addr_end = chdata->mend;
	chregs->dev_addr = chdata->dstart;
	chregs->control = ctrl;

	return 0;
}

static int dma_xec_start(const struct device *dev, uint32_t channel)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_regs * const regs = devcfg->regs;
	uint32_t chan_ctrl = 0U;

	if (channel >= (uint32_t)devcfg->dma_channels) {
		return -EINVAL;
	}

	struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel);

	if (chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) {
		return -EBUSY;
	}

	chregs->ienable = 0u;
	chregs->istatus = 0xffu;
	chan_ctrl = chregs->control;

	if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_DIS_HWFL_POS)) {
		chan_ctrl |= BIT(XEC_DMA_CHAN_CTRL_SWFL_GO_POS);
	} else {
		chan_ctrl |= BIT(XEC_DMA_CHAN_CTRL_HWFL_RUN_POS);
	}

	chregs->ienable = BIT(XEC_DMA_CHAN_IES_BERR_POS) | BIT(XEC_DMA_CHAN_IES_DONE_POS);
	chregs->control = chan_ctrl;
	chregs->actv |= BIT(XEC_DMA_CHAN_ACTV_EN_POS);

	return 0;
}

static int dma_xec_stop(const struct device *dev, uint32_t channel)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_regs * const regs = devcfg->regs;
	int wait_loops = XEC_DMA_ABORT_WAIT_LOOPS;

	if (channel >= (uint32_t)devcfg->dma_channels) {
		return -EINVAL;
	}

	struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel);

	chregs->ienable = 0;

	if (chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) {
		chregs->ienable = 0;
		chregs->control |= BIT(XEC_DMA_CHAN_CTRL_ABORT_POS);
		/* HW stops on next unit boundary (1, 2, or 4 bytes) */

		do {
			if (!(chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS))) {
				break;
			}
		} while (wait_loops--);
	}

	chregs->mem_addr = chregs->mem_addr_end;
	chregs->fsm = 0; /* delay */
	chregs->control = 0;
	chregs->istatus = 0xffu;
	chregs->actv = 0;

	return 0;
}

/* Get DMA transfer status.
 * HW supports: MEMORY_TO_MEMORY, MEMORY_TO_PERIPHERAL, or
 * PERIPHERAL_TO_MEMORY
 * current DMA runtime status structure
 *
 * busy				- is current DMA transfer busy or idle
 * dir				- DMA transfer direction
 * pending_length		- data length pending to be transferred in bytes
 *					or platform dependent.
 * We don't implement a circular buffer
 * free                         - free buffer space
 * write_position               - write position in a circular dma buffer
 * read_position                - read position in a circular dma buffer
 *
 */
static int dma_xec_get_status(const struct device *dev, uint32_t channel,
			      struct dma_status *status)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_data * const data = dev->data;
	struct dma_xec_regs * const regs = devcfg->regs;
	uint32_t chan_ctrl = 0U;

	if ((channel >= (uint32_t)devcfg->dma_channels) || (!status)) {
		LOG_ERR("unsupported channel");
		return -EINVAL;
	}

	struct dma_xec_channel *chan_data = &data->channels[channel];
	struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel);

	chan_ctrl = chregs->control;

	if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) {
		status->busy = true;
		/* number of bytes remaining in channel */
		status->pending_length = chan_data->total_req_xfr_len -
						(chregs->mem_addr_end - chregs->mem_addr);
	} else {
		status->pending_length = chan_data->total_req_xfr_len -
						chan_data->total_curr_xfr_len;
		status->busy = false;
	}

	if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_DIS_HWFL_POS)) {
		status->dir = MEMORY_TO_MEMORY;
	} else if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_M2D_POS)) {
		status->dir = MEMORY_TO_PERIPHERAL;
	} else {
		status->dir = PERIPHERAL_TO_MEMORY;
	}

	status->total_copied = chan_data->total_curr_xfr_len;

	return 0;
}

int xec_dma_get_attribute(const struct device *dev, uint32_t type, uint32_t *value)
{
	if ((type == DMA_ATTR_MAX_BLOCK_COUNT) && value) {
		*value = 1;
		return 0;
	}

	return -EINVAL;
}

/* returns true if filter matched otherwise returns false */
static bool dma_xec_chan_filter(const struct device *dev, int ch, void *filter_param)
{
	const struct dma_xec_config * const devcfg = dev->config;
	uint32_t filter = 0u;

	if (!filter_param && devcfg->dma_channels) {
		filter = GENMASK(devcfg->dma_channels-1u, 0);
	} else {
		filter = *((uint32_t *)filter_param);
	}

	return (filter & BIT(ch));
}

/* API - HW does not stupport suspend/resume */
static const struct dma_driver_api dma_xec_api = {
	.config = dma_xec_configure,
	.reload = dma_xec_reload,
	.start = dma_xec_start,
	.stop = dma_xec_stop,
	.get_status = dma_xec_get_status,
	.chan_filter = dma_xec_chan_filter,
	.get_attribute = xec_dma_get_attribute,
};

#ifdef CONFIG_PM_DEVICE
/* TODO - DMA block has one PCR SLP_EN and one CLK_REQ.
 * If any channel is running the block's CLK_REQ is asserted.
 * CLK_REQ will not clear until all channels are done or disabled.
 * Clearing the DMA Main activate will kill DMA transactions resulting
 * possible data corruption and HW flow control device malfunctions.
 */
static int dmac_xec_pm_action(const struct device *dev,
			      enum pm_device_action action)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_regs * const regs = devcfg->regs;
	int ret = 0;

	switch (action) {
	case PM_DEVICE_ACTION_RESUME:
		regs->mctrl |= BIT(XEC_DMA_MAIN_CTRL_EN_POS);
		break;

	case PM_DEVICE_ACTION_SUSPEND:
		/* regs->mctrl &= ~BIT(XEC_DMA_MAIN_CTRL_EN_POS); */
		break;

	default:
		ret = -ENOTSUP;
	}

	return ret;
}
#endif /* CONFIG_PM_DEVICE */

/* DMA channel interrupt handler called by ISR.
 * Callback flags in struct dma_config
 * completion_callback_en
 *	0 = invoke at completion of all blocks
 *	1 = invoke at completin of each block
 * error_callback_en
 *	0 = invoke on all errors
 *	1 = disabled, do not invoke on errors
 */
/* DEBUG */
#ifdef XEC_DMA_DEBUG
static volatile uint8_t channel_isr_idx[16];
static volatile uint8_t channel_isr_sts[16][16];
static volatile uint32_t channel_isr_ctrl[16][16];

static void xec_dma_debug_clean(void)
{
	memset((void *)channel_isr_idx, 0, sizeof(channel_isr_idx));
	memset((void *)channel_isr_sts, 0, sizeof(channel_isr_sts));
	memset((void *)channel_isr_ctrl, 0, sizeof(channel_isr_ctrl));
}
#endif

static void dma_xec_irq_handler(const struct device *dev, uint32_t channel)
{
	const struct dma_xec_config * const devcfg = dev->config;
	const struct dma_xec_irq_info *info = devcfg->irq_info_list;
	struct dma_xec_data * const data = dev->data;
	struct dma_xec_channel *chan_data = &data->channels[channel];
	struct dma_xec_chan_regs * const regs = xec_chan_regs(devcfg->regs, channel);
	uint32_t sts = regs->istatus;
	int cb_status = 0;

#ifdef XEC_DMA_DEBUG
	uint8_t idx = channel_isr_idx[channel];

	if (idx < 16) {
		channel_isr_sts[channel][idx] = sts;
		channel_isr_ctrl[channel][idx] = regs->control;
		channel_isr_idx[channel] = ++idx;
	}
#endif
	LOG_DBG("maddr=0x%08x mend=0x%08x daddr=0x%08x ctrl=0x%08x sts=0x%02x", regs->mem_addr,
		regs->mem_addr_end, regs->dev_addr, regs->control, sts);

	regs->ienable = 0u;
	regs->istatus = 0xffu;
	mchp_xec_ecia_girq_src_clr(info[channel].gid, info[channel].gpos);

	chan_data->isr_hw_status = sts;
	chan_data->total_curr_xfr_len += (regs->mem_addr - chan_data->mstart);

	if (sts & BIT(XEC_DMA_CHAN_IES_BERR_POS)) {/* Bus Error? */
		if (!(chan_data->flags & BIT(DMA_XEC_CHAN_FLAGS_CB_ERR_DIS_POS))) {
			cb_status = -EIO;
		}
	}

	if (chan_data->cb) {
		chan_data->cb(dev, chan_data->user_data, channel, cb_status);
	}
}

static int dma_xec_init(const struct device *dev)
{
	const struct dma_xec_config * const devcfg = dev->config;
	struct dma_xec_regs * const regs = devcfg->regs;

	LOG_DBG("driver init");

	z_mchp_xec_pcr_periph_sleep(devcfg->pcr_idx, devcfg->pcr_pos, 0);

	/* soft reset, self-clearing */
	regs->mctrl = BIT(XEC_DMA_MAIN_CTRL_SRST_POS);
	regs->mpkt = 0u; /* I/O delay, write to read-only register */
	regs->mctrl = BIT(XEC_DMA_MAIN_CTRL_EN_POS);

	devcfg->irq_connect();

	return 0;
}

/* n = node-id, p = property, i = index */
#define DMA_XEC_GID(n, p, i) MCHP_XEC_ECIA_GIRQ(DT_PROP_BY_IDX(n, p, i))
#define DMA_XEC_GPOS(n, p, i) MCHP_XEC_ECIA_GIRQ_POS(DT_PROP_BY_IDX(n, p, i))

#define DMA_XEC_GIRQ_INFO(n, p, i)						\
	{									\
		.gid = DMA_XEC_GID(n, p, i),					\
		.gpos = DMA_XEC_GPOS(n, p, i),					\
		.anid = MCHP_XEC_ECIA_NVIC_AGGR(DT_PROP_BY_IDX(n, p, i)),	\
		.dnid = MCHP_XEC_ECIA_NVIC_DIRECT(DT_PROP_BY_IDX(n, p, i)),	\
	},

/* n = node-id, p = property, i = index(channel?) */
#define DMA_XEC_IRQ_DECLARE(node_id, p, i)					\
	static void dma_xec_chan_##i##_isr(const struct device *dev)		\
	{									\
		dma_xec_irq_handler(dev, i);					\
	}									\

#define DMA_XEC_IRQ_CONNECT_SUB(node_id, p, i)					\
	IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, i, irq),				\
		    DT_IRQ_BY_IDX(node_id, i, priority),			\
		    dma_xec_chan_##i##_isr,					\
		    DEVICE_DT_GET(node_id), 0);					\
	irq_enable(DT_IRQ_BY_IDX(node_id, i, irq));				\
	mchp_xec_ecia_enable(DMA_XEC_GID(node_id, p, i), DMA_XEC_GPOS(node_id, p, i));

/* i = instance number of DMA controller */
#define DMA_XEC_IRQ_CONNECT(inst)							\
	DT_INST_FOREACH_PROP_ELEM(inst, girqs, DMA_XEC_IRQ_DECLARE)			\
	void dma_xec_irq_connect##inst(void)						\
	{										\
		DT_INST_FOREACH_PROP_ELEM(inst, girqs, DMA_XEC_IRQ_CONNECT_SUB)		\
	}

#define DMA_XEC_DEVICE(i)								\
	BUILD_ASSERT(DT_INST_PROP(i, dma_channels) <= 16, "XEC DMA dma-channels > 16");	\
	BUILD_ASSERT(DT_INST_PROP(i, dma_requests) <= 16, "XEC DMA dma-requests > 16");	\
											\
	static struct dma_xec_channel							\
		dma_xec_ctrl##i##_chans[DT_INST_PROP(i, dma_channels)];			\
	ATOMIC_DEFINE(dma_xec_atomic##i, DT_INST_PROP(i, dma_channels));		\
											\
	static struct dma_xec_data dma_xec_data##i = {					\
		.ctx.magic = DMA_MAGIC,							\
		.ctx.dma_channels = DT_INST_PROP(i, dma_channels),			\
		.ctx.atomic = dma_xec_atomic##i,					\
		.channels = dma_xec_ctrl##i##_chans,					\
	};										\
											\
	DMA_XEC_IRQ_CONNECT(i)								\
											\
	static const struct dma_xec_irq_info dma_xec_irqi##i[] = {			\
		DT_INST_FOREACH_PROP_ELEM(i, girqs, DMA_XEC_GIRQ_INFO)			\
	};										\
	static const struct dma_xec_config dma_xec_cfg##i = {				\
		.regs = (struct dma_xec_regs *)DT_INST_REG_ADDR(i),			\
		.dma_channels = DT_INST_PROP(i, dma_channels),				\
		.dma_requests = DT_INST_PROP(i, dma_requests),				\
		.pcr_idx = DT_INST_PROP_BY_IDX(i, pcrs, 0),				\
		.pcr_pos = DT_INST_PROP_BY_IDX(i, pcrs, 1),				\
		.irq_info_size = ARRAY_SIZE(dma_xec_irqi##i),				\
		.irq_info_list = dma_xec_irqi##i,					\
		.irq_connect = dma_xec_irq_connect##i,					\
	};										\
	PM_DEVICE_DT_DEFINE(i, dmac_xec_pm_action);					\
	DEVICE_DT_INST_DEFINE(i, &dma_xec_init,						\
		PM_DEVICE_DT_GET(i),							\
		&dma_xec_data##i, &dma_xec_cfg##i,					\
		PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY,					\
		&dma_xec_api);

DT_INST_FOREACH_STATUS_OKAY(DMA_XEC_DEVICE)
