blob: e759f7c2b118c2587f41cb23535c7f1e73b38b96 [file] [log] [blame]
/*
* 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_BGR_565) {
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_BGR_565 \
: ((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)