fix(esp_lcd): balance clock source references on teardown

Track enabled clock sources in DSI/DPI/i80 drivers and disable them in
delete/error cleanup paths so esp_clk_tree source refcounts stay
balanced.
This commit is contained in:
morris
2026-05-14 16:39:28 +08:00
parent f3ee69582e
commit 6b711f91f5
4 changed files with 33 additions and 2 deletions

View File

@@ -30,6 +30,8 @@ esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lc
esp_lcd_dsi_bus_t *dsi_bus = heap_caps_calloc(1, sizeof(esp_lcd_dsi_bus_t), DSI_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(dsi_bus, ESP_ERR_NO_MEM, TAG, "no memory for DSI bus");
dsi_bus->bus_id = bus_id;
dsi_bus->phy_pllref_clk_src = SOC_MOD_CLK_INVALID;
dsi_bus->phy_cfg_clk_src = SOC_MOD_CLK_INVALID;
// Enable the APB clock for accessing the DSI host and bridge registers
PERIPH_RCC_ATOMIC() {
@@ -47,9 +49,11 @@ esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lc
#endif
}
ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)phy_clk_src, true), err, TAG, "clock source enable failed");
dsi_bus->phy_pllref_clk_src = (soc_module_clk_t)phy_clk_src;
// always use the default clock source for the DSI PHY configuration
ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)MIPI_DSI_PHY_CFG_CLK_SRC_DEFAULT, true), err, TAG, "clock source enable failed");
dsi_bus->phy_cfg_clk_src = (soc_module_clk_t)MIPI_DSI_PHY_CFG_CLK_SRC_DEFAULT;
// enable the clock source for DSI PHY
PERIPH_RCC_ATOMIC() {
@@ -143,6 +147,14 @@ esp_err_t esp_lcd_del_dsi_bus(esp_lcd_dsi_bus_handle_t bus)
mipi_dsi_ll_enable_phy_pllref_clock(bus_id, false);
mipi_dsi_ll_enable_phy_config_clock(bus_id, false);
}
if (bus->phy_pllref_clk_src != SOC_MOD_CLK_INVALID) {
esp_clk_tree_enable_src(bus->phy_pllref_clk_src, false);
bus->phy_pllref_clk_src = SOC_MOD_CLK_INVALID;
}
if (bus->phy_cfg_clk_src != SOC_MOD_CLK_INVALID) {
esp_clk_tree_enable_src(bus->phy_cfg_clk_src, false);
bus->phy_cfg_clk_src = SOC_MOD_CLK_INVALID;
}
// disable the APB clock for accessing the DSI peripheral registers
PERIPH_RCC_ATOMIC() {
mipi_dsi_ll_enable_bus_clock(bus_id, false);

View File

@@ -34,6 +34,7 @@ struct esp_lcd_dpi_panel_t {
uint32_t v_pixels; // Vertical pixels
size_t fb_size; // Frame buffer size, in bytes
size_t bits_per_pixel; // Bits per pixel
soc_module_clk_t clk_src; // DPI clock source, SOC_MOD_CLK_INVALID if not enabled
lcd_color_format_t in_color_format; // Input color format
lcd_color_format_t out_color_format; // Output color format
dw_gdma_channel_handle_t dma_chan; // DMA channel
@@ -215,6 +216,7 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
dpi_panel = heap_caps_calloc(1, sizeof(esp_lcd_dpi_panel_t), DSI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dpi_panel, ESP_ERR_NO_MEM, err, TAG, "no memory for DPI panel");
dpi_panel->clk_src = SOC_MOD_CLK_INVALID;
dpi_panel->virtual_channel = panel_config->virtual_channel;
dpi_panel->in_color_format = in_color_format;
dpi_panel->out_color_format = out_color_format;
@@ -253,6 +255,7 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
float dpi_clk_src_freq_mhz = (float)dpi_clk_src_freq_hz / 1000.0f / 1000.0f;
uint32_t dpi_div = mipi_dsi_hal_host_dpi_calculate_divider(hal, dpi_clk_src_freq_mhz, panel_config->dpi_clock_freq_mhz);
ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)dpi_clk_src, true), err, TAG, "clock source enable failed");
dpi_panel->clk_src = (soc_module_clk_t)dpi_clk_src;
// set the clock source, set the divider, and enable the dpi clock
PERIPH_RCC_ATOMIC() {
mipi_dsi_ll_set_dpi_clock_source(bus_id, dpi_clk_src);
@@ -360,6 +363,10 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
PERIPH_RCC_ATOMIC() {
mipi_dsi_ll_enable_dpi_clock(bus_id, false);
}
if (dpi_panel->clk_src != SOC_MOD_CLK_INVALID) {
esp_clk_tree_enable_src(dpi_panel->clk_src, false);
dpi_panel->clk_src = SOC_MOD_CLK_INVALID;
}
// disable the DSI bridge
mipi_dsi_brg_ll_enable(hal->bridge, false);
// free memory

View File

@@ -46,6 +46,8 @@ extern "C" {
typedef struct esp_lcd_dsi_bus_t {
int bus_id;
mipi_dsi_hal_context_t hal;
soc_module_clk_t phy_pllref_clk_src; // PHY PLL reference clock source, SOC_MOD_CLK_INVALID if not enabled
soc_module_clk_t phy_cfg_clk_src; // PHY config clock source, SOC_MOD_CLK_INVALID if not enabled
#if CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock;
#endif

View File

@@ -65,6 +65,7 @@ struct esp_lcd_i80_bus_t {
uint8_t *format_buffer; // The driver allocates an internal buffer for DMA to do data format transformer
uint8_t *format_buffer_nc; // Non-cacheable version of format buffer
size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source
soc_module_clk_t clk_src; // peripheral clock source, SOC_MOD_CLK_INVALID if not enabled
size_t max_transfer_bytes; // Maximum number of bytes that can be transferred in one transaction
gdma_channel_handle_t dma_chan; // DMA channel handle
gdma_link_list_handle_t dma_link; // DMA link list handle
@@ -139,6 +140,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus");
bus->bus_width = bus_config->bus_width;
bus->bus_id = -1;
bus->clk_src = SOC_MOD_CLK_INVALID;
// allocate the format buffer from internal memory, with DMA capability
bus->format_buffer = heap_caps_calloc(1, LCD_I80_IO_FORMAT_BUF_SIZE,
MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
@@ -262,6 +264,10 @@ err:
if (bus->format_buffer) {
free(bus->format_buffer);
}
if (bus->clk_src != SOC_MOD_CLK_INVALID) {
esp_clk_tree_enable_src(bus->clk_src, false);
bus->clk_src = SOC_MOD_CLK_INVALID;
}
#if CONFIG_PM_ENABLE
if (bus->pm_lock) {
esp_pm_lock_delete(bus->pm_lock);
@@ -281,6 +287,10 @@ esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
PERIPH_RCC_ATOMIC() {
lcd_ll_enable_clock(bus->hal.dev, false);
}
if (bus->clk_src != SOC_MOD_CLK_INVALID) {
esp_clk_tree_enable_src(bus->clk_src, false);
bus->clk_src = SOC_MOD_CLK_INVALID;
}
#if I80_USE_RETENTION_LINK
const periph_retention_module_t module_id = soc_i80_lcd_retention_info[bus_id].retention_module;
sleep_retention_module_detach(module_id);
@@ -631,12 +641,12 @@ static void lcd_i80_create_retention_module(esp_lcd_i80_bus_t *bus)
static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src)
{
ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true), TAG, "clock source enable failed");
bus->clk_src = (soc_module_clk_t)clk_src;
// get clock source frequency
uint32_t src_clk_hz = 0;
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
TAG, "get clock source frequency failed");
ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true), TAG, "clock source enable failed");
PERIPH_RCC_ATOMIC() {
lcd_ll_select_clk_src(bus->hal.dev, clk_src);
// force to use integer division, as fractional division might lead to clock jitter