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>
374 lines
16 KiB
C
374 lines
16 KiB
C
/*
|
|
* Copyright 2025 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_imx_lcdifv3
|
|
|
|
#include <zephyr/drivers/display.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <fsl_lcdifv3.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/cache.h>
|
|
|
|
LOG_MODULE_REGISTER(display_mcux_lcdifv3, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
/* Required by DEVICE_MMIO_NAMED_* macros */
|
|
#define DEV_CFG(_dev) ((const struct mcux_lcdifv3_config *)(_dev)->config)
|
|
#define DEV_DATA(_dev) ((struct mcux_lcdifv3_data *)(_dev)->data)
|
|
|
|
struct mcux_lcdifv3_config {
|
|
DEVICE_MMIO_NAMED_ROM(reg_base);
|
|
|
|
const struct device *disp_pix_clk_dev;
|
|
clock_control_subsys_t disp_pix_clk_subsys;
|
|
uint32_t disp_pix_clk_rate;
|
|
const struct device *media_axi_clk_dev;
|
|
clock_control_subsys_t media_axi_clk_subsys;
|
|
uint32_t media_axi_clk_rate;
|
|
const struct device *media_apb_clk_dev;
|
|
clock_control_subsys_t media_apb_clk_subsys;
|
|
uint32_t media_apb_clk_rate;
|
|
|
|
void (*irq_config_func)(const struct device *dev);
|
|
lcdifv3_buffer_config_t buffer_config;
|
|
lcdifv3_display_config_t display_config;
|
|
enum display_pixel_format pixel_format;
|
|
uint8_t *fb_ptr;
|
|
size_t fb_bytes;
|
|
};
|
|
|
|
struct mcux_lcdifv3_data {
|
|
DEVICE_MMIO_NAMED_RAM(reg_base);
|
|
/* Pointer to active framebuffer */
|
|
const uint8_t *active_fb;
|
|
uint8_t *fb[CONFIG_MCUX_LCDIFV3_FB_NUM];
|
|
uint8_t pixel_bytes;
|
|
struct k_sem sem;
|
|
/* Tracks index of next active driver framebuffer */
|
|
uint8_t next_idx;
|
|
};
|
|
|
|
static void dump_reg(LCDIF_Type *base)
|
|
{
|
|
/* Dump LCDIF Registers */
|
|
LOG_DBG("CTRL: 0x%x", base->CTRL.RW);
|
|
LOG_DBG("DISP_PARA: 0x%x", base->DISP_PARA);
|
|
LOG_DBG("DISP_SIZE: 0x%x", base->DISP_SIZE);
|
|
LOG_DBG("HSYN_PARA: 0x%x", base->HSYN_PARA);
|
|
LOG_DBG("VSYN_PARA: 0x%x", base->VSYN_PARA);
|
|
LOG_DBG("VSYN_HSYN_WIDTH: 0x%x", base->VSYN_HSYN_WIDTH);
|
|
LOG_DBG("INT_STATUS_D0: 0x%x", base->INT_STATUS_D0);
|
|
LOG_DBG("INT_STATUS_D1: 0x%x", base->INT_STATUS_D1);
|
|
LOG_DBG("CTRLDESCL_1: 0x%x", base->CTRLDESCL_1[0]);
|
|
LOG_DBG("CTRLDESCL_3: 0x%x", base->CTRLDESCL_3[0]);
|
|
LOG_DBG("CTRLDESCL_LOW_4: 0x%x", base->CTRLDESCL_LOW_4[0]);
|
|
LOG_DBG("CTRLDESCL_HIGH_4: 0x%x", base->CTRLDESCL_HIGH_4[0]);
|
|
LOG_DBG("CTRLDESCL_5: 0x%x", base->CTRLDESCL_5[0]);
|
|
}
|
|
|
|
static int mcux_lcdifv3_write(const struct device *dev, const uint16_t x, const uint16_t y,
|
|
const struct display_buffer_descriptor *desc, const void *buf)
|
|
{
|
|
const struct mcux_lcdifv3_config *config = dev->config;
|
|
struct mcux_lcdifv3_data *data = dev->data;
|
|
LCDIF_Type *base = (LCDIF_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
|
|
uint32_t h_idx;
|
|
const uint8_t *src;
|
|
uint8_t *dst;
|
|
|
|
__ASSERT((data->pixel_bytes * desc->pitch * desc->height) <= desc->buf_size,
|
|
"Input buffer too small");
|
|
|
|
LOG_DBG("W=%d, H=%d @%d,%d", desc->width, desc->height, x, y);
|
|
|
|
if ((x == 0) && (y == 0) && (desc->width == config->display_config.panelWidth) &&
|
|
(desc->height == config->display_config.panelHeight) && (desc->pitch == desc->width)) {
|
|
/* We can use the display buffer directly, without copying */
|
|
LOG_DBG("Setting FB from %p->%p", (void *)data->active_fb, (void *)buf);
|
|
data->active_fb = buf;
|
|
} else {
|
|
/* We must use partial framebuffer copy */
|
|
if (CONFIG_MCUX_LCDIFV3_FB_NUM == 0) {
|
|
LOG_ERR("Partial display refresh requires driver framebuffers");
|
|
return -ENOTSUP;
|
|
} else if (data->active_fb != data->fb[data->next_idx]) {
|
|
/*
|
|
* Copy the entirety of the current framebuffer to new
|
|
* buffer, since we are changing the active buffer address
|
|
*/
|
|
src = data->active_fb;
|
|
dst = data->fb[data->next_idx];
|
|
memcpy(dst, src, config->fb_bytes);
|
|
}
|
|
/* Write the display update to the active framebuffer */
|
|
src = buf;
|
|
dst = data->fb[data->next_idx];
|
|
dst += data->pixel_bytes * (y * config->display_config.panelWidth + x);
|
|
|
|
for (h_idx = 0; h_idx < desc->height; h_idx++) {
|
|
memcpy(dst, src, data->pixel_bytes * desc->width);
|
|
src += data->pixel_bytes * desc->pitch;
|
|
dst += data->pixel_bytes * config->display_config.panelWidth;
|
|
}
|
|
LOG_DBG("Setting FB from %p->%p", (void *)data->active_fb,
|
|
(void *)data->fb[data->next_idx]);
|
|
/* Set new active framebuffer */
|
|
data->active_fb = data->fb[data->next_idx];
|
|
}
|
|
|
|
sys_cache_data_flush_and_invd_range((void *)data->active_fb, config->fb_bytes);
|
|
|
|
k_sem_reset(&data->sem);
|
|
|
|
/* Set new framebuffer */
|
|
LCDIFV3_SetLayerBufferAddr(base, 0, (uint32_t)(uintptr_t)data->active_fb);
|
|
LCDIFV3_TriggerLayerShadowLoad(base, 0);
|
|
|
|
#if CONFIG_MCUX_LCDIFV3_FB_NUM != 0
|
|
/* Update index of active framebuffer */
|
|
data->next_idx = (data->next_idx + 1) % CONFIG_MCUX_LCDIFV3_FB_NUM;
|
|
#endif
|
|
/* Wait for frame to complete */
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *mcux_lcdifv3_get_framebuffer(const struct device *dev)
|
|
{
|
|
struct mcux_lcdifv3_data *dev_data = dev->data;
|
|
|
|
return (void *)dev_data->active_fb;
|
|
}
|
|
|
|
static void mcux_lcdifv3_get_capabilities(const struct device *dev,
|
|
struct display_capabilities *capabilities)
|
|
{
|
|
const struct mcux_lcdifv3_config *config = dev->config;
|
|
|
|
memset(capabilities, 0, sizeof(struct display_capabilities));
|
|
capabilities->x_resolution = config->display_config.panelWidth;
|
|
capabilities->y_resolution = config->display_config.panelHeight;
|
|
capabilities->supported_pixel_formats = config->pixel_format;
|
|
capabilities->current_pixel_format = config->pixel_format;
|
|
capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
|
|
}
|
|
|
|
static void mcux_lcdifv3_isr(const struct device *dev)
|
|
{
|
|
struct mcux_lcdifv3_data *dev_data = dev->data;
|
|
uint32_t status;
|
|
LCDIF_Type *base = (LCDIF_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
|
|
|
|
status = LCDIFV3_GetInterruptStatus(base);
|
|
LCDIFV3_ClearInterruptStatus(base, status);
|
|
|
|
k_sem_give(&dev_data->sem);
|
|
}
|
|
|
|
static int mcux_lcdifv3_configure_clock(const struct device *dev)
|
|
{
|
|
const struct mcux_lcdifv3_config *config = dev->config;
|
|
|
|
clock_control_set_rate(config->disp_pix_clk_dev, config->disp_pix_clk_subsys,
|
|
(clock_control_subsys_rate_t)(uintptr_t)config->disp_pix_clk_rate);
|
|
return 0;
|
|
}
|
|
|
|
static int mcux_axi_apb_configure_clock(const struct device *dev)
|
|
{
|
|
const struct mcux_lcdifv3_config *config = dev->config;
|
|
uint32_t clk_freq;
|
|
|
|
/* configure media_axi_clk */
|
|
if (!device_is_ready(config->media_axi_clk_dev)) {
|
|
LOG_ERR("media_axi clock control device not ready");
|
|
return -ENODEV;
|
|
}
|
|
clock_control_set_rate(config->media_axi_clk_dev, config->media_axi_clk_subsys,
|
|
(clock_control_subsys_rate_t)(uintptr_t)config->media_axi_clk_rate);
|
|
if (clock_control_get_rate(config->media_axi_clk_dev, config->media_axi_clk_subsys,
|
|
&clk_freq)) {
|
|
return -EINVAL;
|
|
}
|
|
LOG_DBG("media_axi clock frequency %d", clk_freq);
|
|
|
|
/* configure media_apb_clk */
|
|
if (!device_is_ready(config->media_apb_clk_dev)) {
|
|
LOG_ERR("media_apb clock control device not ready");
|
|
return -ENODEV;
|
|
}
|
|
clock_control_set_rate(config->media_apb_clk_dev, config->media_apb_clk_subsys,
|
|
(clock_control_subsys_rate_t)(uintptr_t)config->media_apb_clk_rate);
|
|
if (clock_control_get_rate(config->media_apb_clk_dev, config->media_apb_clk_subsys,
|
|
&clk_freq)) {
|
|
return -EINVAL;
|
|
}
|
|
LOG_DBG("media_apb clock frequency %d", clk_freq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcux_lcdifv3_init(const struct device *dev)
|
|
{
|
|
const struct mcux_lcdifv3_config *config = dev->config;
|
|
struct mcux_lcdifv3_data *data = dev->data;
|
|
uint32_t clk_freq;
|
|
|
|
DEVICE_MMIO_NAMED_MAP(dev, reg_base, K_MEM_CACHE_NONE | K_MEM_DIRECT_MAP);
|
|
LCDIF_Type *base = (LCDIF_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
|
|
|
|
config->irq_config_func(dev);
|
|
|
|
for (int i = 0; i < CONFIG_MCUX_LCDIFV3_FB_NUM; i++) {
|
|
/* Record pointers to each driver framebuffer */
|
|
data->fb[i] = config->fb_ptr + (config->fb_bytes * i);
|
|
}
|
|
data->active_fb = config->fb_ptr;
|
|
|
|
k_sem_init(&data->sem, 1, 1);
|
|
|
|
/* Clear external memory, as it is uninitialized */
|
|
memset(config->fb_ptr, 0, config->fb_bytes * CONFIG_MCUX_LCDIFV3_FB_NUM);
|
|
|
|
/* configure disp_pix_clk */
|
|
if (!device_is_ready(config->disp_pix_clk_dev)) {
|
|
LOG_ERR("cam_pix clock control device not ready");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mcux_axi_apb_configure_clock(dev);
|
|
mcux_lcdifv3_configure_clock(dev);
|
|
|
|
if (clock_control_get_rate(config->disp_pix_clk_dev, config->disp_pix_clk_subsys,
|
|
&clk_freq)) {
|
|
LOG_ERR("Failed to get disp_pix_clk");
|
|
return -EINVAL;
|
|
}
|
|
LOG_INF("disp_pix clock frequency %d", clk_freq);
|
|
|
|
lcdifv3_buffer_config_t buffer_config = config->buffer_config;
|
|
lcdifv3_display_config_t display_config = config->display_config;
|
|
/* Set the Pixel format */
|
|
if (config->pixel_format == PIXEL_FORMAT_RGB_565X) {
|
|
buffer_config.pixelFormat = kLCDIFV3_PixelFormatRGB565;
|
|
} else if (config->pixel_format == PIXEL_FORMAT_RGB_888) {
|
|
buffer_config.pixelFormat = kLCDIFV3_PixelFormatRGB888;
|
|
} else if (config->pixel_format == PIXEL_FORMAT_ARGB_8888) {
|
|
buffer_config.pixelFormat = kLCDIFV3_PixelFormatARGB8888;
|
|
}
|
|
|
|
LCDIFV3_Init(base);
|
|
|
|
LCDIFV3_SetDisplayConfig(base, &display_config);
|
|
LCDIFV3_EnableDisplay(base, true);
|
|
LCDIFV3_SetLayerBufferConfig(base, 0, &buffer_config);
|
|
LCDIFV3_SetLayerSize(base, 0, display_config.panelWidth, display_config.panelHeight);
|
|
LCDIFV3_EnableLayer(base, 0, true);
|
|
LCDIFV3_EnablePlanePanic(base);
|
|
LCDIFV3_SetLayerBufferAddr(base, 0, (uint64_t)data->fb[0]);
|
|
LCDIFV3_TriggerLayerShadowLoad(base, 0);
|
|
LCDIFV3_EnableInterrupts(base, kLCDIFV3_VerticalBlankingInterrupt);
|
|
|
|
LOG_INF("%s init succeeded", dev->name);
|
|
|
|
dump_reg(base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct display_driver_api mcux_lcdifv3_api = {
|
|
.write = mcux_lcdifv3_write,
|
|
.get_framebuffer = mcux_lcdifv3_get_framebuffer,
|
|
.get_capabilities = mcux_lcdifv3_get_capabilities,
|
|
};
|
|
|
|
#define GET_PIXEL_FORMAT(id) \
|
|
((DT_INST_ENUM_IDX(id, pixel_format) == 0) \
|
|
? PIXEL_FORMAT_RGB_565X \
|
|
: ((DT_INST_ENUM_IDX(id, pixel_format) == 1) ? PIXEL_FORMAT_RGB_888 \
|
|
: PIXEL_FORMAT_ARGB_8888))
|
|
|
|
#define GET_PIXEL_BYTES(id) \
|
|
((DT_INST_ENUM_IDX(id, pixel_format) == 0) \
|
|
? 2 \
|
|
: ((DT_INST_ENUM_IDX(id, pixel_format) == 1) ? 3 : 4))
|
|
|
|
#define MCUX_LCDIFV3_FRAMEBUFFER_DECL(id) \
|
|
uint8_t __aligned(64) \
|
|
mcux_lcdifv3_frame_buffer_##id[DT_INST_PROP(id, width) * DT_INST_PROP(id, height) * \
|
|
GET_PIXEL_BYTES(id) * CONFIG_MCUX_LCDIFV3_FB_NUM]
|
|
#define MCUX_LCDIFV3_FRAMEBUFFER(id) mcux_lcdifv3_frame_buffer_##id
|
|
|
|
#define MCUX_LCDIFV3_DEVICE_INIT(id) \
|
|
static void mcux_lcdifv3_config_func_##id(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), mcux_lcdifv3_isr, \
|
|
DEVICE_DT_INST_GET(id), 0); \
|
|
irq_enable(DT_INST_IRQN(id)); \
|
|
} \
|
|
MCUX_LCDIFV3_FRAMEBUFFER_DECL(id); \
|
|
static struct mcux_lcdifv3_data mcux_lcdifv3_data_##id = { \
|
|
.next_idx = 0, \
|
|
.pixel_bytes = GET_PIXEL_BYTES(id), \
|
|
}; \
|
|
static const struct mcux_lcdifv3_config mcux_lcdifv3_config_##id = { \
|
|
DEVICE_MMIO_NAMED_ROM_INIT(reg_base, DT_DRV_INST(id)), \
|
|
.disp_pix_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_IDX(id, 0)), \
|
|
.disp_pix_clk_subsys = \
|
|
(clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_IDX(id, 0, name), \
|
|
.disp_pix_clk_rate = DT_PROP(DT_INST_CHILD(id, display_timings), clock_frequency), \
|
|
.media_axi_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_IDX(id, 1)), \
|
|
.media_axi_clk_subsys = \
|
|
(clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_IDX(id, 1, name), \
|
|
.media_axi_clk_rate = DT_INST_PROP(id, media_axi_clk_rate), \
|
|
.media_apb_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_IDX(id, 2)), \
|
|
.media_apb_clk_subsys = \
|
|
(clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_IDX(id, 2, name), \
|
|
.media_apb_clk_rate = DT_INST_PROP(id, media_apb_clk_rate), \
|
|
.irq_config_func = mcux_lcdifv3_config_func_##id, \
|
|
.buffer_config = \
|
|
{ \
|
|
.strideBytes = GET_PIXEL_BYTES(id) * DT_INST_PROP(id, width), \
|
|
}, \
|
|
.display_config = \
|
|
{ \
|
|
.panelWidth = DT_INST_PROP(id, width), \
|
|
.panelHeight = DT_INST_PROP(id, height), \
|
|
.lineOrder = kLCDIFV3_LineOrderRGBOrYUV, \
|
|
.hsw = DT_PROP(DT_INST_CHILD(id, display_timings), hsync_len), \
|
|
.hfp = DT_PROP(DT_INST_CHILD(id, display_timings), hfront_porch), \
|
|
.hbp = DT_PROP(DT_INST_CHILD(id, display_timings), hback_porch), \
|
|
.vsw = DT_PROP(DT_INST_CHILD(id, display_timings), vsync_len), \
|
|
.vfp = DT_PROP(DT_INST_CHILD(id, display_timings), vfront_porch), \
|
|
.vbp = DT_PROP(DT_INST_CHILD(id, display_timings), vback_porch), \
|
|
.polarityFlags = \
|
|
(DT_PROP(DT_INST_CHILD(id, display_timings), hsync_active) \
|
|
? kLCDIFV3_HsyncActiveLow \
|
|
: kLCDIFV3_HsyncActiveHigh) | \
|
|
(DT_PROP(DT_INST_CHILD(id, display_timings), vsync_active) \
|
|
? kLCDIFV3_VsyncActiveLow \
|
|
: kLCDIFV3_VsyncActiveHigh) | \
|
|
(DT_PROP(DT_INST_CHILD(id, display_timings), de_active) \
|
|
? kLCDIFV3_DataEnableActiveLow \
|
|
: kLCDIFV3_DataEnableActiveHigh) | \
|
|
(DT_PROP(DT_INST_CHILD(id, display_timings), \
|
|
pixelclk_active) \
|
|
? kLCDIFV3_DriveDataOnRisingClkEdge \
|
|
: kLCDIFV3_DriveDataOnFallingClkEdge), \
|
|
}, \
|
|
.pixel_format = GET_PIXEL_FORMAT(id), \
|
|
.fb_ptr = MCUX_LCDIFV3_FRAMEBUFFER(id), \
|
|
.fb_bytes = \
|
|
DT_INST_PROP(id, width) * DT_INST_PROP(id, height) * GET_PIXEL_BYTES(id), \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(id, &mcux_lcdifv3_init, NULL, &mcux_lcdifv3_data_##id, \
|
|
&mcux_lcdifv3_config_##id, POST_KERNEL, \
|
|
CONFIG_DISPLAY_INIT_PRIORITY, &mcux_lcdifv3_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(MCUX_LCDIFV3_DEVICE_INIT)
|