diff --git a/Kconfig b/Kconfig index d4e3cf04fa..437917f3f8 100644 --- a/Kconfig +++ b/Kconfig @@ -2077,6 +2077,10 @@ menu "LVGL configuration" bool "Generic MIPI driver" default y if LV_USE_ST7735 || LV_USE_ST7789 || LV_USE_ST7796 || LV_USE_ILI9341 + config LV_USE_NXP_ELCDIF + bool "Use NXP ELCDIF to generate display" + default n + config LV_USE_RENESAS_GLCDC bool "Use Renesas GLCDC driver" default n diff --git a/docs/src/details/integration/driver/display/index.rst b/docs/src/details/integration/driver/display/index.rst index e346902536..c024d179d8 100644 --- a/docs/src/details/integration/driver/display/index.rst +++ b/docs/src/details/integration/driver/display/index.rst @@ -10,6 +10,7 @@ Display gen_mipi ili9341 lcd_stm32_guide + nxp_elcdif renesas_glcdc st_ltdc st7735 diff --git a/docs/src/details/integration/driver/display/nxp_elcdif.rst b/docs/src/details/integration/driver/display/nxp_elcdif.rst new file mode 100644 index 0000000000..438985e7f1 --- /dev/null +++ b/docs/src/details/integration/driver/display/nxp_elcdif.rst @@ -0,0 +1,66 @@ +.. _nxp_elcdif: + +============= +NXP eLCDIF +============= + +Overview +-------- +eLCDIF is a peripheral that is provided on some of the NXP devices capable to drive display panels through +the RGB interface, it supports different color depths and, on MIPI-DSI capable devices, its output ca be +directed to the MIPI display physical interface. The LVGL's NXP eLCDIF driver is responsible to bind the +NXP MCUx SDK low-level driver to the LVGL display subsystem. + + +Prerequisites +------------- + +- This driver relies on the presence of the MCUx SDK from NXP in the same project +- Activate the diver by setting :c:macro:`LV_USE_NXP_ELCDIF` to ``1`` in your *"lv_conf.h"*. + +Usage +----- + +The LVGL driver for eLCDIF assumes the platform already configured the display low-level driver, +set the pin-mux, clocks, etc. It also requires the base address of the peripheral and configuration +structure already set. + +The following code demonstrates using the diver in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` mode, +please notice in this mode of operation the application is responsible to allocate the framebuffers space +and pass them to the display, in the example below `buffer1` and `buffer2` are the current and the next +buffers that will be copied to the display screen, being swapped at each flush operation (managed +internally by the display driver). Also observe, in direct mode, each buffer should have the space at +least to hold at least the size of the screen, that is it, the heigh times the width times the bytes +for a pixel (which is application dependent or display supported), on the code below this size is represented +by `buf_size`. + +.. code-block:: c + + elcdif_rgb_mode_config_t config; + ELCDIF_RgbModeGetDefaultConfig(&config); + + lv_display_t * g_disp = lv_nxp_display_elcdif_create_direct(LCDIF, config, buffer1, buffer2, buf_size); + lv_display_set_default(g_disp); + +To use the driver in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` mode, an extra buffer must be allocated, +preferably in the fastest available memory region. + +Buffer swapping can be activated by passing a second buffer of same size instead of the :cpp:expr:`NULL` argument. +please notice in this case the `BUF_SIZE` needs to have, at least, space to hold data of 1/10 of the actual +display dimensions. + +.. code-block:: c + + #define BUF_SIZE (DISPLAY_HEIGHT * DISPLAY_WIDTH / 10 * 2) /*1/10 screen size for RGB565 format*/ + static uint8_t partial_draw_buf[BUF_SIZE]; + lv_display_t * g_disp = lv_nxp_display_elcdif_create_partial(LCDIF, config, partial_draw_buf, NULL, BUF_SIZE); + +In runtime, the event handler function from the eLCDIF driver should be called inside of the eLCDIF interrupt handler +This function is responsible for notify the LVGL display subsystem about a finished flush operation: + +.. code-block:: c + + void eLCDIF_IRQ_Handler(void) + { + lv_nxp_display_elcdif_event_handler(g_disp); + } diff --git a/lv_conf_template.h b/lv_conf_template.h index 2ed3b13d5b..45cbee518d 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -1298,6 +1298,9 @@ #define LV_ST_LTDC_USE_DMA2D_FLUSH 0 #endif +/** Driver for NXP ELCDIF */ +#define LV_USE_NXP_ELCDIF 0 + /** LVGL Windows backend */ #define LV_USE_WINDOWS 0 diff --git a/src/drivers/display/nxp_elcdif/lv_nxp_elcdif.c b/src/drivers/display/nxp_elcdif/lv_nxp_elcdif.c new file mode 100644 index 0000000000..c25b371ee4 --- /dev/null +++ b/src/drivers/display/nxp_elcdif/lv_nxp_elcdif.c @@ -0,0 +1,185 @@ +/** + * @file lv_nxp_elcdif.c + * + * Driver for NXP's ELCD + */ + +#include "lv_nxp_elcdif.h" + +#if LV_USE_NXP_ELCDIF == 1 +/********************* + * INCLUDES + *********************/ +#include "../../../display/lv_display_private.h" +#include "fsl_video_common.h" +#include "fsl_elcdif.h" +#include "fsl_cache.h" + +/********************* + * DEFINES + *********************/ +#if (defined(FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET) && (0 != FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET)) + #define ELCDIF_ADDR_IP_2_CPU(addr) (MEMORY_ConvertMemoryMapAddress((uint32_t)(addr), kMEMORY_DMA2Local)) +#else + #define ELCDIF_ADDR_IP_2_CPU(addr) (addr) +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p); +static void flush_partial_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p); +static lv_color_format_t lv_nxp_elcdif_to_lvgl_color_converter(elcdif_rgb_mode_config_t * config); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_display_t * lv_nxp_display_elcdif_create_direct(LCDIF_Type * base, const elcdif_rgb_mode_config_t * config, + void * frame_buffer1, + void * frame_buffer2, size_t buf_size) +{ + LV_ASSERT(base); + LV_ASSERT(config); + + lv_display_t * disp = lv_display_create(config->panelWidth, config->panelHeight); + LV_ASSERT(disp); + + lv_color_format_t color_format = lv_nxp_elcdif_to_lvgl_color_converter((elcdif_rgb_mode_config_t *)config); + lv_display_set_color_format(disp, color_format); + lv_display_set_buffers(disp, frame_buffer1, frame_buffer2, buf_size, LV_DISPLAY_RENDER_MODE_DIRECT); + lv_display_set_user_data(disp, base); + + ELCDIF_EnableInterrupts(base, kELCDIF_CurFrameDoneInterruptEnable); + NVIC_EnableIRQ(eLCDIF_IRQn); + + return disp; +} + +lv_display_t * lv_nxp_display_elcdif_create_partial(LCDIF_Type * base, const elcdif_rgb_mode_config_t * config, + void * frame_buffer1, + void * frame_buffer2, size_t buf_size) +{ + LV_ASSERT(base); + LV_ASSERT(config); + + /* Create a direct mode display and then update the buffers to be set in partial mode */ + lv_display_t * disp = lv_nxp_display_elcdif_create_direct(base, config, frame_buffer1, frame_buffer2, buf_size); + ELCDIF_DisableInterrupts(base, kELCDIF_CurFrameDoneInterruptEnable); + NVIC_DisableIRQ(eLCDIF_IRQn); + + lv_display_set_flush_cb(disp, flush_partial_cb); + lv_display_set_buffers(disp, frame_buffer1, frame_buffer2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL); + + ELCDIF_EnableInterrupts(base, kELCDIF_CurFrameDoneInterruptEnable); + NVIC_EnableIRQ(eLCDIF_IRQn); + + return disp; +} + +void lv_nxp_display_elcdif_event_handler(const lv_display_t * disp) +{ + if(disp == NULL) { + /* Just return since no valid display has been set yet */ + return; + } + + LCDIF_Type * base = (LCDIF_Type *)lv_display_get_user_data((lv_display_t *)disp); + uint32_t intStatus = ELCDIF_GetInterruptStatus(base); + + ELCDIF_ClearInterruptStatus(base, intStatus); + + if(intStatus & kELCDIF_CurFrameDone) { + /* flush ready is ISR safe and atomic, so calling inside of the + * framebuffer interrupt is safe and makes the flush chain + * non blocking even in bare metal systems. + */ + lv_disp_flush_ready((lv_display_t *)disp); + } + + SDK_ISR_EXIT_BARRIER; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p) +{ + LCDIF_Type * base = (LCDIF_Type *)lv_display_get_user_data(disp); + + DCACHE_CleanInvalidateByRange((uint32_t)color_p, lv_display_get_draw_buf_size(disp)); + + if(!lv_display_flush_is_last(disp)) { + lv_disp_flush_ready(disp); + return; + } + ELCDIF_SetNextBufferAddr(base, (uint32_t)color_p); +} + +static void flush_partial_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p) +{ + LCDIF_Type * base = (LCDIF_Type *)lv_display_get_user_data(disp); + + DCACHE_CleanInvalidateByRange((uint32_t)color_p, lv_display_get_draw_buf_size(disp)); + + uint8_t * fb = (uint8_t *)ELCDIF_ADDR_IP_2_CPU(base->CUR_BUF); + int32_t w = lv_area_get_width(area); + int32_t h = lv_area_get_height(area); + int32_t disp_w = lv_display_get_horizontal_resolution(disp); + int32_t disp_h = lv_display_get_vertical_resolution(disp); + int32_t bytes_per_pixel = LV_COLOR_FORMAT_GET_SIZE(lv_display_get_color_format(disp)); + int32_t i; + + fb = fb + area->y1 * disp_h; + fb = fb + area->x1; + + for(i = 0; i < h; i++) { + lv_memcpy(fb, color_p, w * bytes_per_pixel); + fb += disp_h; + color_p += w; + } +} + +static lv_color_format_t lv_nxp_elcdif_to_lvgl_color_converter(elcdif_rgb_mode_config_t * config) +{ + /*Handle color format conversion*/ + lv_color_format_t color_format; + + switch(config->pixelFormat) { + case kELCDIF_PixelFormatRAW8 : + color_format = LV_COLOR_FORMAT_L8; + break; + case kELCDIF_PixelFormatRGB565 : + color_format = LV_COLOR_FORMAT_RGB565; + break; + case kELCDIF_PixelFormatXRGB8888 : + color_format = LV_COLOR_FORMAT_XRGB8888; + break; + case kELCDIF_PixelFormatRGB888 : + color_format = LV_COLOR_FORMAT_RGB888; + break; + /* + There are some color formats in ELCDIF which LVGL does not support. + For these, use unknown format and drop a msg for the user + */ + default : + color_format = LV_COLOR_FORMAT_UNKNOWN; + LV_LOG_WARN("Not supported color format in ELCDIF. Using LV_UNKNOWN!"); + } + return color_format; +} + +#endif /*LV_USE_NXP_ELCDIF*/ diff --git a/src/drivers/display/nxp_elcdif/lv_nxp_elcdif.h b/src/drivers/display/nxp_elcdif/lv_nxp_elcdif.h new file mode 100644 index 0000000000..ac0b62fba0 --- /dev/null +++ b/src/drivers/display/nxp_elcdif/lv_nxp_elcdif.h @@ -0,0 +1,84 @@ +/** + * @file lv_nxp_elcdif.h + * Driver for NXP's ELCD + */ + +#ifndef LV_NXP_ELCDIF_DRIVER_H +#define LV_NXP_ELCDIF_DRIVER_H + +#include "../../../lvgl.h" +#include "../../../display/lv_display.h" + +#if LV_USE_NXP_ELCDIF == 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "fsl_elcdif.h" +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Attach LVGL to ELCDIF using DIRECT rendering mode. + * ELCDIF should be already initialized. + * @param base The NXP eLCD controller base address + * @param config NXP eLCD config object + * @param frame_buffer1 pointer the first frame buffers + * @param frame_buffer2 pointer the second frame buffers + * @param buf_size size of a buffer in bytes (must be at least as large as the screen) + * @return a display object initialized and registerd on the LVGL runtime + */ +lv_display_t * lv_nxp_display_elcdif_create_direct(LCDIF_Type * base, const elcdif_rgb_mode_config_t * config, + void * frame_buffer1, + void * frame_buffer2, size_t buf_size); + + +/** +* Attach LVGL to ELCDIF using PARTIAL rendering mode. +* ELCDIF should be already initialized. +* @param base The NXP eLCD controller base address +* @param config NXP eLCD config object +* @param frame_buffer1 pointer the first frame buffers +* @param frame_buffer2 pointer the second frame buffers +* @param buf_size size of a buffer in bytes +* @return a display object initialized and registerd on the LVGL runtime +*/ +lv_display_t * lv_nxp_display_elcdif_create_partial(LCDIF_Type * base, const elcdif_rgb_mode_config_t * config, + void * frame_buffer1, + void * frame_buffer2, size_t buf_size); + +/** + * Call this function on the LCD Interrupt Service Routine + * It tells to LVGL what to do when a framebuffer is transmitted + * to the LCD panel + * @param disp The display instance that contains the eLCD related data + * + * @note: the parameter disp is tipycally the return value after + * `lv_nxp_display_elcdif_create_direct` has been sucessfully executed + */ +void lv_nxp_display_elcdif_event_handler(const lv_display_t * disp); +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_USE_NXP_ELCDIF*/ + +#endif /* LV_NXP_ELCDIF_DRIVER_H */ diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 10fe6fbf8c..3d50bb18e0 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -4209,6 +4209,15 @@ #endif #endif +/** Driver for NXP ELCDIF */ +#ifndef LV_USE_NXP_ELCDIF + #ifdef CONFIG_LV_USE_NXP_ELCDIF + #define LV_USE_NXP_ELCDIF CONFIG_LV_USE_NXP_ELCDIF + #else + #define LV_USE_NXP_ELCDIF 0 + #endif +#endif + /** LVGL Windows backend */ #ifndef LV_USE_WINDOWS #ifdef CONFIG_LV_USE_WINDOWS