| /* |
| * 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) |