/* * Copyright 2023 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_lcdic #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(mipi_dbi_lcdic, CONFIG_MIPI_DBI_LOG_LEVEL); #include 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, }; enum lcdic_cmd_te { LCDIC_TE_NO_SYNC = 0, LCDIC_TE_RISING_EDGE = 1, LCDIC_TE_FALLING_EDGE = 2, }; /* 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 /* Max reset pulse count */ #define LCDIC_MAX_RST_PULSE_COUNT 0x7 /* 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; uint8_t write_active_min; uint8_t write_inactive_min; uint8_t timer0_ratio; uint8_t timer1_ratio; }; #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; /* Tracks TE edge setting we should use for pixel data */ uint8_t te_edge; /* Are we starting a new display frame */ bool new_frame; 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 /* 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->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; if (dbi_config->mode == MIPI_DBI_MODE_8080_BUS_8_BIT) { /* Enable 8080 Mode */ reg |= LCDIC_CTRL_LCDIC_MD_MASK; } else if (dbi_config->mode == MIPI_DBI_MODE_SPI_4WIRE) { /* Select SPI 4 wire mode */ reg |= LCDIC_CTRL_SPI_MD_MASK; reg &= ~LCDIC_CTRL_LCDIC_MD_MASK; } else if (dbi_config->mode == MIPI_DBI_MODE_SPI_3WIRE) { /* Select SPI 3 wire mode */ reg &= ~(LCDIC_CTRL_LCDIC_MD_MASK | LCDIC_CTRL_SPI_MD_MASK); } else { /* Unsupported mode */ return -ENOTSUP; } /* If using SPI mode, validate that half-duplex was requested */ if ((!(reg & LCDIC_CTRL_LCDIC_MD_MASK)) && (!(spi_cfg->operation & SPI_HALF_DUPLEX))) { LOG_ERR("LCDIC only supports half duplex operation"); return -ENOTSUP; } /* 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; /* * Set 8080 control based on module properties. TRIW and TRAW are * set to their reset values */ base->I8080_CTRL1 = LCDIC_I8080_CTRL1_TRIW(0xf) | LCDIC_I8080_CTRL1_TRAW(0xf) | LCDIC_I8080_CTRL1_TWIW(config->write_inactive_min) | LCDIC_I8080_CTRL1_TWAW(config->write_active_min); /* 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, enum lcdic_cmd_te te_sync, 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; cmd.bits.te_sync_mode = te_sync; /* 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; enum lcdic_cmd_te te_sync = LCDIC_TE_NO_SYNC; 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; } if (dev_data->new_frame) { switch (dev_data->te_edge) { case MIPI_DBI_TE_RISING_EDGE: te_sync = LCDIC_TE_RISING_EDGE; break; case MIPI_DBI_TE_FALLING_EDGE: te_sync = LCDIC_TE_FALLING_EDGE; break; default: te_sync = LCDIC_TE_NO_SYNC; break; } dev_data->new_frame = false; } if (!desc->frame_incomplete) { /* Next frame will be a new one */ dev_data->new_frame = true; } /* 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, te_sync, 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, LCDIC_TE_NO_SYNC, 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); } mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA, LCDIC_DATA_FMT_BYTE, LCDIC_TE_NO_SYNC, 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, k_timeout_t delay) { const struct mipi_dbi_lcdic_config *config = dev->config; LCDIC_Type *base = config->base; uint32_t lcdic_freq; uint32_t delay_ms = k_ticks_to_ms_ceil32(delay.ticks); uint32_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_ms * (lcdic_freq)) / ((1 << config->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); if ((pulse_cnt - 1) > LCDIC_MAX_RST_PULSE_COUNT) { /* Still issue reset pulse, but warn user */ LOG_WRN("Reset pulse is too long for configured timer0 ratio"); pulse_cnt = LCDIC_MAX_RST_PULSE_COUNT + 1; } /* 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; } static int mipi_dbi_lcdic_configure_te(const struct device *dev, uint8_t edge, k_timeout_t delay) { const struct mipi_dbi_lcdic_config *config = dev->config; LCDIC_Type *base = config->base; struct mipi_dbi_lcdic_data *data = dev->data; uint32_t lcdic_freq, ttew, reg; uint32_t delay_us = k_ticks_to_us_ceil32(delay.ticks); /* Calculate delay based off timer0 ratio. Formula given * by RM is as follows: * TE delay = Timer1_Period * ttew * Timer1_Period = 2^(TIMER_RATIO1) * 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; } /* * Calculate TTEW. Done in multiple steps to avoid overflowing * the uint32_t type. Full formula is: * (lcdic_freq * delay_us) / * ((2 ^ (TIMER_RATIO1 + TIMER_RATIO0)) * USEC_PER_SEC) */ ttew = lcdic_freq / (1 << config->timer0_ratio); ttew *= delay_us; ttew /= (1 << config->timer1_ratio); ttew /= USEC_PER_SEC; /* Check to see if the delay is shorter than we can support */ if ((ttew == 0) && (delay_us != 0)) { LOG_ERR("Timer ratios too large to support this TE delay"); return -ENOTSUP; } reg = base->TE_CTRL; reg &= ~LCDIC_TE_CTRL_TTEW_MASK; reg |= LCDIC_TE_CTRL_TTEW(ttew); base->TE_CTRL = reg; data->te_edge = edge; 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(config->timer1_ratio) | LCDIC_TIMER_CTRL_TIMER_RATIO0(config->timer0_ratio); data->te_edge = MIPI_DBI_TE_NO_EDGE; #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 DEVICE_API(mipi_dbi, mipi_dbi_lcdic_driver_api) = { .command_write = mipi_dbi_lcdic_write_cmd, .write_display = mipi_dbi_lcdic_write_display, .configure_te = mipi_dbi_lcdic_configure_te, .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, data->pixel_fmt, LCDIC_TE_NO_SYNC, 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), \ .write_active_min = \ DT_INST_PROP(n, nxp_write_active_cycles), \ .write_inactive_min = \ DT_INST_PROP(n, nxp_write_inactive_cycles), \ .timer0_ratio = DT_INST_PROP(n, nxp_timer0_ratio), \ .timer1_ratio = DT_INST_PROP(n, nxp_timer1_ratio), \ }; \ 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)