/*
 * Copyright (c) 2023-2024 Analog Devices, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifdef CONFIG_UART_ASYNC_API
#include <zephyr/drivers/dma.h>
#include <wrap_max32_dma.h>
#endif
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/clock_control/adi_max32_clock_control.h>

#include <wrap_max32_uart.h>

#define DT_DRV_COMPAT adi_max32_uart

LOG_MODULE_REGISTER(uart_max32, CONFIG_UART_LOG_LEVEL);

#ifdef CONFIG_UART_ASYNC_API
struct max32_uart_dma_config {
	const struct device *dev;
	const uint32_t channel;
	const uint32_t slot;
};
#endif /* CONFIG_UART_ASYNC_API */

struct max32_uart_config {
	mxc_uart_regs_t *regs;
	const struct pinctrl_dev_config *pctrl;
	const struct device *clock;
	struct max32_perclk perclk;
	struct uart_config uart_conf;
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API)
	uart_irq_config_func_t irq_config_func;
#endif /* CONFIG_UART_INTERRUPT_DRIVEN || CONFIG_UART_ASYNC_API */
#ifdef CONFIG_UART_ASYNC_API
	const struct max32_uart_dma_config tx_dma;
	const struct max32_uart_dma_config rx_dma;
#endif /* CONFIG_UART_ASYNC_API */
};

#ifdef CONFIG_UART_ASYNC_API
#define MAX32_UART_TX_CACHE_NUM 2
struct max32_uart_async_tx {
	const uint8_t *buf;
	const uint8_t *src;
	size_t len;
	uint8_t cache[MAX32_UART_TX_CACHE_NUM][CONFIG_UART_TX_CACHE_LEN];
	uint8_t cache_id;
	struct dma_block_config dma_blk;
	int32_t timeout;
	struct k_work_delayable timeout_work;
};

struct max32_uart_async_rx {
	uint8_t *buf;
	size_t len;
	size_t offset;
	size_t counter;
	uint8_t *next_buf;
	size_t next_len;
	int32_t timeout;
	struct k_work_delayable timeout_work;
};

struct max32_uart_async_data {
	const struct device *uart_dev;
	struct max32_uart_async_tx tx;
	struct max32_uart_async_rx rx;
	uart_callback_t cb;
	void *user_data;
};
#endif

struct max32_uart_data {
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_callback_user_data_t cb; /* Interrupt callback */
	void *cb_data;                    /* Interrupt callback arg */
	uint32_t flags;                   /* Cached interrupt flags */
	uint32_t status;                  /* Cached status flags */
#endif
#ifdef CONFIG_UART_ASYNC_API
	struct max32_uart_async_data async;
#endif
	struct uart_config conf; /* baudrate, stopbits, ... */
};

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static void uart_max32_isr(const struct device *dev);
#endif

#ifdef CONFIG_UART_ASYNC_API
static int uart_max32_tx_dma_load(const struct device *dev, uint8_t *buf, size_t len);
#endif

static void api_poll_out(const struct device *dev, unsigned char c)
{
	const struct max32_uart_config *cfg = dev->config;

	MXC_UART_WriteCharacter(cfg->regs, c);
}

static int api_poll_in(const struct device *dev, unsigned char *c)
{
	int val;
	const struct max32_uart_config *cfg = dev->config;

	val = MXC_UART_ReadCharacterRaw(cfg->regs);
	if (val >= 0) {
		*c = (unsigned char)val;
	} else {
		return -1;
	}

	return 0;
}

static int api_err_check(const struct device *dev)
{
	int err = 0;
	uint32_t flags;
	const struct max32_uart_config *cfg = dev->config;

	flags = MXC_UART_GetFlags(cfg->regs);

	if (flags & ADI_MAX32_UART_ERROR_FRAMING) {
		err |= UART_ERROR_FRAMING;
	}

	if (flags & ADI_MAX32_UART_ERROR_PARITY) {
		err |= UART_ERROR_PARITY;
	}

	if (flags & ADI_MAX32_UART_ERROR_OVERRUN) {
		err |= UART_ERROR_OVERRUN;
	}

	return err;
}

static int api_configure(const struct device *dev, const struct uart_config *uart_cfg)
{
	int err;
	const struct max32_uart_config *const cfg = dev->config;
	mxc_uart_regs_t *regs = cfg->regs;
	struct max32_uart_data *data = dev->data;

	/*
	 *  Set parity
	 */
	if (data->conf.parity != uart_cfg->parity) {
		mxc_uart_parity_t mxc_parity;

		switch (uart_cfg->parity) {
		case UART_CFG_PARITY_NONE:
			mxc_parity = ADI_MAX32_UART_CFG_PARITY_NONE;
			break;
		case UART_CFG_PARITY_ODD:
			mxc_parity = ADI_MAX32_UART_CFG_PARITY_ODD;
			break;
		case UART_CFG_PARITY_EVEN:
			mxc_parity = ADI_MAX32_UART_CFG_PARITY_EVEN;
			break;
		case UART_CFG_PARITY_MARK:
#if defined(ADI_MAX32_UART_CFG_PARITY_MARK)
			mxc_parity = ADI_MAX32_UART_CFG_PARITY_MARK;
			break;
#else
			return -ENOTSUP;
#endif
		case UART_CFG_PARITY_SPACE:
#if defined(ADI_MAX32_UART_CFG_PARITY_SPACE)
			mxc_parity = ADI_MAX32_UART_CFG_PARITY_SPACE;
			break;
#else
			return -ENOTSUP;
#endif
		default:
			return -EINVAL;
		}

		err = MXC_UART_SetParity(regs, mxc_parity);
		if (err < 0) {
			return -ENOTSUP;
		}
		/* incase of success keep configuration */
		data->conf.parity = uart_cfg->parity;
	}

	/*
	 *  Set stop bit
	 */
	if (data->conf.stop_bits != uart_cfg->stop_bits) {
		if (uart_cfg->stop_bits == UART_CFG_STOP_BITS_1) {
			err = MXC_UART_SetStopBits(regs, MXC_UART_STOP_1);
		} else if (uart_cfg->stop_bits == UART_CFG_STOP_BITS_2) {
			err = MXC_UART_SetStopBits(regs, MXC_UART_STOP_2);
		} else {
			return -ENOTSUP;
		}
		if (err < 0) {
			return -ENOTSUP;
		}
		/* incase of success keep configuration */
		data->conf.stop_bits = uart_cfg->stop_bits;
	}

	/*
	 *  Set data bit
	 *  Valid data for MAX32  is 5-6-7-8
	 *  Valid data for Zepyhr is 0-1-2-3
	 *  Added +5 to index match.
	 */
	if (data->conf.data_bits != uart_cfg->data_bits) {
		err = MXC_UART_SetDataSize(regs, (5 + uart_cfg->data_bits));
		if (err < 0) {
			return -ENOTSUP;
		}
		/* incase of success keep configuration */
		data->conf.data_bits = uart_cfg->data_bits;
	}

	/*
	 *  Set flow control
	 *  Flow control not implemented yet so that only support no flow mode
	 */
	if (data->conf.flow_ctrl != uart_cfg->flow_ctrl) {
		if (uart_cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) {
			return -ENOTSUP;
		}
		data->conf.flow_ctrl = uart_cfg->flow_ctrl;
	}

	/*
	 *  Set baudrate
	 */
	if (data->conf.baudrate != uart_cfg->baudrate) {
		err = Wrap_MXC_UART_SetFrequency(regs, uart_cfg->baudrate, cfg->perclk.clk_src);
		if (err < 0) {
			return -ENOTSUP;
		}
		/* In case of success keep configuration */
		data->conf.baudrate = uart_cfg->baudrate;
	}
	return 0;
}

#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE

static int api_config_get(const struct device *dev, struct uart_config *uart_cfg)
{
	struct max32_uart_data *data = dev->data;

	/* copy configs from global setting */
	*uart_cfg = data->conf;

	return 0;
}

#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */

#ifdef CONFIG_UART_ASYNC_API
static void uart_max32_async_tx_timeout(struct k_work *work);
static void uart_max32_async_rx_timeout(struct k_work *work);
#endif /* CONFIG_UART_ASYNC_API */

static int uart_max32_init(const struct device *dev)
{
	int ret;
	const struct max32_uart_config *const cfg = dev->config;
	mxc_uart_regs_t *regs = cfg->regs;
#ifdef CONFIG_UART_ASYNC_API
	struct max32_uart_data *data = dev->data;
#endif

	if (!device_is_ready(cfg->clock)) {
		LOG_ERR("Clock control device not ready");
		return -ENODEV;
	}

	ret = MXC_UART_Shutdown(regs);
	if (ret) {
		return ret;
	}

	ret = clock_control_on(cfg->clock, (clock_control_subsys_t)&cfg->perclk);
	if (ret != 0) {
		LOG_ERR("Cannot enable UART clock");
		return ret;
	}

	ret = pinctrl_apply_state(cfg->pctrl, PINCTRL_STATE_DEFAULT);
	if (ret) {
		return ret;
	}

	ret = api_configure(dev, &cfg->uart_conf);
	if (ret) {
		return ret;
	}

	ret = Wrap_MXC_UART_Init(regs);
	if (ret) {
		return ret;
	}

#if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API)
	/* Clear any pending UART RX/TX interrupts */
	MXC_UART_ClearFlags(regs, (ADI_MAX32_UART_INT_RX | ADI_MAX32_UART_INT_TX));
	cfg->irq_config_func(dev);
#endif

#ifdef CONFIG_UART_ASYNC_API
	data->async.uart_dev = dev;
	k_work_init_delayable(&data->async.tx.timeout_work, uart_max32_async_tx_timeout);
	k_work_init_delayable(&data->async.rx.timeout_work, uart_max32_async_rx_timeout);
	data->async.rx.len = 0;
	data->async.rx.offset = 0;
#endif

	return ret;
}

#ifdef CONFIG_UART_INTERRUPT_DRIVEN

static int api_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size)
{
	unsigned int num_tx = 0;
	const struct max32_uart_config *cfg = dev->config;

	num_tx = MXC_UART_WriteTXFIFO(cfg->regs, (unsigned char *)tx_data, size);

	return (int)num_tx;
}

static int api_fifo_read(const struct device *dev, uint8_t *rx_data, const int size)
{
	unsigned int num_rx = 0;
	const struct max32_uart_config *cfg = dev->config;

	num_rx = MXC_UART_ReadRXFIFO(cfg->regs, (unsigned char *)rx_data, size);
	if (num_rx == 0) {
		MXC_UART_ClearFlags(cfg->regs, ADI_MAX32_UART_INT_RX);
	}

	return num_rx;
}

static void api_irq_tx_enable(const struct device *dev)
{
	const struct max32_uart_config *cfg = dev->config;
	unsigned int key;

	MXC_UART_EnableInt(cfg->regs, ADI_MAX32_UART_INT_TX | ADI_MAX32_UART_INT_TX_OEM);

	key = irq_lock();
	uart_max32_isr(dev);
	irq_unlock(key);
}

static void api_irq_tx_disable(const struct device *dev)
{
	const struct max32_uart_config *cfg = dev->config;

	MXC_UART_DisableInt(cfg->regs, ADI_MAX32_UART_INT_TX | ADI_MAX32_UART_INT_TX_OEM);
}

static int api_irq_tx_ready(const struct device *dev)
{
	struct max32_uart_data *const data = dev->data;
	const struct max32_uart_config *cfg = dev->config;
	uint32_t inten = Wrap_MXC_UART_GetRegINTEN(cfg->regs);

	return ((inten & (ADI_MAX32_UART_INT_TX | ADI_MAX32_UART_INT_TX_OEM)) &&
		!(data->status & MXC_F_UART_STATUS_TX_FULL));
}

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

	if (MXC_UART_GetActive(cfg->regs) == E_BUSY) {
		return 0;
	} else {
		return 1; /* transmission completed */
	}
}

static int api_irq_rx_ready(const struct device *dev)
{
	struct max32_uart_data *const data = dev->data;
	const struct max32_uart_config *cfg = dev->config;
	uint32_t inten = Wrap_MXC_UART_GetRegINTEN(cfg->regs);

	return ((inten & ADI_MAX32_UART_INT_RX) && !(data->status & ADI_MAX32_UART_RX_EMPTY));
}

static void api_irq_err_enable(const struct device *dev)
{
	const struct max32_uart_config *cfg = dev->config;

	MXC_UART_EnableInt(cfg->regs, ADI_MAX32_UART_ERROR_INTERRUPTS);
}

static void api_irq_err_disable(const struct device *dev)
{
	const struct max32_uart_config *cfg = dev->config;

	MXC_UART_DisableInt(cfg->regs, ADI_MAX32_UART_ERROR_INTERRUPTS);
}

static int api_irq_is_pending(const struct device *dev)
{
	struct max32_uart_data *const data = dev->data;

	return (data->flags & (ADI_MAX32_UART_INT_RX | ADI_MAX32_UART_INT_TX));
}

static int api_irq_update(const struct device *dev)
{
	struct max32_uart_data *const data = dev->data;
	const struct max32_uart_config *const cfg = dev->config;

	data->flags = MXC_UART_GetFlags(cfg->regs);
	data->status = MXC_UART_GetStatus(cfg->regs);

	MXC_UART_ClearFlags(cfg->regs, data->flags);

	return 1;
}

static void api_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb,
				 void *cb_data)
{
	struct max32_uart_data *const dev_data = dev->data;

	dev_data->cb = cb;
	dev_data->cb_data = cb_data;
}

#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

#if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API)
static void api_irq_rx_enable(const struct device *dev)
{
	const struct max32_uart_config *cfg = dev->config;

	MXC_UART_EnableInt(cfg->regs, ADI_MAX32_UART_INT_RX);
}

static void api_irq_rx_disable(const struct device *dev)
{
	const struct max32_uart_config *cfg = dev->config;

	MXC_UART_DisableInt(cfg->regs, ADI_MAX32_UART_INT_RX);
}

static void uart_max32_isr(const struct device *dev)
{
	struct max32_uart_data *data = dev->data;
	const struct max32_uart_config *cfg = dev->config;
	uint32_t intfl;

	intfl = MXC_UART_GetFlags(cfg->regs);

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	if (data->cb) {
		data->cb(dev, data->cb_data);
	}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

#ifdef CONFIG_UART_ASYNC_API
	if (data->async.rx.timeout != SYS_FOREVER_US && data->async.rx.timeout != 0 &&
	    (intfl & ADI_MAX32_UART_INT_RX)) {
		k_work_reschedule(&data->async.rx.timeout_work, K_USEC(data->async.rx.timeout));
	}
#endif /* CONFIG_UART_ASYNC_API */

	/* Clear RX/TX interrupts flag after cb is called */
	MXC_UART_ClearFlags(cfg->regs, intfl);
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN || CONFIG_UART_ASYNC_API */

#if defined(CONFIG_UART_ASYNC_API)

static inline void async_timer_start(struct k_work_delayable *work, int32_t timeout)
{
	if ((timeout != SYS_FOREVER_US) && (timeout != 0)) {
		k_work_reschedule(work, K_USEC(timeout));
	}
}

static void async_user_callback(const struct device *dev, struct uart_event *evt)
{
	const struct max32_uart_data *data = dev->data;

	if (data->async.cb) {
		data->async.cb(dev, evt, data->async.user_data);
	}
}

static uint32_t load_tx_cache(const uint8_t *src, size_t len, uint8_t *dest)
{
	memcpy(dest, src, MIN(len, CONFIG_UART_TX_CACHE_LEN));

	return MIN(len, CONFIG_UART_TX_CACHE_LEN);
}

static void uart_max32_async_tx_callback(const struct device *dma_dev, void *user_data,
					 uint32_t channel, int status)
{
	const struct device *dev = user_data;
	const struct max32_uart_config *config = dev->config;
	struct max32_uart_data *data = dev->data;
	struct max32_uart_async_tx *tx = &data->async.tx;
	struct dma_status dma_stat;
	int ret;

	unsigned int key = irq_lock();

	dma_get_status(config->tx_dma.dev, config->tx_dma.channel, &dma_stat);
	/* Skip callback if channel is still busy */
	if (dma_stat.busy) {
		irq_unlock(key);
		return;
	}

	k_work_cancel_delayable(&tx->timeout_work);
	Wrap_MXC_UART_DisableTxDMA(config->regs);

	irq_unlock(key);

	tx->len -= tx->dma_blk.block_size;
	if (tx->len > 0) {
		tx->cache_id = !(tx->cache_id);
		ret = uart_max32_tx_dma_load(dev, tx->cache[tx->cache_id],
					     MIN(tx->len, CONFIG_UART_TX_CACHE_LEN));
		if (ret < 0) {
			LOG_ERR("Error configuring Tx DMA (%d)", ret);
			return;
		}

		ret = dma_start(config->tx_dma.dev, config->tx_dma.channel);
		if (ret < 0) {
			LOG_ERR("Error starting Tx DMA (%d)", ret);
			return;
		}

		async_timer_start(&tx->timeout_work, tx->timeout);

		Wrap_MXC_UART_SetTxDMALevel(config->regs, 2);
		Wrap_MXC_UART_EnableTxDMA(config->regs);

		/* Load next chunk as well */
		if (tx->len > CONFIG_UART_TX_CACHE_LEN) {
			tx->src += load_tx_cache(tx->src, tx->len - CONFIG_UART_TX_CACHE_LEN,
						 tx->cache[!(tx->cache_id)]);
		}
	} else {
		struct uart_event tx_done = {
			.type = status == 0 ? UART_TX_DONE : UART_TX_ABORTED,
			.data.tx.buf = tx->buf,
			.data.tx.len = tx->len,
		};
		async_user_callback(dev, &tx_done);
	}
}

static int uart_max32_tx_dma_load(const struct device *dev, uint8_t *buf, size_t len)
{
	int ret;
	const struct max32_uart_config *config = dev->config;
	struct max32_uart_data *data = dev->data;
	struct dma_config dma_cfg = {0};
	struct dma_block_config *dma_blk = &data->async.tx.dma_blk;

	dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL;
	dma_cfg.dma_callback = uart_max32_async_tx_callback;
	dma_cfg.user_data = (void *)dev;
	dma_cfg.dma_slot = config->tx_dma.slot;
	dma_cfg.block_count = 1;
	dma_cfg.source_data_size = 1U;
	dma_cfg.source_burst_length = 1U;
	dma_cfg.dest_data_size = 1U;
	dma_cfg.head_block = dma_blk;
	dma_blk->block_size = len;
	dma_blk->source_address = (uint32_t)buf;

	ret = dma_config(config->tx_dma.dev, config->tx_dma.channel, &dma_cfg);
	if (ret < 0) {
		return ret;
	}

	return 0;
}

static int api_callback_set(const struct device *dev, uart_callback_t callback, void *user_data)
{
	struct max32_uart_data *data = dev->data;

	data->async.cb = callback;
	data->async.user_data = user_data;

	return 0;
}

static int api_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout)
{
	struct max32_uart_data *data = dev->data;
	const struct max32_uart_config *config = dev->config;
	struct dma_status dma_stat;
	int ret;
	bool use_cache = false;
	unsigned int key = irq_lock();

	if (config->tx_dma.channel == 0xFF) {
		LOG_ERR("Tx DMA channel is not configured");
		ret = -ENOTSUP;
		goto unlock;
	}

	ret = dma_get_status(config->tx_dma.dev, config->tx_dma.channel, &dma_stat);
	if (ret < 0 || dma_stat.busy) {
		LOG_ERR("DMA Tx %s", ret < 0 ? "error" : "busy");
		irq_unlock(key);
		return ret < 0 ? ret : -EBUSY;
	}

	data->async.tx.buf = buf;
	data->async.tx.len = len;
	data->async.tx.src = data->async.tx.buf;

	if (((uint32_t)buf < MXC_SRAM_MEM_BASE) ||
	    (((uint32_t)buf + len) > (MXC_SRAM_MEM_BASE + MXC_SRAM_MEM_SIZE))) {
		use_cache = true;
		len = load_tx_cache(data->async.tx.src, MIN(len, CONFIG_UART_TX_CACHE_LEN),
				    data->async.tx.cache[0]);
		data->async.tx.src += len;
		data->async.tx.cache_id = 0;
	}

	ret = uart_max32_tx_dma_load(dev, use_cache ? data->async.tx.cache[0] : ((uint8_t *)buf),
				     len);
	if (ret < 0) {
		LOG_ERR("Error configuring Tx DMA (%d)", ret);
		goto unlock;
	}

	ret = dma_start(config->tx_dma.dev, config->tx_dma.channel);
	if (ret < 0) {
		LOG_ERR("Error starting Tx DMA (%d)", ret);
		goto unlock;
	}

	data->async.tx.timeout = timeout;
	async_timer_start(&data->async.tx.timeout_work, timeout);

	Wrap_MXC_UART_SetTxDMALevel(config->regs, 2);
	Wrap_MXC_UART_EnableTxDMA(config->regs);

unlock:
	irq_unlock(key);

	return ret;
}

static int api_tx_abort(const struct device *dev)
{
	int ret;
	struct max32_uart_data *data = dev->data;
	const struct max32_uart_config *config = dev->config;
	struct dma_status dma_stat;
	size_t bytes_sent;

	unsigned int key = irq_lock();

	k_work_cancel_delayable(&data->async.tx.timeout_work);

	Wrap_MXC_UART_DisableTxDMA(config->regs);

	ret = dma_get_status(config->tx_dma.dev, config->tx_dma.channel, &dma_stat);
	if (!dma_stat.busy) {
		irq_unlock(key);
		return 0;
	}

	bytes_sent = (ret == 0) ? (data->async.tx.len - dma_stat.pending_length) : 0;

	ret = dma_stop(config->tx_dma.dev, config->tx_dma.channel);

	irq_unlock(key);

	if (ret == 0) {
		struct uart_event tx_aborted = {
			.type = UART_TX_ABORTED,
			.data.tx.buf = data->async.tx.buf,
			.data.tx.len = bytes_sent,
		};
		async_user_callback(dev, &tx_aborted);
	}

	return 0;
}

static void uart_max32_async_tx_timeout(struct k_work *work)
{
	struct k_work_delayable *dwork = k_work_delayable_from_work(work);
	struct max32_uart_async_tx *tx =
		CONTAINER_OF(dwork, struct max32_uart_async_tx, timeout_work);
	struct max32_uart_async_data *async = CONTAINER_OF(tx, struct max32_uart_async_data, tx);
	struct max32_uart_data *data = CONTAINER_OF(async, struct max32_uart_data, async);

	api_tx_abort(data->async.uart_dev);
}

static int api_rx_disable(const struct device *dev)
{
	struct max32_uart_data *data = dev->data;
	const struct max32_uart_config *config = dev->config;
	int ret;
	unsigned int key = irq_lock();

	k_work_cancel_delayable(&data->async.rx.timeout_work);

	Wrap_MXC_UART_DisableRxDMA(config->regs);

	ret = dma_stop(config->rx_dma.dev, config->rx_dma.channel);
	if (ret) {
		LOG_ERR("Error stopping Rx DMA (%d)", ret);
		irq_unlock(key);
		return ret;
	}

	api_irq_rx_disable(dev);

	irq_unlock(key);

	/* Release current buffer event */
	struct uart_event rel_event = {
		.type = UART_RX_BUF_RELEASED,
		.data.rx_buf.buf = data->async.rx.buf,
	};
	async_user_callback(dev, &rel_event);

	/* Disable RX event */
	struct uart_event rx_disabled = {.type = UART_RX_DISABLED};

	async_user_callback(dev, &rx_disabled);

	data->async.rx.buf = NULL;
	data->async.rx.len = 0;
	data->async.rx.counter = 0;
	data->async.rx.offset = 0;

	if (data->async.rx.next_buf) {
		/* Release next buffer event */
		struct uart_event next_rel_event = {
			.type = UART_RX_BUF_RELEASED,
			.data.rx_buf.buf = data->async.rx.next_buf,
		};
		async_user_callback(dev, &next_rel_event);
		data->async.rx.next_buf = NULL;
		data->async.rx.next_len = 0;
	}

	return 0;
}

static void uart_max32_async_rx_callback(const struct device *dma_dev, void *user_data,
					 uint32_t channel, int status)
{
	const struct device *dev = user_data;
	const struct max32_uart_config *config = dev->config;
	struct max32_uart_data *data = dev->data;
	struct max32_uart_async_data *async = &data->async;
	struct dma_status dma_stat;
	size_t total_rx;

	unsigned int key = irq_lock();

	dma_get_status(config->rx_dma.dev, config->rx_dma.channel, &dma_stat);

	if (dma_stat.pending_length > 0) {
		irq_unlock(key);
		return;
	}

	total_rx = async->rx.len - dma_stat.pending_length;

	api_irq_rx_disable(dev);

	irq_unlock(key);

	if (total_rx > async->rx.offset) {
		async->rx.counter = total_rx - async->rx.offset;
		struct uart_event rdy_event = {
			.type = UART_RX_RDY,
			.data.rx.buf = async->rx.buf,
			.data.rx.len = async->rx.counter,
			.data.rx.offset = async->rx.offset,
		};
		async_user_callback(dev, &rdy_event);
	}

	if (async->rx.next_buf) {
		async->rx.offset = 0;
		async->rx.counter = 0;

		struct uart_event rel_event = {
			.type = UART_RX_BUF_RELEASED,
			.data.rx_buf.buf = async->rx.buf,
		};
		async_user_callback(dev, &rel_event);

		async->rx.buf = async->rx.next_buf;
		async->rx.len = async->rx.next_len;

		async->rx.next_buf = NULL;
		async->rx.next_len = 0;
		struct uart_event req_event = {
			.type = UART_RX_BUF_REQUEST,
		};
		async_user_callback(dev, &req_event);

		dma_reload(config->rx_dma.dev, config->rx_dma.channel, config->rx_dma.slot,
			   (uint32_t)async->rx.buf, async->rx.len);
		dma_start(config->rx_dma.dev, config->rx_dma.channel);

		api_irq_rx_enable(dev);
		async_timer_start(&async->rx.timeout_work, async->rx.timeout);
	} else {
		api_rx_disable(dev);
	}
}

static int api_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout)
{
	struct max32_uart_data *data = dev->data;
	const struct max32_uart_config *config = dev->config;
	struct dma_status dma_stat;
	struct dma_config dma_cfg = {0};
	struct dma_block_config dma_blk = {0};
	int ret;

	unsigned int key = irq_lock();

	if (config->rx_dma.channel == 0xFF) {
		LOG_ERR("Rx DMA channel is not configured");
		irq_unlock(key);
		return -ENOTSUP;
	}

	ret = dma_get_status(config->rx_dma.dev, config->rx_dma.channel, &dma_stat);
	if (ret < 0 || dma_stat.busy) {
		LOG_ERR("DMA Rx %s", ret < 0 ? "error" : "busy");
		irq_unlock(key);
		return ret < 0 ? ret : -EBUSY;
	}

	data->async.rx.buf = buf;
	data->async.rx.len = len;

	dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL;
	dma_cfg.dma_callback = uart_max32_async_rx_callback;
	dma_cfg.user_data = (void *)dev;
	dma_cfg.dma_slot = config->rx_dma.slot;
	dma_cfg.block_count = 1;
	dma_cfg.source_data_size = 1U;
	dma_cfg.source_burst_length = 1U;
	dma_cfg.dest_data_size = 1U;
	dma_cfg.head_block = &dma_blk;
	dma_blk.block_size = len;
	dma_blk.dest_address = (uint32_t)buf;

	ret = dma_config(config->rx_dma.dev, config->rx_dma.channel, &dma_cfg);
	if (ret < 0) {
		LOG_ERR("Error configuring Rx DMA (%d)", ret);
		irq_unlock(key);
		return ret;
	}

	ret = dma_start(config->rx_dma.dev, config->rx_dma.channel);
	if (ret < 0) {
		LOG_ERR("Error starting Rx DMA (%d)", ret);
		irq_unlock(key);
		return ret;
	}

	data->async.rx.timeout = timeout;

	Wrap_MXC_UART_SetRxDMALevel(config->regs, 1);
	Wrap_MXC_UART_EnableRxDMA(config->regs);

	struct uart_event buf_req = {
		.type = UART_RX_BUF_REQUEST,
	};

	async_user_callback(dev, &buf_req);

	api_irq_rx_enable(dev);
	async_timer_start(&data->async.rx.timeout_work, timeout);

	irq_unlock(key);
	return ret;
}

static int api_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len)
{
	struct max32_uart_data *data = dev->data;

	data->async.rx.next_buf = buf;
	data->async.rx.next_len = len;

	return 0;
}

static void uart_max32_async_rx_timeout(struct k_work *work)
{
	struct k_work_delayable *dwork = k_work_delayable_from_work(work);
	struct max32_uart_async_rx *rx =
		CONTAINER_OF(dwork, struct max32_uart_async_rx, timeout_work);
	struct max32_uart_async_data *async = CONTAINER_OF(rx, struct max32_uart_async_data, rx);
	struct max32_uart_data *data = CONTAINER_OF(async, struct max32_uart_data, async);
	const struct max32_uart_config *config = data->async.uart_dev->config;
	struct dma_status dma_stat;
	uint32_t total_rx;

	unsigned int key = irq_lock();

	dma_get_status(config->rx_dma.dev, config->rx_dma.channel, &dma_stat);

	api_irq_rx_disable(data->async.uart_dev);
	k_work_cancel_delayable(&data->async.rx.timeout_work);

	irq_unlock(key);

	total_rx = async->rx.len - dma_stat.pending_length;

	if (total_rx > async->rx.offset) {
		async->rx.counter = total_rx - async->rx.offset;
		struct uart_event rdy_event = {
			.type = UART_RX_RDY,
			.data.rx.buf = async->rx.buf,
			.data.rx.len = async->rx.counter,
			.data.rx.offset = async->rx.offset,
		};
		async_user_callback(async->uart_dev, &rdy_event);
	}
	async->rx.offset += async->rx.counter;
	async->rx.counter = 0;

	api_irq_rx_enable(data->async.uart_dev);
}

#endif

static DEVICE_API(uart, uart_max32_driver_api) = {
	.poll_in = api_poll_in,
	.poll_out = api_poll_out,
	.err_check = api_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
	.configure = api_configure,
	.config_get = api_config_get,
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	.fifo_fill = api_fifo_fill,
	.fifo_read = api_fifo_read,
	.irq_tx_enable = api_irq_tx_enable,
	.irq_tx_disable = api_irq_tx_disable,
	.irq_tx_ready = api_irq_tx_ready,
	.irq_rx_enable = api_irq_rx_enable,
	.irq_rx_disable = api_irq_rx_disable,
	.irq_tx_complete = api_irq_tx_complete,
	.irq_rx_ready = api_irq_rx_ready,
	.irq_err_enable = api_irq_err_enable,
	.irq_err_disable = api_irq_err_disable,
	.irq_is_pending = api_irq_is_pending,
	.irq_update = api_irq_update,
	.irq_callback_set = api_irq_callback_set,
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
#ifdef CONFIG_UART_ASYNC_API
	.callback_set = api_callback_set,
	.tx = api_tx,
	.tx_abort = api_tx_abort,
	.rx_enable = api_rx_enable,
	.rx_buf_rsp = api_rx_buf_rsp,
	.rx_disable = api_rx_disable,
#endif /* CONFIG_UART_ASYNC_API */
};

#ifdef CONFIG_UART_ASYNC_API
#define MAX32_DT_INST_DMA_CTLR(n, name)                                                            \
	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, dmas),                                                \
		    (DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(n, name))), (NULL))

#define MAX32_DT_INST_DMA_CELL(n, name, cell)                                                      \
	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, dmas), (DT_INST_DMAS_CELL_BY_NAME(n, name, cell)),    \
		    (0xff))

#define MAX32_UART_DMA_INIT(n)                                                                     \
	.tx_dma.dev = MAX32_DT_INST_DMA_CTLR(n, tx),                                               \
	.tx_dma.channel = MAX32_DT_INST_DMA_CELL(n, tx, channel),                                  \
	.tx_dma.slot = MAX32_DT_INST_DMA_CELL(n, tx, slot),                                        \
	.rx_dma.dev = MAX32_DT_INST_DMA_CTLR(n, rx),                                               \
	.rx_dma.channel = MAX32_DT_INST_DMA_CELL(n, rx, channel),                                  \
	.rx_dma.slot = MAX32_DT_INST_DMA_CELL(n, rx, slot),
#else
#define MAX32_UART_DMA_INIT(n)
#endif

#if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API)
#define MAX32_UART_USE_IRQ 1
#else
#define MAX32_UART_USE_IRQ 0
#endif

#define MAX32_UART_INIT(_num)                                                                      \
	PINCTRL_DT_INST_DEFINE(_num);                                                              \
	IF_ENABLED(MAX32_UART_USE_IRQ,                                                             \
		   (static void uart_max32_irq_init_##_num(const struct device *dev)               \
		   {             \
			   IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority),            \
				       uart_max32_isr, DEVICE_DT_INST_GET(_num), 0);               \
			   irq_enable(DT_INST_IRQN(_num));                                         \
		   }));                                                                            \
	static const struct max32_uart_config max32_uart_config_##_num = {                         \
		.regs = (mxc_uart_regs_t *)DT_INST_REG_ADDR(_num),                                 \
		.pctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(_num),                                     \
		.clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(_num)),                                 \
		.perclk.bus = DT_INST_CLOCKS_CELL(_num, offset),                                   \
		.perclk.bit = DT_INST_CLOCKS_CELL(_num, bit),                                      \
		.perclk.clk_src =                                                                  \
			DT_INST_PROP_OR(_num, clock_source, ADI_MAX32_PRPH_CLK_SRC_PCLK),          \
		.uart_conf.baudrate = DT_INST_PROP(_num, current_speed),                           \
		.uart_conf.parity = DT_INST_ENUM_IDX(_num, parity),                                \
		.uart_conf.data_bits = DT_INST_ENUM_IDX(_num, data_bits),                          \
		.uart_conf.stop_bits = DT_INST_ENUM_IDX(_num, stop_bits),                          \
		.uart_conf.flow_ctrl =                                                             \
			DT_INST_PROP_OR(index, hw_flow_control, UART_CFG_FLOW_CTRL_NONE),          \
		MAX32_UART_DMA_INIT(_num) IF_ENABLED(                                              \
			MAX32_UART_USE_IRQ, (.irq_config_func = uart_max32_irq_init_##_num,))};    \
	static struct max32_uart_data max32_uart_data##_num = {                                    \
		IF_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN, (.cb = NULL,))};                          \
	DEVICE_DT_INST_DEFINE(_num, uart_max32_init, NULL, &max32_uart_data##_num,                 \
			      &max32_uart_config_##_num, PRE_KERNEL_1,                             \
			      CONFIG_SERIAL_INIT_PRIORITY, (void *)&uart_max32_driver_api);

DT_INST_FOREACH_STATUS_OKAY(MAX32_UART_INIT)
