/* * Copyright 2023 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_dcnano_lcdif #include #include #include #include #include #include #ifdef CONFIG_HAS_MCUX_CACHE #include #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 DEVICE_API(display, 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)) / BITS_PER_BYTE) #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)