From 7ef7c9f3a187bbf8aaec0cdccd2510bcfe45824e Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Tue, 23 Sep 2025 18:44:35 +0800 Subject: [PATCH] feat(lcd): re-support i80 lcd and support underrun inerrupt on p4 rev 3.0 --- components/esp_lcd/i80/esp_lcd_panel_io_i80.c | 48 +++++++++++++------ components/esp_lcd/rgb/esp_lcd_panel_rgb.c | 12 +++-- components/hal/esp32p4/include/hal/lcd_ll.h | 19 ++++++-- components/hal/esp32s3/include/hal/lcd_ll.h | 3 ++ 4 files changed, 61 insertions(+), 21 deletions(-) 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 cf74d9aada..056d38584d 100644 --- a/components/esp_lcd/i80/esp_lcd_panel_io_i80.c +++ b/components/esp_lcd/i80/esp_lcd_panel_io_i80.c @@ -195,10 +195,10 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED; ret = esp_intr_alloc_intrstatus(lcd_periph_i80_signals.buses[bus_id].irq_id, isr_flags, (uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev), - LCD_LL_EVENT_TRANS_DONE, i80_lcd_default_isr_handler, bus, &bus->intr); + LCD_LL_EVENT_I80, i80_lcd_default_isr_handler, bus, &bus->intr); ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed"); PERIPH_RCC_ATOMIC() { - lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, false); // disable all interrupts + lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_I80, false); // disable all interrupts } lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt // install DMA service @@ -216,8 +216,8 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc // number of data cycles is controlled by DMA buffer size lcd_ll_enable_output_always_on(bus->hal.dev, true); PERIPH_RCC_ATOMIC() { - // enable trans done interrupt - lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, true); + // enable all interrupts + lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_I80, true); } // trigger a quick "trans done" event, and wait for the interrupt line goes active // this could ensure we go into ISR handler next time we call `esp_intr_enable` @@ -489,12 +489,13 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons trans_desc->cmd_value = lcd_cmd; // either the param is NULL or the param_size is zero, means there isn't a data phase in this transaction trans_desc->data = (param && param_len) ? bus->format_buffer : NULL; - trans_desc->data_length = trans_desc->data ? param_len : 0; + trans_desc->data_length = trans_desc->data ? param_len : 4; trans_desc->trans_done_cb = NULL; // no callback for parameter transaction size_t buffer_alignment = esp_ptr_internal(trans_desc->data) ? bus->int_mem_align : bus->ext_mem_align; + static uint32_t fake_trigger = 0; // mount data to DMA links gdma_buffer_mount_config_t mount_config = { - .buffer = (void *)trans_desc->data, + .buffer = trans_desc->data ? (void *)trans_desc->data : (&fake_trigger), .buffer_alignment = buffer_alignment, .length = trans_desc->data_length, .flags = { @@ -688,6 +689,17 @@ static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t b // next time when esp_intr_enable is invoked, we can go into interrupt handler immediately // where we dispatch transactions for i80 devices lcd_ll_set_phase_cycles(bus->hal.dev, 0, 1, 0); + static uint32_t fake_trigger = 0; + gdma_buffer_mount_config_t mount_config = { + .buffer = &fake_trigger, + .length = 4, + .flags = { + .mark_eof = true, // mark the "EOF" flag to trigger LCD EOF interrupt + .mark_final = true, // singly link list, mark final descriptor + } + }; + gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL); + gdma_start(bus->dma_chan, gdma_link_get_head_addr(bus->dma_link)); lcd_ll_start(bus->hal.dev); while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE)) {} } @@ -705,15 +717,15 @@ static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descript lcd_ll_set_phase_cycles(bus->hal.dev, cmd_cycles, dummy_cycles, data_cycles); lcd_ll_set_blank_cycles(bus->hal.dev, 1, 1); - // reset FIFO before starting a new transaction + // reset FIFO before starting a new transaction, in case there remains some dirty data in the FIFO because of the "fake trigger". lcd_ll_fifo_reset(bus->hal.dev); - if (trans_desc->data) { // some specific LCD commands can have no parameters - gdma_start(bus->dma_chan, gdma_link_get_head_addr(bus->dma_link)); - // delay 1us is sufficient for DMA to pass data to LCD FIFO - // in fact, this is only needed when LCD pixel clock is set too high - esp_rom_delay_us(1); - } + // always start GDMA, because the lcd will only start working after the dma retrieves the data + gdma_start(bus->dma_chan, gdma_link_get_head_addr(bus->dma_link)); + // delay 1us is sufficient for DMA to pass data to LCD FIFO + // in fact, this is only needed when LCD pixel clock is set too high + esp_rom_delay_us(1); + lcd_ll_start(bus->hal.dev); } @@ -749,6 +761,14 @@ IRAM_ATTR static void i80_lcd_default_isr_handler(void *args) BaseType_t high_task_woken = pdFALSE; bool need_yield = false; uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev); + +#if LCD_LL_EVENT_UNDERRUN + if (intr_status & LCD_LL_EVENT_UNDERRUN) { + lcd_ll_clear_interrupt_status(bus->hal.dev, LCD_LL_EVENT_UNDERRUN); + ESP_EARLY_LOGE(TAG, "LCD underrun"); + } +#endif + if (intr_status & LCD_LL_EVENT_TRANS_DONE) { // disable interrupt temporarily, only re-enable when there be remained transaction in the queue esp_intr_disable(bus->intr); @@ -787,7 +807,7 @@ IRAM_ATTR static void i80_lcd_default_isr_handler(void *args) // sanity check assert(trans_desc); // only clear the interrupt status when we're sure there still remains transaction to handle - lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status); + lcd_ll_clear_interrupt_status(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE); // switch devices if necessary lcd_i80_switch_devices(cur_device, next_device); // only reverse data bit/bytes for color data diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index e265fb2b58..3fc8714846 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -330,10 +330,10 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf int isr_flags = LCD_RGB_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED; ret = esp_intr_alloc_intrstatus(lcd_periph_rgb_signals.panels[panel_id].irq_id, isr_flags, (uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev), - LCD_LL_EVENT_VSYNC_END, rgb_lcd_default_isr_handler, rgb_panel, &rgb_panel->intr); + LCD_LL_EVENT_RGB, rgb_lcd_default_isr_handler, rgb_panel, &rgb_panel->intr); ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed"); PERIPH_RCC_ATOMIC() { - lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, false); // disable all interrupts + lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_RGB, false); // disable all interrupts } lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt @@ -573,7 +573,7 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode); PERIPH_RCC_ATOMIC() { // trigger interrupt on the end of frame - lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true); + lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_RGB, true); } // enable intr esp_intr_enable(rgb_panel->intr); @@ -1139,6 +1139,12 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args) uint32_t intr_status = lcd_ll_get_interrupt_status(rgb_panel->hal.dev); lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status); +#if LCD_LL_EVENT_UNDERRUN + if (intr_status & LCD_LL_EVENT_UNDERRUN) { + ESP_EARLY_LOGE(TAG, "LCD underrun"); + } +#endif + // VSYNC event happened if (intr_status & LCD_LL_EVENT_VSYNC_END) { // call user registered callback diff --git a/components/hal/esp32p4/include/hal/lcd_ll.h b/components/hal/esp32p4/include/hal/lcd_ll.h index 416e93b520..b70a305051 100644 --- a/components/hal/esp32p4/include/hal/lcd_ll.h +++ b/components/hal/esp32p4/include/hal/lcd_ll.h @@ -13,6 +13,7 @@ #include "soc/lcd_cam_struct.h" #include "hal/assert.h" #include "hal/lcd_types.h" +#include "hal/config.h" #include "soc/hp_sys_clkrst_struct.h" #ifdef __cplusplus @@ -25,6 +26,16 @@ extern "C" { #define LCD_LL_EVENT_VSYNC_END (1 << 0) #define LCD_LL_EVENT_TRANS_DONE (1 << 1) +// Underrun interrupt is only supported on ESP32P4 Rev. 3.0 and later +#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 +#define LCD_LL_EVENT_UNDERRUN (1 << 4) +#else +#define LCD_LL_EVENT_UNDERRUN 0 +#endif + +#define LCD_LL_EVENT_I80 LCD_LL_EVENT_TRANS_DONE | LCD_LL_EVENT_UNDERRUN +#define LCD_LL_EVENT_RGB LCD_LL_EVENT_VSYNC_END | LCD_LL_EVENT_UNDERRUN + #define LCD_LL_CLK_FRAC_DIV_N_MAX 256 // LCD_CLK = LCD_CLK_S / (N + b/a), the N register is 8 bit-width #define LCD_LL_CLK_FRAC_DIV_AB_MAX 64 // LCD_CLK = LCD_CLK_S / (N + b/a), the a/b register is 6 bit-width #define LCD_LL_PCLK_DIV_MAX 64 // LCD_PCLK = LCD_CLK / MO, the MO register is 6 bit-width @@ -721,9 +732,9 @@ static inline void lcd_ll_set_delay_ticks(lcd_cam_dev_t *dev, uint32_t hsync_del static inline void lcd_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bool en) { if (en) { - dev->lc_dma_int_ena.val |= mask & 0x03; + dev->lc_dma_int_ena.val |= mask & 0x13; } else { - dev->lc_dma_int_ena.val &= ~(mask & 0x03); + dev->lc_dma_int_ena.val &= ~(mask & 0x13); } } /// use a macro to wrap the function, force the caller to use it in a critical section @@ -742,7 +753,7 @@ static inline void lcd_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bo __attribute__((always_inline)) static inline uint32_t lcd_ll_get_interrupt_status(lcd_cam_dev_t *dev) { - return dev->lc_dma_int_st.val & 0x03; + return dev->lc_dma_int_st.val & 0x13; } /** @@ -754,7 +765,7 @@ static inline uint32_t lcd_ll_get_interrupt_status(lcd_cam_dev_t *dev) __attribute__((always_inline)) static inline void lcd_ll_clear_interrupt_status(lcd_cam_dev_t *dev, uint32_t mask) { - dev->lc_dma_int_clr.val = mask & 0x03; + dev->lc_dma_int_clr.val = mask & 0x13; } /** diff --git a/components/hal/esp32s3/include/hal/lcd_ll.h b/components/hal/esp32s3/include/hal/lcd_ll.h index 644cbb7825..451d497f82 100644 --- a/components/hal/esp32s3/include/hal/lcd_ll.h +++ b/components/hal/esp32s3/include/hal/lcd_ll.h @@ -25,6 +25,9 @@ extern "C" { #define LCD_LL_EVENT_VSYNC_END (1 << 0) #define LCD_LL_EVENT_TRANS_DONE (1 << 1) +#define LCD_LL_EVENT_I80 LCD_LL_EVENT_TRANS_DONE +#define LCD_LL_EVENT_RGB LCD_LL_EVENT_VSYNC_END + #define LCD_LL_CLK_FRAC_DIV_N_MAX 256 // LCD_CLK = LCD_CLK_S / (N + b/a), the N register is 8 bit-width #define LCD_LL_CLK_FRAC_DIV_AB_MAX 64 // LCD_CLK = LCD_CLK_S / (N + b/a), the a/b register is 6 bit-width #define LCD_LL_PCLK_DIV_MAX 64 // LCD_PCLK = LCD_CLK / MO, the MO register is 6 bit-width