mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feat/lcd_sleep_retention_p4_v6.0' into 'release/v6.0'
feat(lcd): support i80 lcd sleep retention on p4 (v6.0) See merge request espressif/esp-idf!46525
This commit is contained in:
@@ -10,6 +10,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
|
||||
@@ -31,6 +35,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);
|
||||
@@ -117,6 +126,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");
|
||||
@@ -143,6 +156,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() {
|
||||
@@ -240,6 +273,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(soc_lcd_i80_signals[bus_id].module, ref_count) {
|
||||
if (ref_count == 0) {
|
||||
@@ -546,6 +589,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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -30,6 +30,11 @@ typedef struct {
|
||||
size_t bus_width; /*!< Number of data lines, 8 or 16 */
|
||||
size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */
|
||||
size_t dma_burst_size; /*!< DMA burst size, in bytes */
|
||||
/// 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;
|
||||
|
||||
/**
|
||||
@@ -67,19 +72,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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "hal/cache_ll.h"
|
||||
#include "hal/color_hal.h"
|
||||
#include "rgb_lcd_rotation_sw.h"
|
||||
#include "esp_private/sleep_retention.h"
|
||||
|
||||
// hardware issue workaround
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
@@ -284,6 +285,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);
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
|
||||
sleep_retention_power_lock_release();
|
||||
#endif
|
||||
free(rgb_panel);
|
||||
return ESP_OK;
|
||||
@@ -342,6 +346,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
|
||||
@@ -379,6 +388,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(hal->dev);
|
||||
lcd_ll_reset(hal->dev);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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 CONFIG_PM_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 CONFIG_PM_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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user