/*
 * Copyright 2023 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nxp_lcdic

#include <zephyr/drivers/mipi_dbi.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/logging/log.h>
#include <soc.h>
#include <zephyr/drivers/dma/dma_mcux_lpc.h>
LOG_MODULE_REGISTER(mipi_dbi_lcdic, CONFIG_MIPI_DBI_LOG_LEVEL);

#include <fsl_inputmux.h>

enum lcdic_data_fmt {
	LCDIC_DATA_FMT_BYTE = 0,
	LCDIC_DATA_FMT_HALFWORD = 1, /* 2 byte */
	LCDIC_DATA_FMT_WORD = 2, /* 4 byte */
};

enum lcdic_cmd_dc {
	LCDIC_COMMAND = 0,
	LCDIC_DATA = 1,
};

enum lcdic_cmd_type {
	LCDIC_RX = 0,
	LCDIC_TX = 1,
};

/* Limit imposed by size of data length field in LCDIC command */
#define LCDIC_MAX_XFER 0x40000
/* Max reset width (in terms of Timer0_Period, see RST_CTRL register) */
#define LCDIC_MAX_RST_WIDTH 0x3F

/* Descriptor for LCDIC command */
union lcdic_trx_cmd {
	struct {
		/* Data length in bytes. LCDIC transfers data_len + 1 */
		uint32_t data_len: 18;
		/* Dummy SCLK cycles between TX and RX (for SPI mode) */
		uint32_t dummy_count: 3;
		uint32_t rsvd: 2;
		/* Use auto repeat mode */
		uint32_t auto_repeat: 1;
		/* Tearing enable sync mode */
		uint32_t te_sync_mode: 2;
		/* TRX command timeout mode */
		uint32_t trx_timeout_mode: 1;
		/* Data format, see lcdic_data_fmt */
		uint32_t data_format: 2;
		/* Enable command done interrupt */
		uint32_t cmd_done_int: 1;
		/* LCD command or LCD data, see lcdic_cmd_dc */
		uint32_t cmd_data: 1;
		/* TX or RX command, see lcdic_cmd_type */
		uint32_t trx: 1;
	} bits;
	uint32_t u32;
};

struct mipi_dbi_lcdic_config {
	LCDIC_Type *base;
	void (*irq_config_func)(const struct device *dev);
	const struct pinctrl_dev_config *pincfg;
	const struct device *clock_dev;
	clock_control_subsys_t clock_subsys;
	bool swap_bytes;
};

#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
struct stream {
	const struct device *dma_dev;
	uint32_t channel;
	struct dma_config dma_cfg;
	struct dma_block_config blk_cfg[2];
};
#endif

struct mipi_dbi_lcdic_data {
	/* Tracks number of bytes remaining in command */
	uint32_t cmd_bytes;
	/* Tracks number of bytes remaining in transfer */
	uint32_t xfer_bytes;
	/* Tracks start of transfer buffer */
	const uint8_t *xfer_buf;
	/* When sending data that does not evenly fit into 4 byte chunks,
	 * this is used to store the last unaligned segment of the data.
	 */
	uint32_t unaligned_word __aligned(4);
	/* Tracks lcdic_data_fmt value we should use for pixel data */
	uint8_t pixel_fmt;
	const struct mipi_dbi_config *active_cfg;
	struct k_sem xfer_sem;
	struct k_sem lock;
#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
	struct stream dma_stream;
#endif
};

#define LCDIC_ALL_INTERRUPTS \
	(LCDIC_ICR_RFIFO_THRES_INTR_CLR_MASK | \
	LCDIC_ICR_RFIFO_UNDERFLOW_INTR_CLR_MASK | \
	LCDIC_ICR_TFIFO_THRES_INTR_CLR_MASK | \
	LCDIC_ICR_TFIFO_OVERFLOW_INTR_CLR_MASK | \
	LCDIC_ICR_TE_TO_INTR_CLR_MASK | \
	LCDIC_ICR_CMD_TO_INTR_CLR_MASK | \
	LCDIC_ICR_CMD_DONE_INTR_CLR_MASK | \
	LCDIC_ICR_RST_DONE_INTR_CLR_MASK)

/* RX and TX FIFO thresholds */
#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
#define LCDIC_RX_FIFO_THRESH 0x0
#define LCDIC_TX_FIFO_THRESH 0x0
#else
#define LCDIC_RX_FIFO_THRESH 0x0
#define LCDIC_TX_FIFO_THRESH 0x3
#endif

/* Timer0 and Timer1 bases. We choose a longer timer0 base to enable
 * long reset periods
 */
#define LCDIC_TIMER0_RATIO 0xF
#define LCDIC_TIMER1_RATIO 0x9

/* After LCDIC is enabled or disabled, there should be a wait longer than
 * 5x the module clock before other registers are read
 */
static inline void mipi_dbi_lcdic_reset_delay(void)
{
	k_busy_wait(1);
}

/* Resets state of the LCDIC TX/RX FIFO */
static inline void mipi_dbi_lcdic_reset_state(const struct device *dev)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	LCDIC_Type *base = config->base;

	base->CTRL &= ~LCDIC_CTRL_LCDIC_EN_MASK;
	mipi_dbi_lcdic_reset_delay();
	base->CTRL |= LCDIC_CTRL_LCDIC_EN_MASK;
	mipi_dbi_lcdic_reset_delay();
}


#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA

/* Start DMA to send data using LCDIC TX FIFO */
static int mipi_dbi_lcdic_start_dma(const struct device *dev)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	struct mipi_dbi_lcdic_data *data = dev->data;
	struct stream *stream = &data->dma_stream;
	uint32_t aligned_len = data->cmd_bytes & (~0x3);
	uint32_t unaligned_len = data->cmd_bytes & 0x3;
	int ret;

	stream->dma_cfg.head_block = &stream->blk_cfg[0];
	if (aligned_len == 0) {
		/* Only unaligned data exists, send it in the first block */
		/* First DMA block configuration is used to send aligned data */
		stream->blk_cfg[0].source_address = (uint32_t)&data->unaligned_word;
		stream->blk_cfg[0].dest_address = (uint32_t)&config->base->TFIFO_WDATA;
		/* Block size should be the aligned portion of the transfer */
		stream->blk_cfg[0].block_size = sizeof(uint32_t);
		stream->dma_cfg.block_count = 1;
		stream->blk_cfg[0].next_block = NULL;
	} else {
		/* First DMA block configuration is used to send aligned data */
		stream->blk_cfg[0].source_address = (uint32_t)data->xfer_buf;
		stream->blk_cfg[0].dest_address = (uint32_t)&config->base->TFIFO_WDATA;
		/* Block size should be the aligned portion of the transfer */
		stream->blk_cfg[0].block_size = aligned_len;
		/* Second DMA block configuration sends unaligned block */
		if (unaligned_len) {
			stream->dma_cfg.block_count = 2;
			stream->blk_cfg[0].next_block =
						&stream->blk_cfg[1];
			stream->blk_cfg[1].source_address =
				(uint32_t)&data->unaligned_word;
			stream->blk_cfg[1].dest_address =
				(uint32_t)&config->base->TFIFO_WDATA;
			stream->blk_cfg[1].block_size = sizeof(uint32_t);
		} else {
			stream->dma_cfg.block_count = 1;
			stream->blk_cfg[0].next_block = NULL;
		}
	}

	ret = dma_config(stream->dma_dev, stream->channel, &stream->dma_cfg);
	if (ret) {
		return ret;
	}
	/* Enable DMA channel before we set up DMA request. This way,
	 * the hardware DMA trigger does not fire until the DMA
	 * start function has initialized the DMA.
	 */
	ret = dma_start(stream->dma_dev, stream->channel);
	if (ret) {
		return ret;
	}
	/* Enable DMA request */
	config->base->CTRL |= LCDIC_CTRL_DMA_EN_MASK;
	return ret;
}

/* DMA completion callback */
static void mipi_dbi_lcdic_dma_callback(const struct device *dma_dev,
	     void *user_data, uint32_t channel, int status)
{
	if (status < 0) {
		LOG_ERR("DMA callback with error %d", status);
	}
}

#endif /* CONFIG_MIPI_DBI_NXP_LCDIC_DMA */

/* Configure LCDIC */
static int mipi_dbi_lcdic_configure(const struct device *dev,
				    const struct mipi_dbi_config  *dbi_config)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	struct mipi_dbi_lcdic_data *data = dev->data;
	const struct spi_config *spi_cfg = &dbi_config->config;
	LCDIC_Type *base = config->base;
	int ret;
	uint32_t reg;

	if (dbi_config == data->active_cfg) {
		return 0;
	}

	/* Clear all interrupt flags */
	base->ICR = LCDIC_ALL_INTERRUPTS;
	/* Mask all interrupts */
	base->IMR = LCDIC_ALL_INTERRUPTS;

	/* Set LCDIC clock frequency */
	ret = clock_control_set_rate(config->clock_dev, config->clock_subsys,
			(clock_control_subsys_rate_t)spi_cfg->frequency);
	if (ret) {
		LOG_ERR("Invalid clock frequency %d", spi_cfg->frequency);
		return ret;
	}
	if (!(spi_cfg->operation & SPI_HALF_DUPLEX)) {
		LOG_ERR("LCDIC only supports half duplex operation");
		return -ENOTSUP;
	}
	if (spi_cfg->slave != 0) {
		/* Only one slave select line */
		return -ENOTSUP;
	}
	if (SPI_WORD_SIZE_GET(spi_cfg->operation) > 8) {
		LOG_ERR("Unsupported word size");
		return -ENOTSUP;
	}

	reg = base->CTRL;
	/* Disable LCD module during configuration */
	reg &= ~LCDIC_CTRL_LCDIC_EN_MASK;
	/* Select SPI mode */
	reg &= ~LCDIC_CTRL_LCDIC_MD_MASK;
	/* Select 3 or 4 wire mode based on config selection */
	if (dbi_config->mode == MIPI_DBI_MODE_SPI_4WIRE) {
		reg |= LCDIC_CTRL_SPI_MD_MASK;
	} else {
		reg &= ~LCDIC_CTRL_SPI_MD_MASK;
	}
	/* Enable byte swapping if user requested it */
	reg = (reg & ~LCDIC_CTRL_DAT_ENDIAN_MASK) |
		LCDIC_CTRL_DAT_ENDIAN(!config->swap_bytes);
	/* Disable DMA */
	reg &= ~LCDIC_CTRL_DMA_EN_MASK;
	base->CTRL = reg;
	mipi_dbi_lcdic_reset_delay();

	/* Setup SPI CPOL and CPHA selections */
	reg = base->SPI_CTRL;
	reg = (reg & ~LCDIC_SPI_CTRL_SDAT_ENDIAN_MASK) |
		LCDIC_SPI_CTRL_SDAT_ENDIAN((spi_cfg->operation &
					    SPI_TRANSFER_LSB) ? 1 : 0);
	reg = (reg & ~LCDIC_SPI_CTRL_CPHA_MASK) |
		LCDIC_SPI_CTRL_CPHA((spi_cfg->operation & SPI_MODE_CPHA) ? 1 : 0);
	reg = (reg & ~LCDIC_SPI_CTRL_CPOL_MASK) |
		LCDIC_SPI_CTRL_CPOL((spi_cfg->operation & SPI_MODE_CPOL) ? 1 : 0);
	base->SPI_CTRL = reg;

	/* Enable the module */
	base->CTRL |= LCDIC_CTRL_LCDIC_EN_MASK;
	mipi_dbi_lcdic_reset_delay();

	data->active_cfg = dbi_config;

	return 0;
}

/* Gets unaligned word data from array. Return value will be a 4 byte
 * value containing the last unaligned section of the array data
 */
static uint32_t mipi_dbi_lcdic_get_unaligned(const uint8_t *bytes,
					     uint32_t buf_len)
{
	uint32_t word = 0U;
	uint8_t unaligned_len = buf_len & 0x3;
	uint32_t aligned_len = buf_len - unaligned_len;

	while ((unaligned_len--)) {
		word <<= 8U;
		word |= bytes[aligned_len + unaligned_len];
	}
	return word;
}

/* Fills the TX fifo with data. Returns number of bytes written. */
static int mipi_dbi_lcdic_fill_tx(LCDIC_Type *base, const uint8_t *buf,
				  uint32_t buf_len, uint32_t last_word)
{
	uint32_t *word_buf = (uint32_t *)buf;
	uint32_t bytes_written = 0U;
	uint32_t write_len;

	/* TX FIFO consumes 4 bytes on each write, so we can write up
	 * to buf_len / 4 times before we send all data.
	 * Write to FIFO it overflows or we send entire buffer.
	 */
	while (buf_len) {
		if (buf_len < 4) {
			/* Send last bytes */
			base->TFIFO_WDATA = last_word;
			write_len = buf_len;
		} else {
			/* Otherwise, write one word */
			base->TFIFO_WDATA = word_buf[bytes_written >> 2];
			write_len = 4;
		}
		if (base->IRSR & LCDIC_IRSR_TFIFO_OVERFLOW_RAW_INTR_MASK) {
			/* TX FIFO has overflowed, last word write did not
			 * complete. Return current number of bytes written.
			 */
			base->ICR |= LCDIC_ICR_TFIFO_OVERFLOW_INTR_CLR_MASK;
			return bytes_written;
		}
		bytes_written += write_len;
		buf_len -= write_len;
	}
	return bytes_written;
}

/* Writes command word */
static void mipi_dbi_lcdic_set_cmd(LCDIC_Type *base,
				   enum lcdic_cmd_type dir,
				   enum lcdic_cmd_dc dc,
				   enum lcdic_data_fmt data_fmt,
				   uint32_t buf_len)
{
	union lcdic_trx_cmd cmd = {0};


	/* TX FIFO will be clear, write command word */
	cmd.bits.data_len = buf_len - 1;
	cmd.bits.cmd_data = dc;
	cmd.bits.trx = dir;
	cmd.bits.cmd_done_int = true;
	cmd.bits.data_format = data_fmt;
	/* Write command */
	base->TFIFO_WDATA = cmd.u32;
}

static int mipi_dbi_lcdic_write_display(const struct device *dev,
					const struct mipi_dbi_config *dbi_config,
					const uint8_t *framebuf,
					struct display_buffer_descriptor *desc,
					enum display_pixel_format pixfmt)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	struct mipi_dbi_lcdic_data *dev_data = dev->data;
	LCDIC_Type *base = config->base;
	int ret;
	uint32_t interrupts = 0U;

	ret = k_sem_take(&dev_data->lock, K_FOREVER);
	if (ret) {
		goto out;
	}

	ret = mipi_dbi_lcdic_configure(dev, dbi_config);
	if (ret) {
		goto out;
	}

	/* State reset is required before transfer */
	mipi_dbi_lcdic_reset_state(dev);

	if (desc->buf_size != 0) {
		dev_data->xfer_bytes = desc->buf_size;
		/* Cap command to max transfer size */
		dev_data->cmd_bytes = MIN(desc->buf_size, LCDIC_MAX_XFER);
		dev_data->xfer_buf = framebuf;
		/* If the length of the transfer is not divisible by
		 * 4, save the unaligned portion of the transfer into
		 * a temporary buffer
		 */
		if (dev_data->cmd_bytes & 0x3) {
			dev_data->unaligned_word = mipi_dbi_lcdic_get_unaligned(
							dev_data->xfer_buf,
							dev_data->cmd_bytes);
		}

		/* Save pixel format */
		if (DISPLAY_BITS_PER_PIXEL(pixfmt) == 32) {
			dev_data->pixel_fmt = LCDIC_DATA_FMT_WORD;
		} else if (DISPLAY_BITS_PER_PIXEL(pixfmt) == 16) {
			dev_data->pixel_fmt = LCDIC_DATA_FMT_HALFWORD;
		} else if (DISPLAY_BITS_PER_PIXEL(pixfmt) == 8) {
			dev_data->pixel_fmt = LCDIC_DATA_FMT_BYTE;
		} else {
			if (config->swap_bytes) {
				LOG_WRN("Unsupported pixel format, byte swapping disabled");
			}
		}
		/* Use pixel format data width, so we can byte swap
		 * if needed
		 */
		mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA,
				       dev_data->pixel_fmt,
				       dev_data->cmd_bytes);
#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
		/* Enable command complete interrupt */
		interrupts |= LCDIC_IMR_CMD_DONE_INTR_MSK_MASK;
		/* Write interrupt mask */
		base->IMR &= ~interrupts;
		/* Configure DMA to send data */
		ret = mipi_dbi_lcdic_start_dma(dev);
		if (ret) {
			LOG_ERR("Could not start DMA (%d)", ret);
			goto out;
		}
#else
		/* Enable TX FIFO threshold interrupt. This interrupt
		 * should fire once enabled, which will kick off
		 * the transfer
		 */
		interrupts |= LCDIC_IMR_TFIFO_THRES_INTR_MSK_MASK;
		/* Enable command complete interrupt */
		interrupts |= LCDIC_IMR_CMD_DONE_INTR_MSK_MASK;
		/* Write interrupt mask */
		base->IMR &= ~interrupts;
#endif
		ret = k_sem_take(&dev_data->xfer_sem, K_FOREVER);
	}
out:
	k_sem_give(&dev_data->lock);
	return ret;

}

static int mipi_dbi_lcdic_write_cmd(const struct device *dev,
				    const struct mipi_dbi_config *dbi_config,
				    uint8_t cmd,
				    const uint8_t *data,
				    size_t data_len)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	struct mipi_dbi_lcdic_data *dev_data = dev->data;
	LCDIC_Type *base = config->base;
	int ret;
	uint32_t interrupts = 0U;

	ret = k_sem_take(&dev_data->lock, K_FOREVER);
	if (ret) {
		goto out;
	}

	ret = mipi_dbi_lcdic_configure(dev, dbi_config);
	if (ret) {
		goto out;
	}

	/* State reset is required before transfer */
	mipi_dbi_lcdic_reset_state(dev);

	/* Write command */
	mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_COMMAND,
			       LCDIC_DATA_FMT_BYTE, 1);
	/* Use standard byte writes */
	dev_data->pixel_fmt = LCDIC_DATA_FMT_BYTE;
	base->TFIFO_WDATA = cmd;
	/* Wait for command completion */
	while ((base->IRSR & LCDIC_IRSR_CMD_DONE_RAW_INTR_MASK) == 0) {
		/* Spin */
	}
	base->ICR |= LCDIC_ICR_CMD_DONE_INTR_CLR_MASK;

	if (data_len != 0) {
		dev_data->xfer_bytes = data_len;
		/* Cap command to max transfer size */
		dev_data->cmd_bytes = MIN(data_len, LCDIC_MAX_XFER);
		dev_data->xfer_buf = data;
		/* If the length of the transfer is not divisible by
		 * 4, save the unaligned portion of the transfer into
		 * a temporary buffer
		 */
		if (dev_data->cmd_bytes & 0x3) {
			dev_data->unaligned_word = mipi_dbi_lcdic_get_unaligned(
							dev_data->xfer_buf,
							dev_data->cmd_bytes);
		}
		if (cmd == MIPI_DCS_WRITE_MEMORY_START) {
			/* Use pixel format data width, so we can byte swap
			 * if needed
			 */
			mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA,
					       dev_data->pixel_fmt,
					       dev_data->cmd_bytes);
		} else {
			mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA,
					       LCDIC_DATA_FMT_BYTE,
					       dev_data->cmd_bytes);
		}
#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
		if (((((uint32_t)dev_data->xfer_buf) & 0x3) == 0) ||
		    (dev_data->cmd_bytes < 4)) {
			/* Data is aligned, we can use DMA */
			/* Enable command complete interrupt */
			interrupts |= LCDIC_IMR_CMD_DONE_INTR_MSK_MASK;
			/* Write interrupt mask */
			base->IMR &= ~interrupts;
			/* Configure DMA to send data */
			ret = mipi_dbi_lcdic_start_dma(dev);
			if (ret) {
				LOG_ERR("Could not start DMA (%d)", ret);
				goto out;
			}
		} else /* Data is not aligned */
#endif
		{
			/* Enable TX FIFO threshold interrupt. This interrupt
			 * should fire once enabled, which will kick off
			 * the transfer
			 */
			interrupts |= LCDIC_IMR_TFIFO_THRES_INTR_MSK_MASK;
			/* Enable command complete interrupt */
			interrupts |= LCDIC_IMR_CMD_DONE_INTR_MSK_MASK;
			/* Write interrupt mask */
			base->IMR &= ~interrupts;
		}
		ret = k_sem_take(&dev_data->xfer_sem, K_FOREVER);
	}
out:
	k_sem_give(&dev_data->lock);
	return ret;
}

static int mipi_dbi_lcdic_reset(const struct device *dev, uint32_t delay)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	LCDIC_Type *base = config->base;
	uint32_t lcdic_freq;
	uint8_t rst_width, pulse_cnt;

	/* Calculate delay based off timer0 ratio. Formula given
	 * by RM is as follows:
	 * Reset pulse width = (RST_WIDTH + 1) * Timer0_Period
	 * Timer0_Period = 2^(TIMER_RATIO0) / LCDIC_Clock_Freq
	 */
	if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
				   &lcdic_freq)) {
		return -EIO;
	}
	rst_width = (delay * (lcdic_freq)) /
			((1 << LCDIC_TIMER0_RATIO) * MSEC_PER_SEC);
	/* If rst_width is larger than max value supported by hardware,
	 * increase the pulse count (rounding up)
	 */
	pulse_cnt = ((rst_width + (LCDIC_MAX_RST_WIDTH - 1)) / LCDIC_MAX_RST_WIDTH);
	rst_width = MIN(LCDIC_MAX_RST_WIDTH, rst_width);

	/* Start the reset signal */
	base->RST_CTRL = LCDIC_RST_CTRL_RST_WIDTH(rst_width - 1) |
			LCDIC_RST_CTRL_RST_SEQ_NUM(pulse_cnt - 1) |
			LCDIC_RST_CTRL_RST_START_MASK;
	/* Wait for reset to complete */
	while ((base->IRSR & LCDIC_IRSR_RST_DONE_RAW_INTR_MASK) == 0) {
		/* Spin */
	}
	base->ICR |= LCDIC_ICR_RST_DONE_INTR_CLR_MASK;
	return 0;
}



/* Initializes LCDIC peripheral */
static int mipi_dbi_lcdic_init(const struct device *dev)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	struct mipi_dbi_lcdic_data *data = dev->data;
	LCDIC_Type *base = config->base;
	int ret;

	ret = clock_control_on(config->clock_dev, config->clock_subsys);
	if (ret) {
		return ret;
	}

	/* Set initial clock rate of 10 MHz */
	ret = clock_control_set_rate(config->clock_dev, config->clock_subsys,
			(clock_control_subsys_rate_t)MHZ(10));
	if (ret) {
		return ret;
	}

	ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
	if (ret) {
		return ret;
	}
	ret = k_sem_init(&data->xfer_sem, 0, 1);
	if (ret) {
		return ret;
	}
	ret = k_sem_init(&data->lock, 1, 1);
	if (ret) {
		return ret;
	}
	/* Clear all interrupt flags */
	base->ICR = LCDIC_ALL_INTERRUPTS;
	/* Mask all interrupts */
	base->IMR = LCDIC_ALL_INTERRUPTS;

	/* Enable interrupts */
	config->irq_config_func(dev);

	/* Setup RX and TX fifo thresholds */
	base->FIFO_CTRL = LCDIC_FIFO_CTRL_RFIFO_THRES(LCDIC_RX_FIFO_THRESH) |
			LCDIC_FIFO_CTRL_TFIFO_THRES(LCDIC_TX_FIFO_THRESH);
	/* Disable command timeouts */
	base->TO_CTRL &= ~(LCDIC_TO_CTRL_CMD_LONG_TO_MASK |
			LCDIC_TO_CTRL_CMD_SHORT_TO_MASK);

	/* Ensure LCDIC timer ratios are at reset values */
	base->TIMER_CTRL = LCDIC_TIMER_CTRL_TIMER_RATIO1(LCDIC_TIMER1_RATIO) |
			LCDIC_TIMER_CTRL_TIMER_RATIO0(LCDIC_TIMER0_RATIO);

#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
	/* Attach the LCDIC DMA request signal to the DMA channel we will
	 * use with hardware triggering.
	 */
	INPUTMUX_AttachSignal(INPUTMUX, data->dma_stream.channel,
			kINPUTMUX_LcdTxRegToDmaSingleToDma0);
	INPUTMUX_EnableSignal(INPUTMUX,
			kINPUTMUX_Dmac0InputTriggerLcdTxRegToDmaSingleEna, true);
#endif

	return 0;
}

static struct mipi_dbi_driver_api mipi_dbi_lcdic_driver_api = {
	.command_write = mipi_dbi_lcdic_write_cmd,
	.write_display = mipi_dbi_lcdic_write_display,
	.reset = mipi_dbi_lcdic_reset,
};

static void mipi_dbi_lcdic_isr(const struct device *dev)
{
	const struct mipi_dbi_lcdic_config *config = dev->config;
	struct mipi_dbi_lcdic_data *data = dev->data;
	LCDIC_Type *base = config->base;
	uint32_t bytes_written, isr_status;

	isr_status = base->ISR;
	/* Clear pending interrupts */
	base->ICR |= isr_status;

	if (isr_status & LCDIC_ISR_CMD_DONE_INTR_MASK) {
		if (config->base->CTRL & LCDIC_CTRL_DMA_EN_MASK) {
			/* DMA completed. Update buffer tracking data */
			data->xfer_bytes -= data->cmd_bytes;
			data->xfer_buf += data->cmd_bytes;
			/* Disable DMA request */
			config->base->CTRL &= ~LCDIC_CTRL_DMA_EN_MASK;
		}
		if (data->xfer_bytes == 0) {
			/* Disable interrupts */
			base->IMR |= LCDIC_ALL_INTERRUPTS;
			/* All data has been sent. */
			k_sem_give(&data->xfer_sem);
		} else {
			/* Command done. Queue next command */
			data->cmd_bytes = MIN(data->xfer_bytes, LCDIC_MAX_XFER);
			mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA,
					       LCDIC_DATA_FMT_BYTE,
					       data->cmd_bytes);
			if (data->cmd_bytes & 0x3) {
				/* Save unaligned portion of transfer into
				 * a temporary buffer
				 */
				data->unaligned_word = mipi_dbi_lcdic_get_unaligned(
								data->xfer_buf,
								data->cmd_bytes);
			}
#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
			if (((((uint32_t)data->xfer_buf) & 0x3) == 0) ||
			    (data->cmd_bytes < 4)) {
				/* Data is aligned. We can use DMA */
				mipi_dbi_lcdic_start_dma(dev);
			} else
#endif
			{
				/* We must refill the FIFO here in order to continue
				 * the next transfer, since the TX FIFO threshold
				 * interrupt may have already fired.
				 */
				bytes_written = mipi_dbi_lcdic_fill_tx(base, data->xfer_buf,
								       data->cmd_bytes,
								       data->unaligned_word);
				if (bytes_written > 0) {
					data->xfer_buf += bytes_written;
					data->cmd_bytes -= bytes_written;
					data->xfer_bytes -= bytes_written;
				}
			}
		}
	} else if (isr_status & LCDIC_ISR_TFIFO_THRES_INTR_MASK) {
		/* If command is not done, continue filling TX FIFO from
		 * current transfer buffer
		 */
		bytes_written = mipi_dbi_lcdic_fill_tx(base, data->xfer_buf,
						      data->cmd_bytes,
						      data->unaligned_word);
		if (bytes_written > 0) {
			data->xfer_buf += bytes_written;
			data->cmd_bytes -= bytes_written;
			data->xfer_bytes -= bytes_written;
		}
	}
}

#ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA
#define LCDIC_DMA_CHANNELS(n)						\
	.dma_stream = {							\
		.dma_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR(n)),		\
		.channel = DT_INST_DMAS_CELL_BY_IDX(n, 0, channel),	\
		.dma_cfg = {						\
			.dma_slot = LPC_DMA_HWTRIG_EN |			\
				LPC_DMA_TRIGPOL_HIGH_RISING |		\
				LPC_DMA_TRIGBURST,			\
			.channel_direction = MEMORY_TO_PERIPHERAL,	\
			.dma_callback = mipi_dbi_lcdic_dma_callback,	\
			.source_data_size = 4,				\
			.dest_data_size = 4,				\
			.user_data = (void *)DEVICE_DT_INST_GET(n),	\
		},							\
	},
#else
#define LCDIC_DMA_CHANNELS(n)
#endif


#define MIPI_DBI_LCDIC_INIT(n)						\
	static void mipi_dbi_lcdic_config_func_##n(			\
			const struct device *dev)			\
	{								\
		IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority),	\
			    mipi_dbi_lcdic_isr,				\
			    DEVICE_DT_INST_GET(n), 0);			\
									\
		irq_enable(DT_INST_IRQN(n));				\
	}								\
									\
	PINCTRL_DT_INST_DEFINE(n);					\
	static const struct mipi_dbi_lcdic_config			\
	    mipi_dbi_lcdic_config_##n = {				\
		.base = (LCDIC_Type *)DT_INST_REG_ADDR(n),		\
		.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),		\
		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)),	\
		.clock_subsys = (clock_control_subsys_t)		\
		    DT_INST_CLOCKS_CELL(n, name),			\
		.irq_config_func = mipi_dbi_lcdic_config_func_##n,	\
		.swap_bytes = DT_INST_PROP(n, nxp_swap_bytes),		\
	};								\
	static struct mipi_dbi_lcdic_data mipi_dbi_lcdic_data_##n = {	\
		LCDIC_DMA_CHANNELS(n)					\
	};								\
	DEVICE_DT_INST_DEFINE(n, mipi_dbi_lcdic_init, NULL,		\
			&mipi_dbi_lcdic_data_##n,			\
			&mipi_dbi_lcdic_config_##n,			\
			POST_KERNEL,					\
			CONFIG_MIPI_DBI_INIT_PRIORITY,			\
			&mipi_dbi_lcdic_driver_api);

DT_INST_FOREACH_STATUS_OKAY(MIPI_DBI_LCDIC_INIT)
