drivers: mipi_dbi: introduce NXP LCDIC driver
Introduce NXP LCDIC driver using MIPI DBI class. This peripheral supports 8080 and SPI 3/4 wire mode, although only SPI 4 wire support is currently implemented. The driver supports DMA and interrupt driven transfers. Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
This commit is contained in:
parent
2ec7a8df3a
commit
2206085cd8
|
@ -3,5 +3,5 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_sources_ifdef(CONFIG_MIPI_DBI_SPI mipi_dbi_spi.c)
|
||||
|
||||
zephyr_sources_ifdef(CONFIG_MIPI_DBI_SMARTBOND mipi_dbi_smartbond.c)
|
||||
zephyr_sources_ifdef(CONFIG_MIPI_DBI_NXP_LCDIC mipi_dbi_nxp_lcdic.c)
|
||||
|
|
|
@ -22,7 +22,7 @@ config MIPI_DBI_INIT_PRIORITY
|
|||
MIPI-DBI Host Controllers initialization priority.
|
||||
|
||||
source "drivers/mipi_dbi/Kconfig.spi"
|
||||
|
||||
source "drivers/mipi_dbi/Kconfig.smartbond"
|
||||
source "drivers/mipi_dbi/Kconfig.nxp_lcdic"
|
||||
|
||||
endif
|
||||
|
|
22
drivers/mipi_dbi/Kconfig.nxp_lcdic
Normal file
22
drivers/mipi_dbi/Kconfig.nxp_lcdic
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2023 NXP
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config MIPI_DBI_NXP_LCDIC
|
||||
bool "NXP MIPI DBI LCDIC driver"
|
||||
default y
|
||||
depends on DT_HAS_NXP_LCDIC_ENABLED
|
||||
depends on CLOCK_CONTROL
|
||||
select PINCTRL
|
||||
help
|
||||
Enable support for NXP SPI LCDIC display controller driver
|
||||
|
||||
if MIPI_DBI_NXP_LCDIC
|
||||
|
||||
config MIPI_DBI_NXP_LCDIC_DMA
|
||||
bool "Use DMA for transfers with LCDIC driver"
|
||||
select DMA
|
||||
help
|
||||
Use DMA for transfers when sending data with the LCDIC driver.
|
||||
Commands will still be sent in polling mode.
|
||||
|
||||
endif # MIPI_DBI_NXP_LCDIC
|
797
drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c
Normal file
797
drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c
Normal file
|
@ -0,0 +1,797 @@
|
|||
/*
|
||||
* 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)
|
Loading…
Reference in a new issue