351 lines
11 KiB
C
351 lines
11 KiB
C
/*
|
|
* Copyright 2023 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_dcnano_lcdif
|
|
|
|
#include <zephyr/drivers/display.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
|
|
#include <fsl_lcdif.h>
|
|
#ifdef CONFIG_HAS_MCUX_CACHE
|
|
#include <fsl_cache.h>
|
|
#endif
|
|
|
|
|
|
LOG_MODULE_REGISTER(display_mcux_dcnano_lcdif, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
struct mcux_dcnano_lcdif_config {
|
|
LCDIF_Type *base;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
const struct gpio_dt_spec backlight_gpio;
|
|
lcdif_dpi_config_t dpi_config;
|
|
/* Pointer to start of first framebuffer */
|
|
uint8_t *fb_ptr;
|
|
/* Number of bytes used for each framebuffer */
|
|
uint32_t fb_bytes;
|
|
};
|
|
|
|
struct mcux_dcnano_lcdif_data {
|
|
/* Pointer to active framebuffer */
|
|
const uint8_t *active_fb;
|
|
uint8_t *fb[CONFIG_MCUX_DCNANO_LCDIF_FB_NUM];
|
|
lcdif_fb_config_t fb_config;
|
|
uint8_t pixel_bytes;
|
|
struct k_sem sem;
|
|
/* Tracks index of next active driver framebuffer */
|
|
uint8_t next_idx;
|
|
};
|
|
|
|
static int mcux_dcnano_lcdif_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_dcnano_lcdif_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_data *data = dev->data;
|
|
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->dpi_config.panelWidth) &&
|
|
(desc->height == config->dpi_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_DCNANO_LCDIF_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->dpi_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->dpi_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];
|
|
}
|
|
|
|
#if defined(CONFIG_HAS_MCUX_CACHE) && defined(CONFIG_MCUX_DCNANO_LCDIF_MAINTAIN_CACHE)
|
|
CACHE64_CleanCacheByRange((uint32_t) data->active_fb,
|
|
config->fb_bytes);
|
|
#endif
|
|
|
|
k_sem_reset(&data->sem);
|
|
|
|
/* Set new framebuffer */
|
|
LCDIF_SetFrameBufferStride(config->base, 0,
|
|
config->dpi_config.panelWidth * data->pixel_bytes);
|
|
LCDIF_SetFrameBufferAddr(config->base, 0,
|
|
(uint32_t)data->active_fb);
|
|
LCDIF_SetFrameBufferConfig(config->base, 0, &data->fb_config);
|
|
|
|
#if CONFIG_MCUX_DCNANO_LCDIF_FB_NUM != 0
|
|
/* Update index of active framebuffer */
|
|
data->next_idx = (data->next_idx + 1) % CONFIG_MCUX_DCNANO_LCDIF_FB_NUM;
|
|
#endif
|
|
/* Wait for frame to complete */
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mcux_dcnano_lcdif_get_capabilities(const struct device *dev,
|
|
struct display_capabilities *capabilities)
|
|
{
|
|
const struct mcux_dcnano_lcdif_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_data *data = dev->data;
|
|
|
|
capabilities->y_resolution = config->dpi_config.panelHeight;
|
|
capabilities->x_resolution = config->dpi_config.panelWidth;
|
|
capabilities->supported_pixel_formats =
|
|
(PIXEL_FORMAT_BGR_565 | PIXEL_FORMAT_ARGB_8888);
|
|
capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
|
|
switch (data->fb_config.format) {
|
|
case kLCDIF_PixelFormatRGB565:
|
|
/* Zephyr stores RGB565 as big endian, and LCDIF
|
|
* expects little endian. Use BGR565 format to resolve
|
|
* this.
|
|
*/
|
|
capabilities->current_pixel_format = PIXEL_FORMAT_BGR_565;
|
|
break;
|
|
case kLCDIF_PixelFormatXRGB8888:
|
|
capabilities->current_pixel_format = PIXEL_FORMAT_ARGB_8888;
|
|
break;
|
|
default:
|
|
/* Other LCDIF formats don't have a Zephyr enum yet */
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void *mcux_dcnano_lcdif_get_framebuffer(const struct device *dev)
|
|
{
|
|
struct mcux_dcnano_lcdif_data *data = dev->data;
|
|
|
|
return (void *)data->active_fb;
|
|
}
|
|
|
|
static int mcux_dcnano_lcdif_display_blanking_off(const struct device *dev)
|
|
{
|
|
const struct mcux_dcnano_lcdif_config *config = dev->config;
|
|
|
|
return gpio_pin_set_dt(&config->backlight_gpio, 1);
|
|
}
|
|
|
|
static int mcux_dcnano_lcdif_display_blanking_on(const struct device *dev)
|
|
{
|
|
const struct mcux_dcnano_lcdif_config *config = dev->config;
|
|
|
|
return gpio_pin_set_dt(&config->backlight_gpio, 0);
|
|
}
|
|
|
|
static int mcux_dcnano_lcdif_set_pixel_format(const struct device *dev,
|
|
const enum display_pixel_format
|
|
pixel_format)
|
|
{
|
|
struct mcux_dcnano_lcdif_data *data = dev->data;
|
|
|
|
switch (pixel_format) {
|
|
case PIXEL_FORMAT_BGR_565:
|
|
/* Zephyr stores RGB565 as big endian, and LCDIF
|
|
* expects little endian. Use BGR565 format to resolve
|
|
* this.
|
|
*/
|
|
data->fb_config.format = kLCDIF_PixelFormatRGB565;
|
|
data->pixel_bytes = 2;
|
|
break;
|
|
case PIXEL_FORMAT_ARGB_8888:
|
|
data->fb_config.format = kLCDIF_PixelFormatXRGB8888;
|
|
data->pixel_bytes = 4;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mcux_dcnano_lcdif_isr(const struct device *dev)
|
|
{
|
|
const struct mcux_dcnano_lcdif_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_data *data = dev->data;
|
|
uint32_t status;
|
|
|
|
status = LCDIF_GetAndClearInterruptPendingFlags(config->base);
|
|
|
|
if (0 != (status & kLCDIF_Display0FrameDoneInterrupt)) {
|
|
k_sem_give(&data->sem);
|
|
}
|
|
}
|
|
|
|
static int mcux_dcnano_lcdif_init(const struct device *dev)
|
|
{
|
|
const struct mcux_dcnano_lcdif_config *config = dev->config;
|
|
struct mcux_dcnano_lcdif_data *data = dev->data;
|
|
int ret;
|
|
|
|
ret = gpio_pin_configure_dt(&config->backlight_gpio, GPIO_OUTPUT_ACTIVE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Convert pixel format from devicetree to the format used by HAL */
|
|
ret = mcux_dcnano_lcdif_set_pixel_format(dev, data->fb_config.format);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
LCDIF_Init(config->base);
|
|
|
|
LCDIF_DpiModeSetConfig(config->base, 0, &config->dpi_config);
|
|
|
|
LCDIF_EnableInterrupts(config->base, kLCDIF_Display0FrameDoneInterrupt);
|
|
config->irq_config_func(dev);
|
|
|
|
for (int i = 0; i < CONFIG_MCUX_DCNANO_LCDIF_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);
|
|
|
|
#ifdef CONFIG_MCUX_DCNANO_LCDIF_EXTERNAL_FB_MEM
|
|
/* Clear external memory, as it is uninitialized */
|
|
memset(config->fb_ptr, 0, config->fb_bytes * CONFIG_MCUX_DCNANO_LCDIF_FB_NUM);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct display_driver_api mcux_dcnano_lcdif_api = {
|
|
.blanking_on = mcux_dcnano_lcdif_display_blanking_on,
|
|
.blanking_off = mcux_dcnano_lcdif_display_blanking_off,
|
|
.set_pixel_format = mcux_dcnano_lcdif_set_pixel_format,
|
|
.write = mcux_dcnano_lcdif_write,
|
|
.get_capabilities = mcux_dcnano_lcdif_get_capabilities,
|
|
.get_framebuffer = mcux_dcnano_lcdif_get_framebuffer,
|
|
};
|
|
|
|
#define MCUX_DCNANO_LCDIF_PIXEL_BYTES(n) \
|
|
(DISPLAY_BITS_PER_PIXEL(DT_INST_PROP(n, pixel_format)) / 8)
|
|
#define MCUX_DCNANO_LCDIF_FB_SIZE(n) DT_INST_PROP(n, width) * \
|
|
DT_INST_PROP(n, height) * MCUX_DCNANO_LCDIF_PIXEL_BYTES(n)
|
|
|
|
/* When using external framebuffer mem, we should not allocate framebuffers
|
|
* in SRAM. Instead, we use external framebuffer address and size
|
|
* from devicetree.
|
|
*/
|
|
#ifdef CONFIG_MCUX_DCNANO_LCDIF_EXTERNAL_FB_MEM
|
|
#define MCUX_DCNANO_LCDIF_FRAMEBUFFER_DECL(n)
|
|
#define MCUX_DCNANO_LCDIF_FRAMEBUFFER(n) \
|
|
(uint8_t *)CONFIG_MCUX_DCNANO_LCDIF_EXTERNAL_FB_ADDR
|
|
#else
|
|
#define MCUX_DCNANO_LCDIF_FRAMEBUFFER_DECL(n) uint8_t __aligned(LCDIF_FB_ALIGN) \
|
|
mcux_dcnano_lcdif_frame_buffer_##n[DT_INST_PROP(n, width) * \
|
|
DT_INST_PROP(n, height) * \
|
|
MCUX_DCNANO_LCDIF_PIXEL_BYTES(n) * \
|
|
CONFIG_MCUX_DCNANO_LCDIF_FB_NUM]
|
|
#define MCUX_DCNANO_LCDIF_FRAMEBUFFER(n) mcux_dcnano_lcdif_frame_buffer_##n
|
|
#endif
|
|
|
|
#define MCUX_DCNANO_LCDIF_DEVICE_INIT(n) \
|
|
static void mcux_dcnano_lcdif_config_func_##n(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), \
|
|
DT_INST_IRQ(n, priority), \
|
|
mcux_dcnano_lcdif_isr, \
|
|
DEVICE_DT_INST_GET(n), \
|
|
0); \
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
} \
|
|
MCUX_DCNANO_LCDIF_FRAMEBUFFER_DECL(n); \
|
|
struct mcux_dcnano_lcdif_data mcux_dcnano_lcdif_data_##n = { \
|
|
.fb_config = { \
|
|
.enable = true, \
|
|
.enableGamma = false, \
|
|
.format = DT_INST_PROP(n, pixel_format), \
|
|
}, \
|
|
.next_idx = 0, \
|
|
.pixel_bytes = MCUX_DCNANO_LCDIF_PIXEL_BYTES(n), \
|
|
}; \
|
|
struct mcux_dcnano_lcdif_config mcux_dcnano_lcdif_config_##n = { \
|
|
.base = (LCDIF_Type *) DT_INST_REG_ADDR(n), \
|
|
.irq_config_func = mcux_dcnano_lcdif_config_func_##n, \
|
|
.backlight_gpio = GPIO_DT_SPEC_INST_GET(n, backlight_gpios), \
|
|
.dpi_config = { \
|
|
.panelWidth = DT_INST_PROP(n, width), \
|
|
.panelHeight = DT_INST_PROP(n, height), \
|
|
.hsw = DT_PROP(DT_INST_CHILD(n, display_timings), \
|
|
hsync_len), \
|
|
.hfp = DT_PROP(DT_INST_CHILD(n, display_timings), \
|
|
hfront_porch), \
|
|
.hbp = DT_PROP(DT_INST_CHILD(n, display_timings), \
|
|
hback_porch), \
|
|
.vsw = DT_PROP(DT_INST_CHILD(n, display_timings), \
|
|
vsync_len), \
|
|
.vfp = DT_PROP(DT_INST_CHILD(n, display_timings), \
|
|
vfront_porch), \
|
|
.vbp = DT_PROP(DT_INST_CHILD(n, display_timings), \
|
|
vback_porch), \
|
|
.polarityFlags = (DT_PROP(DT_INST_CHILD(n, \
|
|
display_timings), de_active) ? \
|
|
kLCDIF_DataEnableActiveHigh : \
|
|
kLCDIF_DataEnableActiveLow) | \
|
|
(DT_PROP(DT_INST_CHILD(n, \
|
|
display_timings), pixelclk_active) ? \
|
|
kLCDIF_DriveDataOnRisingClkEdge : \
|
|
kLCDIF_DriveDataOnFallingClkEdge) | \
|
|
(DT_PROP(DT_INST_CHILD(n, \
|
|
display_timings), hsync_active) ? \
|
|
kLCDIF_HsyncActiveHigh : \
|
|
kLCDIF_HsyncActiveLow) | \
|
|
(DT_PROP(DT_INST_CHILD(n, \
|
|
display_timings), vsync_active) ? \
|
|
kLCDIF_VsyncActiveHigh : \
|
|
kLCDIF_VsyncActiveLow), \
|
|
.format = DT_INST_ENUM_IDX(n, data_bus_width), \
|
|
}, \
|
|
.fb_ptr = MCUX_DCNANO_LCDIF_FRAMEBUFFER(n), \
|
|
.fb_bytes = MCUX_DCNANO_LCDIF_FB_SIZE(n), \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(n, \
|
|
&mcux_dcnano_lcdif_init, \
|
|
NULL, \
|
|
&mcux_dcnano_lcdif_data_##n, \
|
|
&mcux_dcnano_lcdif_config_##n, \
|
|
POST_KERNEL, \
|
|
CONFIG_DISPLAY_INIT_PRIORITY, \
|
|
&mcux_dcnano_lcdif_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(MCUX_DCNANO_LCDIF_DEVICE_INIT)
|