diff --git a/components/esp_hw_support/sleep_system_peripheral.c b/components/esp_hw_support/sleep_system_peripheral.c index 286659d00ce..e2b6748f8cc 100644 --- a/components/esp_hw_support/sleep_system_peripheral.c +++ b/components/esp_hw_support/sleep_system_peripheral.c @@ -294,6 +294,10 @@ bool peripheral_domain_pd_allowed(void) mask.bitmap[SLEEP_RETENTION_MODULE_MCPWM0 >> 5] |= BIT(SLEEP_RETENTION_MODULE_MCPWM0 % 32); #endif +#if SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + mask.bitmap[SLEEP_RETENTION_MODULE_LCDCAM >> 5] |= BIT(SLEEP_RETENTION_MODULE_LCDCAM % 32); +#endif + const sleep_retention_module_bitmap_t peripheral_domain_inited_modules = sleep_retention_module_bitmap_and(inited_modules, mask); const sleep_retention_module_bitmap_t peripheral_domain_created_modules = sleep_retention_module_bitmap_and(created_modules, mask); return sleep_retention_module_bitmap_eq(peripheral_domain_inited_modules, peripheral_domain_created_modules); diff --git a/components/esp_lcd/i80/esp_lcd_panel_io_i80.c b/components/esp_lcd/i80/esp_lcd_panel_io_i80.c index f004feb49a0..519282ddbe4 100644 --- a/components/esp_lcd/i80/esp_lcd_panel_io_i80.c +++ b/components/esp_lcd/i80/esp_lcd_panel_io_i80.c @@ -43,6 +43,10 @@ #include "hal/lcd_hal.h" #include "hal/cache_ll.h" #include "hal/cache_hal.h" +#include "esp_private/sleep_retention.h" + +// Use retention link only when the target supports sleep retention is enabled +#define I80_USE_RETENTION_LINK (SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP) #if defined(SOC_GDMA_TRIG_PERIPH_LCD0_BUS) && (SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AHB) #define LCD_GDMA_NEW_CHANNEL gdma_new_ahb_channel @@ -66,6 +70,11 @@ typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t; typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t; typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t; +#if I80_USE_RETENTION_LINK +static esp_err_t lcd_i80_create_sleep_retention_link_cb(void *arg); +static void lcd_i80_create_retention_module(esp_lcd_i80_bus_t *bus); +#endif // I80_USE_RETENTION_LINK + static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io); @@ -153,6 +162,10 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc // although LCD_CAM can support up to 24 data lines, we restrict users to only use 8 or 16 bit width ESP_RETURN_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG, TAG, "invalid bus width:%d", bus_config->bus_width); +#if !SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + ESP_RETURN_ON_FALSE(bus_config->flags.allow_pd == 0, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported"); +#endif // SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + // allocate i80 bus memory bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t), LCD_I80_MEM_ALLOC_CAPS); ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus"); @@ -179,6 +192,26 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc lcd_ll_reset_register(bus_id); } } +#if I80_USE_RETENTION_LINK + // no need to acquire mutex, because the bus is exclusive + sleep_retention_module_t module_id = soc_i80_lcd_retention_info[bus_id].retention_module; + sleep_retention_module_init_param_t init_param = { + .cbs = { + .create = { + .handle = lcd_i80_create_sleep_retention_link_cb, + .arg = bus, + }, + }, + .depends = RETENTION_MODULE_BITMAP_INIT(CLOCK_SYSTEM) + }; + if (sleep_retention_module_init(module_id, &init_param) != ESP_OK) { + // even though the sleep retention module init failed, LCD driver should still work, so just warning here + ESP_LOGW(TAG, "init sleep retention failed, power domain may be turned off during sleep"); + } + if (bus_config->flags.allow_pd) { + lcd_i80_create_retention_module(bus); + } +#endif // I80_USE_RETENTION_LINK // initialize HAL layer, so we can call LL APIs later lcd_hal_init(&bus->hal, bus_id); LCD_CLOCK_SRC_ATOMIC() { @@ -269,6 +302,16 @@ esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus) ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty"); int bus_id = bus->bus_id; +#if I80_USE_RETENTION_LINK + const periph_retention_module_t module_id = soc_i80_lcd_retention_info[bus_id].retention_module; + if (sleep_retention_is_module_created(module_id)) { + assert(sleep_retention_is_module_inited(module_id)); + sleep_retention_module_free(module_id); + } + if (sleep_retention_is_module_inited(module_id)) { + sleep_retention_module_deinit(module_id); + } +#endif // I80_USE_RETENTION_LINK lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus_id); PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_i80_signals.buses[bus_id].module, ref_count) { if (ref_count == 0) { @@ -572,6 +615,33 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons return ESP_OK; } +#if I80_USE_RETENTION_LINK +static esp_err_t lcd_i80_create_sleep_retention_link_cb(void *arg) +{ + esp_lcd_i80_bus_t *bus = (esp_lcd_i80_bus_t *)arg; + int bus_id = bus->bus_id; + sleep_retention_module_t module_id = soc_i80_lcd_retention_info[bus_id].retention_module; + esp_err_t err = sleep_retention_entries_create(soc_i80_lcd_retention_info[bus_id].regdma_entry_array, + soc_i80_lcd_retention_info[bus_id].array_size, + REGDMA_LINK_PRI_LCDCAM, module_id); + ESP_RETURN_ON_ERROR(err, TAG, "create retention link failed"); + return ESP_OK; +} + +static void lcd_i80_create_retention_module(esp_lcd_i80_bus_t *bus) +{ + int bus_id = bus->bus_id; + sleep_retention_module_t module_id = soc_i80_lcd_retention_info[bus_id].retention_module; + + if (sleep_retention_is_module_inited(module_id) && !sleep_retention_is_module_created(module_id)) { + if (sleep_retention_module_allocate(module_id) != ESP_OK) { + // even though the sleep retention module create failed, LCD driver should still work, so just warning here + ESP_LOGW(TAG, "create retention module failed, power domain can't turn off"); + } + } +} +#endif // I80_USE_RETENTION_LINK + static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src) { // get clock source frequency diff --git a/components/esp_lcd/include/esp_lcd_io_i80.h b/components/esp_lcd/include/esp_lcd_io_i80.h index f2885390f5f..79aa7d89378 100644 --- a/components/esp_lcd/include/esp_lcd_io_i80.h +++ b/components/esp_lcd/include/esp_lcd_io_i80.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,6 +34,11 @@ typedef struct { size_t dma_burst_size; /*!< DMA burst size, in bytes */ }; size_t sram_trans_align __attribute__((deprecated)); /*!< DMA transfer alignment for data allocated from SRAM */ + /// Extra configuration flags for I80 bus + struct extra_i80_bus_flags { + uint32_t allow_pd: 1; /*!< If set, driver allows the power domain to be powered off when system enters sleep mode. + This can save power, but at the expense of more RAM being consumed to save register context. */ + } flags; /*!< Extra bus config flags */ } esp_lcd_i80_bus_config_t; /** @@ -71,19 +76,21 @@ typedef struct { void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */ int lcd_cmd_bits; /*!< Bit-width of LCD command */ int lcd_param_bits; /*!< Bit-width of LCD parameter */ - struct { + /// D/C line levels configuration + struct i80_panel_io_dc_levels { unsigned int dc_idle_level: 1; /*!< Level of DC line in IDLE phase */ unsigned int dc_cmd_level: 1; /*!< Level of DC line in CMD phase */ unsigned int dc_dummy_level: 1; /*!< Level of DC line in DUMMY phase */ unsigned int dc_data_level: 1; /*!< Level of DC line in DATA phase */ } dc_levels; /*!< Each i80 device might have its own D/C control logic */ - struct { + /// Extra configuration flags for I80 panel IO + struct extra_i80_panel_io_flags { unsigned int cs_active_high: 1; /*!< If set, a high level of CS line will select the device, otherwise, CS line is low level active */ unsigned int reverse_color_bits: 1; /*!< Reverse the data bits, D[N:0] -> D[0:N] */ unsigned int swap_color_bytes: 1; /*!< Swap adjacent two color bytes */ unsigned int pclk_active_neg: 1; /*!< The display will write data lines when there's a falling edge on WR signal (a.k.a the PCLK) */ unsigned int pclk_idle_low: 1; /*!< The WR signal (a.k.a the PCLK) stays at low level in IDLE phase */ - } flags; /*!< Panel IO config flags */ + } flags; /*!< Extra panel IO config flags */ } esp_lcd_panel_io_i80_config_t; /** diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index 616e68bb6b2..8584bada50a 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -47,6 +47,7 @@ #include "hal/cache_hal.h" #include "hal/cache_ll.h" #include "rgb_lcd_rotation_sw.h" +#include "esp_private/sleep_retention.h" // hardware issue workaround #if CONFIG_IDF_TARGET_ESP32S3 @@ -250,6 +251,9 @@ static esp_err_t lcd_rgb_panel_destroy(esp_rgb_panel_t *rgb_panel) esp_pm_lock_release(rgb_panel->pm_lock); esp_pm_lock_delete(rgb_panel->pm_lock); } +#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP + sleep_retention_power_lock_release(); +#endif free(rgb_panel); return ESP_OK; } @@ -300,6 +304,11 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf expect_bb_eof_count = fb_size / bb_size; } +#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP + // acquire the retention power lock to prevent the power domain from being turned off during light sleep + sleep_retention_power_lock_acquire(); +#endif + // calculate the number of DMA descriptors size_t num_dma_nodes = 0; // allocate memory for rgb panel @@ -334,6 +343,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf // set clock source ret = lcd_rgb_panel_select_clock_src(rgb_panel, rgb_panel_config->clk_src); ESP_GOTO_ON_ERROR(ret, err, TAG, "set source clock failed"); + // reset peripheral and FIFO after we select a correct clock source lcd_ll_fifo_reset(rgb_panel->hal.dev); lcd_ll_reset(rgb_panel->hal.dev); diff --git a/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt b/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt index 2a3cea972db..f9f50925092 100644 --- a/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt +++ b/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt @@ -1,6 +1,10 @@ set(srcs "test_app_main.c" "test_i80_lcd_panel.c") +if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED AND CONFIG_PM_ENABLE) + list(APPEND srcs "test_i80_lcd_panel_sleep.c") +endif() + # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE idf_component_register(SRCS ${srcs} diff --git a/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel_sleep.c b/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel_sleep.c new file mode 100644 index 00000000000..d3ed0325e18 --- /dev/null +++ b/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel_sleep.c @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "esp_random.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "soc/soc_caps.h" +#include "driver/gpio.h" +#include "test_i80_board.h" +#include "esp_private/sleep_cpu.h" +#include "esp_private/esp_sleep_internal.h" +#include "esp_private/esp_pmu.h" + +static void test_i80_lcd_sleep_retention(bool allow_pd) +{ +#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) + uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); + TEST_ASSERT_NOT_NULL(img); + + gpio_config_t bk_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << TEST_LCD_BK_LIGHT_GPIO + }; + TEST_ESP_OK(gpio_config(&bk_gpio_config)); + + esp_lcd_i80_bus_handle_t i80_bus = NULL; + esp_lcd_i80_bus_config_t bus_config = { + .dc_gpio_num = TEST_LCD_DC_GPIO, + .wr_gpio_num = TEST_LCD_PCLK_GPIO, + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = { + TEST_LCD_DATA0_GPIO, + TEST_LCD_DATA1_GPIO, + TEST_LCD_DATA2_GPIO, + TEST_LCD_DATA3_GPIO, + TEST_LCD_DATA4_GPIO, + TEST_LCD_DATA5_GPIO, + TEST_LCD_DATA6_GPIO, + TEST_LCD_DATA7_GPIO, + }, + .bus_width = 8, + .max_transfer_bytes = TEST_IMG_SIZE + 10, + .flags = { + .allow_pd = allow_pd, + }, + }; + TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_i80_config_t io_config = { + .cs_gpio_num = TEST_LCD_CS_GPIO, + .pclk_hz = 8000000, // 8MHz + .trans_queue_depth = 10, + .dc_levels = { + .dc_idle_level = 0, + .dc_cmd_level = 0, + .dc_dummy_level = 0, + .dc_data_level = 1, + }, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); + + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = TEST_LCD_RST_GPIO, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + }; + TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); + + // turn off backlight + gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0); + esp_lcd_panel_reset(panel_handle); + esp_lcd_panel_init(panel_handle); + esp_lcd_panel_invert_color(panel_handle, true); + // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value + esp_lcd_panel_set_gap(panel_handle, 0, 20); + // turn on display + esp_lcd_panel_disp_on_off(panel_handle, true); + // turn on backlight + gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); + + for (int i = 0; i < 200; i++) { + uint8_t color_byte = esp_random() & 0xFF; + int x_start = esp_random() % (TEST_LCD_H_RES - 100); + int y_start = esp_random() % (TEST_LCD_V_RES - 100); + memset(img, color_byte, TEST_IMG_SIZE); + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + } + // turn off screen + esp_lcd_panel_disp_on_off(panel_handle, false); + + // go to sleep + esp_sleep_context_t sleep_ctx; + esp_sleep_set_sleep_context(&sleep_ctx); + printf("go to light sleep for 2 seconds\r\n"); +#if ESP_SLEEP_POWER_DOWN_CPU + TEST_ESP_OK(sleep_cpu_configure(true)); +#endif + TEST_ESP_OK(esp_sleep_enable_timer_wakeup(2 * 1000 * 1000)); + TEST_ESP_OK(esp_light_sleep_start()); + + printf("Waked up! Let's see if I80 LCD driver can still work...\r\n"); +#if ESP_SLEEP_POWER_DOWN_CPU + TEST_ESP_OK(sleep_cpu_configure(false)); +#endif + + printf("check if the sleep happened as expected\r\n"); + TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result); +#if SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + // check if the power domain also is powered down + TEST_ASSERT_EQUAL(allow_pd ? PMU_SLEEP_PD_TOP : 0, (sleep_ctx.sleep_flags) & PMU_SLEEP_PD_TOP); +#endif + esp_sleep_set_sleep_context(NULL); + + // turn on screen + esp_lcd_panel_disp_on_off(panel_handle, true); + + printf("Testing I80 LCD after sleep...\n"); + for (int i = 0; i < 200; i++) { + uint8_t color_byte = esp_random() & 0xFF; + int x_start = esp_random() % (TEST_LCD_H_RES - 100); + int y_start = esp_random() % (TEST_LCD_V_RES - 100); + memset(img, color_byte, TEST_IMG_SIZE); + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + } + + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); + TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); + TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO)); + free(img); +#undef TEST_IMG_SIZE +} + +TEST_CASE("i80_lcd light sleep", "[lcd]") +{ + test_i80_lcd_sleep_retention(false); +#if SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + test_i80_lcd_sleep_retention(true); +#endif +} diff --git a/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release b/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release index 91d93f163e6..eb643d4d06b 100644 --- a/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release +++ b/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release @@ -1,4 +1,5 @@ CONFIG_PM_ENABLE=y +CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y CONFIG_FREERTOS_USE_TICKLESS_IDLE=y CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y diff --git a/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults b/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults index ccc43c6fdfa..15635373d95 100644 --- a/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults +++ b/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults @@ -3,3 +3,6 @@ # # CONFIG_ESP_TASK_WDT_INIT is not set CONFIG_FREERTOS_HZ=1000 + +# primitives for checking sleep internal state +CONFIG_ESP_SLEEP_DEBUG=y diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 7ea9b960efe..d81c73ad36b 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -2291,6 +2291,10 @@ config SOC_LCDCAM_CAM_DATA_WIDTH_MAX int default 16 +config SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + bool + default y + config SOC_I3C_MASTER_PERIPH_NUM bool default y diff --git a/components/soc/esp32p4/include/soc/retention_periph_defs.h b/components/soc/esp32p4/include/soc/retention_periph_defs.h index 17cf61998f3..29a155b0841 100644 --- a/components/soc/esp32p4/include/soc/retention_periph_defs.h +++ b/components/soc/esp32p4/include/soc/retention_periph_defs.h @@ -60,6 +60,7 @@ typedef enum periph_retention_module { SLEEP_RETENTION_MODULE_MCPWM0 = 34, SLEEP_RETENTION_MODULE_MCPWM1 = 35, SLEEP_RETENTION_MODULE_PCNT0 = 36, + SLEEP_RETENTION_MODULE_LCDCAM = 39, SLEEP_RETENTION_MODULE_MAX = SOC_PM_RETENTION_MODULE_NUM - 1 } periph_retention_module_t; @@ -102,6 +103,7 @@ typedef enum periph_retention_module { : ((m) == SLEEP_RETENTION_MODULE_MCPWM0) ? true \ : ((m) == SLEEP_RETENTION_MODULE_MCPWM1) ? true \ : ((m) == SLEEP_RETENTION_MODULE_PCNT0) ? true \ + : ((m) == SLEEP_RETENTION_MODULE_LCDCAM) ? true \ : false) #ifdef __cplusplus diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index ebe884796d0..ded2ec59f75 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -847,6 +847,9 @@ #define SOC_LCDCAM_CAM_PERIPH_NUM (1U) #define SOC_LCDCAM_CAM_DATA_WIDTH_MAX (16U) +/*--------------------------- LCD ---------------------------------*/ +#define SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION (1) /*!< Support back up registers before sleep */ + /*--------------------------- I3C ---------------------------------*/ #define SOC_I3C_MASTER_PERIPH_NUM (1) #define SOC_I3C_MASTER_ADDRESS_TABLE_NUM (12) diff --git a/components/soc/esp32p4/lcd_periph.c b/components/soc/esp32p4/lcd_periph.c index 9668825e1f0..21da9e497e4 100644 --- a/components/soc/esp32p4/lcd_periph.c +++ b/components/soc/esp32p4/lcd_periph.c @@ -88,3 +88,39 @@ const lcd_rgb_signal_conn_t lcd_periph_rgb_signals = { } }; #endif // SOC_LCDCAM_RGB_LCD_SUPPORTED + +/** + * LCD_CAM Registers to be saved during sleep retention + * - LCD Clock Configuration registers: LCDCAM_LCD_CLOCK_REG (0x0) + * - LCD RGB/YUV Configuration registers: LCDCAM_LCD_RGB_YUV_REG (0x10) + * - LCD User Configuration registers: LCDCAM_LCD_USER_REG (0x14), LCDCAM_LCD_MISC_REG (0x18) + * - LCD Control registers: LCDCAM_LCD_CTRL_REG (0x1c), LCDCAM_LCD_CTRL1_REG (0x20), LCDCAM_LCD_CTRL2_REG (0x24) + * - LCD Command Value registers: LCDCAM_LCD_FIRST_CMD_VAL_REG (0x28), LCDCAM_LCD_LATTER_CMD_VAL_REG (0x2c) + * - LCD Delay Mode Configuration registers: LCDCAM_LCD_DLY_MODE_CFG1_REG (0x30), LCDCAM_LCD_DLY_MODE_CFG2_REG (0x38) + * - LCD DMA Interrupt Enable register: LCDCAM_LC_DMA_INT_ENA_REG (0x64) + * + * NOTE: Only I80 LCD supports sleep retention, not RGB LCD. + */ +#define LCD_RETENTION_REGS_CNT 12 +#define LCD_RETENTION_REGS_BASE (DR_REG_LCDCAM_BASE + 0x0) +static const uint32_t lcd_cam_regs_map[4] = {0x2005ff1, 0x0, 0x0, 0x0}; +static const regdma_entries_config_t lcd_regs_retention[] = { + // backup stage: save configuration registers + // restore stage: restore the configuration registers + [0] = { + .config = REGDMA_LINK_ADDR_MAP_INIT(REGDMA_LCDCAM_LINK(0x00), + LCD_RETENTION_REGS_BASE, LCD_RETENTION_REGS_BASE, + LCD_RETENTION_REGS_CNT, 0, 0, + lcd_cam_regs_map[0], lcd_cam_regs_map[1], + lcd_cam_regs_map[2], lcd_cam_regs_map[3]), + .owner = ENTRY(0), + }, +}; + +const soc_i80_lcd_retention_info_t soc_i80_lcd_retention_info[1] = { + [0] = { + .regdma_entry_array = lcd_regs_retention, + .array_size = ARRAY_SIZE(lcd_regs_retention), + .retention_module = SLEEP_RETENTION_MODULE_LCDCAM + }, +}; diff --git a/components/soc/include/soc/lcd_periph.h b/components/soc/include/soc/lcd_periph.h index 3bcf50e3c5d..dcaacd60846 100644 --- a/components/soc/include/soc/lcd_periph.h +++ b/components/soc/include/soc/lcd_periph.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,12 @@ #include "soc/soc_caps.h" #include "soc/periph_defs.h" +#include "soc/regdma.h" + +#if SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION +#include "soc/retention_periph_defs.h" +#include "soc/lcd_cam_reg.h" +#endif #ifdef __cplusplus extern "C" { @@ -58,6 +64,17 @@ typedef struct { extern const lcd_i2s_signal_conn_t lcd_periph_i2s_signals; #endif // SOC_I2S_SUPPORTS_LCD_CAMERA +#if SOC_PAU_SUPPORTED && SOC_LCDCAM_I80_LCD_SUPPORTED +// Only LCDCAM I80 LCD supports sleep retention +typedef struct { + const periph_retention_module_t retention_module; + const regdma_entries_config_t *regdma_entry_array; + uint32_t array_size; +} soc_i80_lcd_retention_info_t; + +extern const soc_i80_lcd_retention_info_t soc_i80_lcd_retention_info[SOC_LCDCAM_I80_NUM_BUSES]; +#endif // SOC_PAU_SUPPORTED && SOC_LCDCAM_I80_LCD_SUPPORTED + #ifdef __cplusplus } #endif diff --git a/components/soc/include/soc/regdma.h b/components/soc/include/soc/regdma.h index 5d3e940561e..f9fc37c819e 100644 --- a/components/soc/include/soc/regdma.h +++ b/components/soc/include/soc/regdma.h @@ -64,6 +64,7 @@ extern "C" { #define REGDMA_LEDC_LINK(_pri) ((0x24 << 8) | _pri) #define REGDMA_PCNT_LINK(_pri) ((0x25 << 8) | _pri) #define REGDMA_MCPWM_LINK(_pri) ((0x26 << 8) | _pri) +#define REGDMA_LCDCAM_LINK(_pri) ((0x29 << 8) | _pri) #define REGDMA_MODEM_FE_LINK(_pri) ((0xFF << 8) | _pri) @@ -91,6 +92,7 @@ extern "C" { #define REGDMA_LINK_PRI_GPSPI REGDMA_LINK_PRI_GENERAL_PERIPH #define REGDMA_LINK_PRI_LEDC REGDMA_LINK_PRI_GENERAL_PERIPH #define REGDMA_LINK_PRI_MCPWM REGDMA_LINK_PRI_GENERAL_PERIPH +#define REGDMA_LINK_PRI_LCDCAM REGDMA_LINK_PRI_GENERAL_PERIPH typedef enum { REGDMA_LINK_PRI_0 = 0, diff --git a/docs/en/api-reference/system/power_management.rst b/docs/en/api-reference/system/power_management.rst index 312c16784de..59b33a29338 100644 --- a/docs/en/api-reference/system/power_management.rst +++ b/docs/en/api-reference/system/power_management.rst @@ -170,6 +170,7 @@ The following peripheral drivers are not aware of DFS yet. Applications need to :SOC_TWAI_SUPPORT_SLEEP_RETENTION: - All TWAIs :SOC_PARLIO_SUPPORT_SLEEP_RETENTION: - PARL_IO :SOC_SPI_SUPPORT_SLEEP_RETENTION: - All GPSPIs + :SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION: - I80 LCD Some peripherals haven't support Light-sleep context retention, or it cannot survive from the register lose. They will prevent the power-down of peripherals even when the feature is enabled. diff --git a/docs/zh_CN/api-reference/system/power_management.rst b/docs/zh_CN/api-reference/system/power_management.rst index 1d648f1fc30..0a38161216f 100644 --- a/docs/zh_CN/api-reference/system/power_management.rst +++ b/docs/zh_CN/api-reference/system/power_management.rst @@ -170,6 +170,7 @@ ESP-IDF 中集成的电源管理算法可以根据应用程序组件的需求, :SOC_TWAI_SUPPORT_SLEEP_RETENTION: - All TWAIs :SOC_PARLIO_SUPPORT_SLEEP_RETENTION: - PARL_IO :SOC_SPI_SUPPORT_SLEEP_RETENTION: - All GPSPIs + :SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION: - I80 LCD 一些外设尚未支持睡眠上下文恢复,或者寄存器丢失后根本无法恢复。即使外设下电功能被启用,它们也会阻止外设下电的发生: