The format currently expected by devices and sample display
application for BGR_565 is actually RGB_565 format with bytes
swapped (not B / R swapped). That is:
PIXEL_FORMAT_RGB_565:
* @code{.unparsed}
* 7......0 15.....8
* | gggBbbbb RrrrrGgg | ...
* @endcode
current PIXEL_FORMAT_BGR_565:
* @code{.unparsed}
* 7......0 15.....8
* | RrrrrGgg gggBbbbb | ...
* @endcode
This is explained in both st7796s display driver but also is
what is generated by the sample display application. As a video
format (ex: V4L2), such format is not mentioned as BGR (for
which R and B are swapped) but RGB_565X.
Within the whole Zephyr tree, rename the curremt BGR_565 format
into RGB_565X in order to emphasis that this is a byte swapped
format rather than a B/G component swapped format.
This also correct the description of the format in display.h
file, which wasn't correct based on what was being used by
display driver or sample display app.
Signed-off-by: Alain Volmat <alain.volmat@foss.st.com>
383 lines
11 KiB
C
383 lines
11 KiB
C
/*
|
|
* Copyright 2025 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_mipi_dbi_dcnano_lcdif
|
|
|
|
#include <zephyr/drivers/display.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/drivers/mipi_dbi.h>
|
|
#include <zephyr/dt-bindings/mipi_dbi/mipi_dbi.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <fsl_lcdif.h>
|
|
|
|
LOG_MODULE_REGISTER(mipi_dbi_nxp_dcnano_lcdif, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
struct mcux_dcnano_lcdif_dbi_data {
|
|
struct k_sem transfer_done;
|
|
const struct mipi_dbi_config *active_cfg;
|
|
};
|
|
|
|
struct mcux_dcnano_lcdif_dbi_config {
|
|
LCDIF_Type *base;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
lcdif_dbi_config_t dbi_config;
|
|
lcdif_panel_config_t panel_config;
|
|
const struct pinctrl_dev_config *pincfg;
|
|
const struct gpio_dt_spec reset;
|
|
};
|
|
|
|
struct mcux_dcnano_lcdif_dbi_foramt_map_t {
|
|
uint8_t bus_type;
|
|
uint8_t color_coding;
|
|
lcdif_dbi_out_format_t format;
|
|
};
|
|
|
|
const struct mcux_dcnano_lcdif_dbi_foramt_map_t format_map[] = {
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB332, kLCDIF_DbiOutD8RGB332},
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB444, kLCDIF_DbiOutD8RGB444},
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB565, kLCDIF_DbiOutD8RGB565},
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB666_1, kLCDIF_DbiOutD8RGB666},
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB888_1, kLCDIF_DbiOutD8RGB888},
|
|
{MIPI_DBI_MODE_6800_BUS_9_BIT, MIPI_DBI_MODE_RGB666_1, kLCDIF_DbiOutD9RGB666},
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB666_2, kLCDIF_DbiOutD8RGB666},
|
|
{MIPI_DBI_MODE_6800_BUS_8_BIT, MIPI_DBI_MODE_RGB888_2, kLCDIF_DbiOutD8RGB888},
|
|
{MIPI_DBI_MODE_6800_BUS_9_BIT, MIPI_DBI_MODE_RGB666_2, kLCDIF_DbiOutD9RGB666},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB332, kLCDIF_DbiOutD16RGB332},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB444, kLCDIF_DbiOutD16RGB444},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB565, kLCDIF_DbiOutD16RGB565},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB666_1,
|
|
kLCDIF_DbiOutD16RGB666Option1},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB666_2,
|
|
kLCDIF_DbiOutD16RGB666Option2},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB888_1,
|
|
kLCDIF_DbiOutD16RGB888Option1},
|
|
{MIPI_DBI_MODE_6800_BUS_16_BIT, MIPI_DBI_MODE_RGB888_2,
|
|
kLCDIF_DbiOutD16RGB888Option2},
|
|
};
|
|
|
|
static int mcux_dcnano_lcdif_dbi_get_format(uint8_t bus_type, uint8_t color_coding,
|
|
lcdif_dbi_out_format_t *format)
|
|
{
|
|
for (uint8_t i = 0; i < ARRAY_SIZE(format_map); i++) {
|
|
if ((format_map[i].bus_type == (bus_type - MIPI_DBI_MODE_6800_BUS_16_BIT)) &&
|
|
(format_map[i].color_coding == color_coding)) {
|
|
*format = format_map[i].format;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void mcux_dcnano_lcdif_dbi_isr(const struct device *dev)
|
|
{
|
|
const struct mcux_dcnano_lcdif_dbi_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_dbi_data *lcdif_data = dev->data;
|
|
uint32_t status;
|
|
|
|
status = LCDIF_GetAndClearInterruptPendingFlags(config->base);
|
|
|
|
if (0 != (status & kLCDIF_Display0FrameDoneInterrupt)) {
|
|
k_sem_give(&lcdif_data->transfer_done);
|
|
}
|
|
}
|
|
|
|
static int mcux_dcnano_lcdif_dbi_configure(const struct device *dev,
|
|
const struct mipi_dbi_config *dbi_config)
|
|
{
|
|
const struct mcux_dcnano_lcdif_dbi_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_dbi_data *lcdif_data = dev->data;
|
|
uint8_t bus_type = dbi_config->mode & 0xFU;
|
|
uint8_t color_coding = dbi_config->mode & 0xF0U;
|
|
lcdif_dbi_config_t lcdif_dbi_config = config->dbi_config;
|
|
status_t status;
|
|
|
|
/* No need to update if configuration is the same. */
|
|
if (dbi_config == lcdif_data->active_cfg) {
|
|
return 0;
|
|
}
|
|
|
|
/* SPI mode is not supported by the SDK LCDIF driver */
|
|
if ((bus_type == MIPI_DBI_MODE_SPI_3WIRE) ||
|
|
(bus_type == MIPI_DBI_MODE_SPI_4WIRE)) {
|
|
LOG_ERR("Bus type not supported.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 9-bit bus only has RGB666 color coding. */
|
|
if (((bus_type == MIPI_DBI_MODE_6800_BUS_9_BIT) ||
|
|
(bus_type == MIPI_DBI_MODE_8080_BUS_9_BIT)) &&
|
|
((color_coding != MIPI_DBI_MODE_RGB666_1) &&
|
|
(color_coding != MIPI_DBI_MODE_RGB666_2))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get the bus type */
|
|
switch (bus_type) {
|
|
case MIPI_DBI_MODE_6800_BUS_16_BIT:
|
|
case MIPI_DBI_MODE_6800_BUS_9_BIT:
|
|
case MIPI_DBI_MODE_6800_BUS_8_BIT:
|
|
lcdif_dbi_config.type = kLCDIF_DbiTypeA_FixedE;
|
|
break;
|
|
case MIPI_DBI_MODE_8080_BUS_16_BIT:
|
|
case MIPI_DBI_MODE_8080_BUS_9_BIT:
|
|
case MIPI_DBI_MODE_8080_BUS_8_BIT:
|
|
lcdif_dbi_config.type = kLCDIF_DbiTypeB;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get the color cosing */
|
|
status = mcux_dcnano_lcdif_dbi_get_format(bus_type, color_coding,
|
|
&lcdif_dbi_config.format);
|
|
|
|
if (kStatus_Success != status) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Update DBI configuration. */
|
|
status = LCDIF_DbiModeSetConfig(config->base, 0, &lcdif_dbi_config);
|
|
|
|
if (kStatus_Success != status) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
lcdif_data->active_cfg = dbi_config;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcux_dcnano_lcdif_dbi_init(const struct device *dev)
|
|
{
|
|
const struct mcux_dcnano_lcdif_dbi_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_dbi_data *lcdif_data = dev->data;
|
|
|
|
#ifndef CONFIG_MIPI_DSI_MCUX_NXP_DCNANO_LCDIF
|
|
int ret;
|
|
|
|
/* Pin control is not applied when DCNano is used in MCUX DSI driver. */
|
|
ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
LCDIF_Init(config->base);
|
|
|
|
LCDIF_DbiModeSetConfig(config->base, 0, &config->dbi_config);
|
|
|
|
LCDIF_SetPanelConfig(config->base, 0, &config->panel_config);
|
|
|
|
LCDIF_EnableInterrupts(config->base, kLCDIF_Display0FrameDoneInterrupt);
|
|
|
|
config->irq_config_func(dev);
|
|
|
|
k_sem_init(&lcdif_data->transfer_done, 0, 1);
|
|
|
|
LOG_DBG("%s device init complete", dev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mipi_dbi_dcnano_lcdif_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 mcux_dcnano_lcdif_dbi_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_dbi_data *lcdif_data = dev->data;
|
|
int ret = 0;
|
|
uint8_t bytes_per_pixel = 0U;
|
|
|
|
/* The DBI bus type and color coding. */
|
|
ret = mcux_dcnano_lcdif_dbi_configure(dev, dbi_config);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
lcdif_fb_config_t fbConfig;
|
|
|
|
LCDIF_FrameBufferGetDefaultConfig(&fbConfig);
|
|
|
|
fbConfig.enable = true;
|
|
fbConfig.inOrder = kLCDIF_PixelInputOrderARGB;
|
|
fbConfig.rotateFlipMode = kLCDIF_Rotate0;
|
|
switch (pixfmt) {
|
|
case PIXEL_FORMAT_RGB_888:
|
|
fbConfig.format = kLCDIF_PixelFormatRGB888;
|
|
bytes_per_pixel = 3U;
|
|
break;
|
|
case PIXEL_FORMAT_ARGB_8888:
|
|
fbConfig.format = kLCDIF_PixelFormatARGB8888;
|
|
bytes_per_pixel = 4U;
|
|
break;
|
|
case PIXEL_FORMAT_RGB_565X:
|
|
fbConfig.inOrder = kLCDIF_PixelInputOrderABGR;
|
|
case PIXEL_FORMAT_RGB_565:
|
|
fbConfig.format = kLCDIF_PixelFormatRGB565;
|
|
bytes_per_pixel = 2U;
|
|
break;
|
|
default:
|
|
LOG_ERR("Bus type not supported.");
|
|
ret = -ENODEV;
|
|
break;
|
|
}
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
fbConfig.alpha.enable = false;
|
|
fbConfig.colorkey.enable = false;
|
|
fbConfig.topLeftX = 0U;
|
|
fbConfig.topLeftY = 0U;
|
|
fbConfig.width = desc->width;
|
|
fbConfig.height = desc->height;
|
|
|
|
LCDIF_SetFrameBufferConfig(config->base, 0, &fbConfig);
|
|
|
|
if (bytes_per_pixel == 3U) {
|
|
/* For RGB888 the stride shall be calculated as
|
|
* 4 bytes per pixel instead of 3.
|
|
*/
|
|
LCDIF_SetFrameBufferStride(config->base, 0, 4U * desc->pitch);
|
|
} else {
|
|
LCDIF_SetFrameBufferStride(config->base, 0, bytes_per_pixel * desc->pitch);
|
|
}
|
|
|
|
/* Set the updated area's size according to desc. */
|
|
LCDIF_SetFrameBufferPosition(config->base, 0U, 0U, 0U, desc->width,
|
|
desc->height);
|
|
|
|
LCDIF_DbiSelectArea(config->base, 0, 0, 0, desc->width - 1U,
|
|
desc->height - 1U, false);
|
|
|
|
LCDIF_SetFrameBufferAddr(config->base, 0, (uint32_t)framebuf);
|
|
|
|
/* Enable DMA and send out data. */
|
|
LCDIF_DbiWriteMem(config->base, 0);
|
|
|
|
/* Wait for transfer done. */
|
|
k_sem_take(&lcdif_data->transfer_done, K_FOREVER);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mipi_dbi_dcnano_lcdif_command_write(const struct device *dev,
|
|
const struct mipi_dbi_config *dbi_config,
|
|
uint8_t cmd, const uint8_t *data_buf,
|
|
size_t len)
|
|
{
|
|
const struct mcux_dcnano_lcdif_dbi_config *config = dev->config;
|
|
int ret = 0;
|
|
|
|
/* The DBI bus type and color coding. */
|
|
ret = mcux_dcnano_lcdif_dbi_configure(dev, dbi_config);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
LCDIF_DbiSendCommand(config->base, 0U, cmd);
|
|
|
|
if (len != 0U) {
|
|
LCDIF_DbiSendData(config->base, 0U, data_buf, len);
|
|
}
|
|
|
|
return kStatus_Success;
|
|
}
|
|
|
|
static int mipi_dbi_dcnano_lcdif_reset(const struct device *dev, k_timeout_t delay)
|
|
{
|
|
int ret;
|
|
|
|
const struct mcux_dcnano_lcdif_dbi_config *config = dev->config;
|
|
|
|
/* Check if a reset port is provided to reset the LCD controller */
|
|
if (config->reset.port == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reset the LCD controller. */
|
|
ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_HIGH);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_pin_set_dt(&config->reset, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
k_sleep(delay);
|
|
|
|
ret = gpio_pin_set_dt(&config->reset, 1);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
LOG_DBG("%s device reset complete", dev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DEVICE_API(mipi_dbi, mcux_dcnano_lcdif_dbi_api) = {
|
|
.reset = mipi_dbi_dcnano_lcdif_reset,
|
|
.command_write = mipi_dbi_dcnano_lcdif_command_write,
|
|
.write_display = mipi_dbi_dcnano_lcdif_write_display,
|
|
};
|
|
|
|
#define MCUX_DCNANO_LCDIF_DEVICE_INIT(n) \
|
|
static void mcux_dcnano_lcdif_dbi_config_func_##n(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), \
|
|
DT_INST_IRQ(n, priority), \
|
|
mcux_dcnano_lcdif_dbi_isr, \
|
|
DEVICE_DT_INST_GET(n), \
|
|
0); \
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
} \
|
|
PINCTRL_DT_INST_DEFINE(n); \
|
|
struct mcux_dcnano_lcdif_dbi_data mcux_dcnano_lcdif_dbi_data_##n; \
|
|
struct mcux_dcnano_lcdif_dbi_config mcux_dcnano_lcdif_dbi_config_##n = { \
|
|
.base = (LCDIF_Type *) DT_INST_REG_ADDR(n), \
|
|
.irq_config_func = mcux_dcnano_lcdif_dbi_config_func_##n, \
|
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
|
|
.reset = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \
|
|
.dbi_config = { \
|
|
.type = kLCDIF_DbiTypeA_FixedE, \
|
|
.swizzle = DT_INST_ENUM_IDX_OR(n, swizzle, 0), \
|
|
.format = kLCDIF_DbiOutD8RGB332, \
|
|
.acTimeUnit = DT_INST_PROP_OR(n, divider, 1) - 1, \
|
|
.writeWRPeriod = DT_INST_PROP(n, wr_period), \
|
|
.writeWRAssert = DT_INST_PROP(n, wr_assert), \
|
|
.writeWRDeassert = DT_INST_PROP(n, wr_deassert), \
|
|
.writeCSAssert = DT_INST_PROP(n, cs_assert), \
|
|
.writeCSDeassert = DT_INST_PROP(n, cs_deassert), \
|
|
}, \
|
|
.panel_config = { \
|
|
.enable = true, \
|
|
.enableGamma = false, \
|
|
.order = kLCDIF_VideoOverlay0Overlay1, \
|
|
.endian = DT_INST_ENUM_IDX_OR(n, endian, 0), \
|
|
}, \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(n, \
|
|
&mcux_dcnano_lcdif_dbi_init, \
|
|
NULL, \
|
|
&mcux_dcnano_lcdif_dbi_data_##n, \
|
|
&mcux_dcnano_lcdif_dbi_config_##n, \
|
|
POST_KERNEL, \
|
|
CONFIG_MIPI_DBI_INIT_PRIORITY, \
|
|
&mcux_dcnano_lcdif_dbi_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(MCUX_DCNANO_LCDIF_DEVICE_INIT)
|