diff --git a/components/esp_lvgl_port/CHANGELOG.md b/components/esp_lvgl_port/CHANGELOG.md index 942bc00ef..9e59ee435 100644 --- a/components/esp_lvgl_port/CHANGELOG.md +++ b/components/esp_lvgl_port/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features - Scaling feature in touch +- Added support for PPA rotation in LVGL9 (available for ESP32-P4) ## 2.5.0 diff --git a/components/esp_lvgl_port/CMakeLists.txt b/components/esp_lvgl_port/CMakeLists.txt index 94aac0418..af2915c2f 100644 --- a/components/esp_lvgl_port/CMakeLists.txt +++ b/components/esp_lvgl_port/CMakeLists.txt @@ -4,13 +4,24 @@ if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "4.4") return() endif() +set(ADD_SRCS "") +set(ADD_LIBS "") +set(PRIV_REQ "") +idf_build_get_property(target IDF_TARGET) +if(${target} STREQUAL "esp32p4") + list(APPEND ADD_SRCS "src/common/ppa/lcd_ppa.c") + list(APPEND ADD_LIBS idf::esp_driver_ppa) + list(APPEND PRIV_REQ esp_driver_ppa) +endif() + # This component uses a CMake workaround, so we can compile esp_lvgl_port for both LVGL8.x and LVGL9.x # At the time of idf_component_register() we don't know which LVGL version is used, so we only register an INTERFACE component (with no sources) # Later, when we know the LVGL version, we create another CMake library called 'lvgl_port_lib' and link it to the 'esp_lvgl_port' INTERFACE component idf_component_register( INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "priv_include" - REQUIRES "esp_lcd") + REQUIRES "esp_lcd" + PRIV_REQUIRES "${PRIV_REQ}") # Get LVGL version idf_build_get_property(build_components BUILD_COMPONENTS) @@ -39,8 +50,6 @@ endif() # Add LVGL port extensions set(PORT_PATH "src/${PORT_FOLDER}") -set(ADD_SRCS "") -set(ADD_LIBS "") idf_build_get_property(build_components BUILD_COMPONENTS) if("espressif__button" IN_LIST build_components) diff --git a/components/esp_lvgl_port/Kconfig b/components/esp_lvgl_port/Kconfig new file mode 100644 index 000000000..b54886bd1 --- /dev/null +++ b/components/esp_lvgl_port/Kconfig @@ -0,0 +1,10 @@ +menu "ESP LVGL PORT" + + config LVGL_PORT_ENABLE_PPA + depends on SOC_PPA_SUPPORTED + bool "Enable PPA for screen rotation" + default n + help + Enables using PPA for screen rotation. + +endmenu diff --git a/components/esp_lvgl_port/README.md b/components/esp_lvgl_port/README.md index 82c602832..cbdff862e 100644 --- a/components/esp_lvgl_port/README.md +++ b/components/esp_lvgl_port/README.md @@ -270,7 +270,7 @@ Display rotation can be changed at runtime. ``` > [!NOTE] -> This feature consume more RAM. +> Software rotation consumes more RAM. Software rotation uses [PPA](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/ppa.html) if available on the chip (e.g. ESP32P4). > [!NOTE] > During the hardware rotating, the component call [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) API. When using software rotation, you cannot use neither `direct_mode` nor `full_refresh` in the driver. See [LVGL documentation](https://docs.lvgl.io/8.3/porting/display.html?highlight=sw_rotate) for more info. diff --git a/components/esp_lvgl_port/idf_component.yml b/components/esp_lvgl_port/idf_component.yml index 2bf8d6ea0..d9cf83d6f 100644 --- a/components/esp_lvgl_port/idf_component.yml +++ b/components/esp_lvgl_port/idf_component.yml @@ -1,4 +1,4 @@ -version: "2.5.0" +version: "2.6.0" description: ESP LVGL port url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port dependencies: diff --git a/components/esp_lvgl_port/src/common/ppa/lcd_ppa.c b/components/esp_lvgl_port/src/common/ppa/lcd_ppa.c new file mode 100644 index 000000000..3f117d499 --- /dev/null +++ b/components/esp_lvgl_port/src/common/ppa/lcd_ppa.c @@ -0,0 +1,199 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include "esp_err.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "soc/soc_caps.h" +#include "lcd_ppa.h" + +#define PPA_LCD_ENABLE_CB 0 + +#if SOC_PPA_SUPPORTED +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +struct lvgl_port_ppa_t { + uint8_t *buffer; + uint32_t buffer_size; + ppa_client_handle_t srm_handle; + uint32_t color_type_id; +}; + +static const char *TAG = "PPA"; +/******************************************************************************* +* Function definitions +*******************************************************************************/ +#if PPA_LCD_ENABLE_CB +static bool _lvgl_port_ppa_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data); +#endif +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lvgl_port_ppa_handle_t lvgl_port_ppa_create(const lvgl_port_ppa_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + assert(cfg != NULL); + + lvgl_port_ppa_t *ppa_ctx = malloc(sizeof(lvgl_port_ppa_t)); + ESP_GOTO_ON_FALSE(ppa_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for PPA context allocation!"); + memset(ppa_ctx, 0, sizeof(lvgl_port_ppa_t)); + + uint32_t buffer_caps = 0; + if (cfg->flags.buff_dma) { + buffer_caps |= MALLOC_CAP_DMA; + } + if (cfg->flags.buff_spiram) { + buffer_caps |= MALLOC_CAP_SPIRAM; + } + if (buffer_caps == 0) { + buffer_caps |= MALLOC_CAP_DEFAULT; + } + + ppa_ctx->buffer_size = ALIGN_UP(cfg->buffer_size, CONFIG_CACHE_L2_CACHE_LINE_SIZE); + ppa_ctx->buffer = heap_caps_aligned_calloc(CONFIG_CACHE_L2_CACHE_LINE_SIZE, ppa_ctx->buffer_size, sizeof(uint8_t), buffer_caps); + assert(ppa_ctx->buffer != NULL); + + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_SRM, + }; + ESP_GOTO_ON_ERROR(ppa_register_client(&ppa_client_config, &ppa_ctx->srm_handle), err, TAG, "Error when registering PPA client!"); + +#if PPA_LCD_ENABLE_CB + ppa_event_callbacks_t ppa_cbs = { + .on_trans_done = _lvgl_port_ppa_callback, + }; + ESP_GOTO_ON_ERROR(ppa_client_register_event_callbacks(ppa_ctx->srm_handle, &ppa_cbs), err, TAG, "Error when registering PPA callbacks!"); +#endif + + ppa_ctx->color_type_id = COLOR_TYPE_ID(cfg->color_space, cfg->pixel_format); + +err: + if (ret != ESP_OK) { + if (ppa_ctx->buffer) { + free(ppa_ctx->buffer); + } + if (ppa_ctx) { + free(ppa_ctx); + } + } + + return ppa_ctx; +} + +void lvgl_port_ppa_delete(lvgl_port_ppa_handle_t handle) +{ + lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle; + assert(ppa_ctx != NULL); + + if (ppa_ctx->buffer) { + free(ppa_ctx->buffer); + } + + ppa_unregister_client(ppa_ctx->srm_handle); + + free(ppa_ctx); +} + +uint8_t *lvgl_port_ppa_get_output_buffer(lvgl_port_ppa_handle_t handle) +{ + lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle; + assert(ppa_ctx != NULL); + return ppa_ctx->buffer; +} + +esp_err_t lvgl_port_ppa_rotate(lvgl_port_ppa_handle_t handle, lvgl_port_ppa_disp_rotate_t *rotate_cfg) +{ + lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle; + assert(ppa_ctx != NULL); + assert(rotate_cfg != NULL); + const int w = rotate_cfg->area.x2 - rotate_cfg->area.x1 + 1; + const int h = rotate_cfg->area.y2 - rotate_cfg->area.y1 + 1; + + /* Set dimension by screen size and rotation */ + int out_w = w; + int out_h = h; + + int x1 = rotate_cfg->area.x1; + int x2 = rotate_cfg->area.x2; + int y1 = rotate_cfg->area.y1; + int y2 = rotate_cfg->area.y2; + + /* Rotate coordinates */ + switch (rotate_cfg->rotation) { + case PPA_SRM_ROTATION_ANGLE_0: + break; + case PPA_SRM_ROTATION_ANGLE_90: + out_w = h; + out_h = w; + x1 = rotate_cfg->area.y1; + x2 = rotate_cfg->area.y2; + y1 = rotate_cfg->disp_size.hres - rotate_cfg->area.x2 - 1; + y2 = rotate_cfg->disp_size.hres - rotate_cfg->area.x1 - 1; + break; + case PPA_SRM_ROTATION_ANGLE_180: + x1 = rotate_cfg->disp_size.hres - rotate_cfg->area.x2 - 1; + x2 = rotate_cfg->disp_size.hres - rotate_cfg->area.x1 - 1; + y1 = rotate_cfg->disp_size.vres - rotate_cfg->area.y2 - 1; + y2 = rotate_cfg->disp_size.vres - rotate_cfg->area.y1 - 1; + break; + case PPA_SRM_ROTATION_ANGLE_270: + out_w = h; + out_h = w; + x1 = rotate_cfg->disp_size.vres - rotate_cfg->area.y2 - 1; + x2 = rotate_cfg->disp_size.vres - rotate_cfg->area.y1 - 1; + y1 = rotate_cfg->area.x1; + y2 = rotate_cfg->area.x2; + break; + } + /* Return new coordinates */ + rotate_cfg->area.x1 = x1; + rotate_cfg->area.x2 = x2; + rotate_cfg->area.y1 = y1; + rotate_cfg->area.y2 = y2; + + /* Prepare Operation */ + ppa_srm_oper_config_t srm_oper_config = { + .in.buffer = rotate_cfg->in_buff, + .in.pic_w = w, + .in.pic_h = h, + .in.block_w = w, + .in.block_h = h, + .in.block_offset_x = 0, + .in.block_offset_y = 0, + .in.srm_cm = ppa_ctx->color_type_id, + + .out.buffer = ppa_ctx->buffer, + .out.buffer_size = ppa_ctx->buffer_size, + .out.pic_w = out_w, + .out.pic_h = out_h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.srm_cm = ppa_ctx->color_type_id, + + .rotation_angle = rotate_cfg->rotation, + .scale_x = 1.0, + .scale_y = 1.0, + + .byte_swap = rotate_cfg->swap_bytes, + + .mode = rotate_cfg->ppa_mode, + .user_data = rotate_cfg->user_data, + }; + + return ppa_do_scale_rotate_mirror(ppa_ctx->srm_handle, &srm_oper_config); +} + +#if PPA_LCD_ENABLE_CB +static bool _lvgl_port_ppa_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data) +{ + return false; +} +#endif + +#endif diff --git a/components/esp_lvgl_port/src/common/ppa/lcd_ppa.h b/components/esp_lvgl_port/src/common/ppa/lcd_ppa.h new file mode 100644 index 000000000..ffc6f9185 --- /dev/null +++ b/components/esp_lvgl_port/src/common/ppa/lcd_ppa.h @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief LCD PPA + */ + +#pragma once +#include "driver/ppa.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct lvgl_port_ppa_t lvgl_port_ppa_t; +typedef lvgl_port_ppa_t *lvgl_port_ppa_handle_t; + +/** + * @brief Init configuration structure + */ +typedef struct { + uint32_t buffer_size; /*!< Size of the buffer for the PPA */ + color_space_t color_space; /*!< Color space of input/output data */ + uint32_t pixel_format; /*!< Pixel format of input/output data */ + struct { + unsigned int buff_dma: 1; /*!< Allocated buffer will be DMA capable */ + unsigned int buff_spiram: 1; /*!< Allocated buffer will be in PSRAM */ + } flags; +} lvgl_port_ppa_cfg_t; + +/** + * @brief Display area structure + */ +typedef struct { + uint16_t x1; + uint16_t x2; + uint16_t y1; + uint16_t y2; +} lvgl_port_ppa_disp_area_t; + +/** + * @brief Display size structure + */ +typedef struct { + uint32_t hres; + uint32_t vres; +} lvgl_port_ppa_disp_size_t; + +/** + * @brief Rotation configuration + */ +typedef struct { + uint8_t *in_buff; /*!< Input buffer for rotation */ + lvgl_port_ppa_disp_area_t area; /*!< Coordinates of area */ + lvgl_port_ppa_disp_size_t disp_size; /*!< Display size */ + ppa_srm_rotation_angle_t rotation; /*!< Output rotation */ + ppa_trans_mode_t ppa_mode; /*!< Blocking or non-blocking mode */ + bool swap_bytes; /*!< SWAP bytes */ + void *user_data; +} lvgl_port_ppa_disp_rotate_t; + + +/** + * @brief Initialize PPA + * + * @note This function initialize PPA SRM Client and create buffer for process. + * + * @param cfg Configuration structure + * + * @return + * - PPA LCD handle + */ +lvgl_port_ppa_handle_t lvgl_port_ppa_create(const lvgl_port_ppa_cfg_t *cfg); + +/** + * @brief Remove PPA + * + * @param handle PPA LCD handle + * + * @note This function free buffer and deinitialize PPA. + */ +void lvgl_port_ppa_delete(lvgl_port_ppa_handle_t handle); + +/** + * @brief Get output buffer + * + * @param handle PPA LCD handle + * + * @note This function get allocated buffer for output of PPA operation. + */ +uint8_t *lvgl_port_ppa_get_output_buffer(lvgl_port_ppa_handle_t handle); + +/** + * @brief Do rotation + * + * @param handle PPA LCD handle + * @param rotate_cfg Rotation settings + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if memory allocation fails + */ +esp_err_t lvgl_port_ppa_rotate(lvgl_port_ppa_handle_t handle, lvgl_port_ppa_disp_rotate_t *rotate_cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c index 4fcb645f2..a5a07d7f3 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -19,6 +19,12 @@ #include "esp_lvgl_port.h" #include "esp_lvgl_port_priv.h" +#define LVGL_PORT_PPA (CONFIG_LVGL_PORT_ENABLE_PPA) + +#if LVGL_PORT_PPA +#include "../common/ppa/lcd_ppa.h" +#endif + #if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #include "esp_lcd_panel_rgb.h" #endif @@ -54,6 +60,9 @@ typedef struct { lv_display_t *disp_drv; /* LVGL display driver */ lv_display_rotation_t current_rotation; SemaphoreHandle_t trans_sem; /* Idle transfer mutex */ +#if LVGL_PORT_PPA + lvgl_port_ppa_handle_t ppa_handle; +#endif //LVGL_PORT_PPA struct { unsigned int monochrome: 1; /* True, if display is monochrome and using 1bit for 1px */ unsigned int swap_bytes: 1; /* Swap bytes in RGB656 (16-bit) before send to LCD driver */ @@ -140,6 +149,7 @@ lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, co /* Apply rotation from initial display configuration */ lvgl_port_disp_rotation_update(disp_ctx); + #else ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!"); #endif @@ -220,6 +230,11 @@ esp_err_t lvgl_port_remove_disp(lv_display_t *disp) if (disp_ctx->trans_sem) { vSemaphoreDelete(disp_ctx->trans_sem); } +#if LVGL_PORT_PPA + if (disp_ctx->ppa_handle) { + lvgl_port_ppa_delete(disp_ctx->ppa_handle); + } +#endif //LVGL_PORT_PPA free(disp_ctx); @@ -377,8 +392,30 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp /* Use SW rotation */ if (disp_cfg->flags.sw_rotate) { +#if LVGL_PORT_PPA + ESP_LOGI(TAG, "Setting PPA context for SW rotation"); + uint32_t pixel_format = COLOR_PIXEL_RGB565; + if (disp_cfg->color_format == LV_COLOR_FORMAT_RGB888) { + pixel_format = COLOR_PIXEL_RGB888; + } + + /* Create LCD PPA for rotation */ + lvgl_port_ppa_cfg_t ppa_cfg = { + .buffer_size = disp_cfg->buffer_size * color_bytes, + .color_space = COLOR_SPACE_RGB, + .pixel_format = pixel_format, + .flags = { + .buff_dma = disp_cfg->flags.buff_dma, + .buff_spiram = disp_cfg->flags.buff_spiram, + } + }; + disp_ctx->ppa_handle = lvgl_port_ppa_create(&ppa_cfg); + assert(disp_ctx->ppa_handle != NULL); +#else disp_ctx->draw_buffs[2] = heap_caps_malloc(buffer_size * color_bytes, buff_caps); ESP_GOTO_ON_FALSE(disp_ctx->draw_buffs[2], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (rotation buffer) allocation!"); + +#endif //LVGL_PORT_PPA } @@ -574,6 +611,39 @@ static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, u /* SW rotation enabled */ if (disp_ctx->flags.sw_rotate && (disp_ctx->current_rotation > LV_DISPLAY_ROTATION_0)) { +#if LVGL_PORT_PPA + if (disp_ctx->ppa_handle) { + /* Screen vertical size */ + int32_t hres = lv_display_get_horizontal_resolution(drv); + int32_t vres = lv_display_get_vertical_resolution(drv); + lvgl_port_ppa_disp_rotate_t rotate_cfg = { + .in_buff = color_map, + .area = { + .x1 = area->x1, + .x2 = area->x2, + .y1 = area->y1, + .y2 = area->y2, + }, + .disp_size = { + .hres = hres, + .vres = vres, + }, + .rotation = disp_ctx->current_rotation, + .ppa_mode = PPA_TRANS_MODE_BLOCKING, + .swap_bytes = (disp_ctx->flags.swap_bytes ? true : false), + .user_data = disp_ctx + }; + /* Do operation */ + esp_err_t err = lvgl_port_ppa_rotate(disp_ctx->ppa_handle, &rotate_cfg); + if (err == ESP_OK) { + color_map = lvgl_port_ppa_get_output_buffer(disp_ctx->ppa_handle); + offsetx1 = rotate_cfg.area.x1; + offsetx2 = rotate_cfg.area.x2; + offsety1 = rotate_cfg.area.y1; + offsety2 = rotate_cfg.area.y2; + } + } +#else /* SW rotation */ if (disp_ctx->draw_buffs[2]) { int32_t ww = lv_area_get_width(area); @@ -595,13 +665,13 @@ static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, u offsety1 = area->y1; offsety2 = area->y2; } +#endif //LVGL_PORT_PPA } if (disp_ctx->flags.swap_bytes) { size_t len = lv_area_get_size(area); lv_draw_sw_rgb565_swap(color_map, len); } - /* Transfer data in buffer for monochromatic screen */ if (disp_ctx->flags.monochrome) { _lvgl_port_transform_monochrome(drv, area, &color_map); diff --git a/examples/display_lvgl_demos/main/dispaly_lvgl_demos_main.c b/examples/display_lvgl_demos/main/dispaly_lvgl_demos_main.c index 606ba43c0..7559601d4 100644 --- a/examples/display_lvgl_demos/main/dispaly_lvgl_demos_main.c +++ b/examples/display_lvgl_demos/main/dispaly_lvgl_demos_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */