From 8dd26e9e9fac6b3330d06edb4013d9b2083c3063 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 7 Jun 2022 10:25:47 +0800 Subject: [PATCH 1/4] spi_lcd: make tjpgd example runnable on C2 --- examples/peripherals/lcd/tjpgd/README.md | 4 ++-- examples/peripherals/lcd/tjpgd/main/Kconfig.projbuild | 7 +++++++ .../peripherals/lcd/tjpgd/main/lcd_tjpgd_example_main.c | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/peripherals/lcd/tjpgd/README.md b/examples/peripherals/lcd/tjpgd/README.md index 16345eff07..48ded1462d 100644 --- a/examples/peripherals/lcd/tjpgd/README.md +++ b/examples/peripherals/lcd/tjpgd/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | -| ----------------- | ----- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | ESP32-C2 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | ## LCD tjpgd example diff --git a/examples/peripherals/lcd/tjpgd/main/Kconfig.projbuild b/examples/peripherals/lcd/tjpgd/main/Kconfig.projbuild index 8d71328c4d..bcc8851507 100644 --- a/examples/peripherals/lcd/tjpgd/main/Kconfig.projbuild +++ b/examples/peripherals/lcd/tjpgd/main/Kconfig.projbuild @@ -7,4 +7,11 @@ menu "Example Configuration" help This option can be chosen when using 8-line lcd. + config EXAMPLE_LCD_FLUSH_PARALLEL_LINES + int "LCD flush parallel lines" + default 12 if IDF_TARGET_ESP32C2 + default 16 + help + To speed up transfers, every SPI transfer sends a bunch of lines. + endmenu diff --git a/examples/peripherals/lcd/tjpgd/main/lcd_tjpgd_example_main.c b/examples/peripherals/lcd/tjpgd/main/lcd_tjpgd_example_main.c index 76187ec395..9fb00d9a66 100644 --- a/examples/peripherals/lcd/tjpgd/main/lcd_tjpgd_example_main.c +++ b/examples/peripherals/lcd/tjpgd/main/lcd_tjpgd_example_main.c @@ -5,6 +5,7 @@ */ #include +#include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_lcd_panel_io.h" @@ -20,7 +21,7 @@ // To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. // More means more memory use, but less overhead for setting up / finishing transfers. Make sure 240 // is dividable by this. -#define PARALLEL_LINES 12 +#define PARALLEL_LINES CONFIG_EXAMPLE_LCD_FLUSH_PARALLEL_LINES // The number of frames to show before rotate the graph #define ROTATE_FRAME 30 From 843279d287d164000469bd4b3abcbd62efe02930 Mon Sep 17 00:00:00 2001 From: morris Date: Mon, 30 May 2022 16:09:40 +0800 Subject: [PATCH 2/4] rgb_lcd: support fractional clock divisor --- components/esp_lcd/src/esp_lcd_panel_io_i80.c | 5 +- components/esp_lcd/src/esp_lcd_rgb_panel.c | 35 ++++++------ components/hal/esp32s3/include/hal/lcd_ll.h | 47 +++++++++------- components/hal/include/hal/lcd_hal.h | 29 +++++++++- components/hal/lcd_hal.c | 54 ++++++++++++++++++- 5 files changed, 127 insertions(+), 43 deletions(-) diff --git a/components/esp_lcd/src/esp_lcd_panel_io_i80.c b/components/esp_lcd/src/esp_lcd_panel_io_i80.c index a7285690fe..a015148e31 100644 --- a/components/esp_lcd/src/esp_lcd_panel_io_i80.c +++ b/components/esp_lcd/src/esp_lcd_panel_io_i80.c @@ -248,7 +248,7 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p ESP_GOTO_ON_FALSE(!bus_exclusive, ESP_ERR_INVALID_STATE, err, TAG, "bus has been exclusively owned by device"); // check if pixel clock setting is valid uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz; - ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= LCD_LL_CLOCK_PRESCALE_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG, + ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= LCD_LL_PCLK_DIV_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG, "prescaler can't satisfy PCLK clock %u", io_config->pclk_hz); i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS); ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io"); @@ -472,7 +472,8 @@ static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_c { esp_err_t ret = ESP_OK; // force to use integer division, as fractional division might lead to clock jitter - lcd_ll_set_group_clock_src(bus->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0); + lcd_ll_select_clk_src(bus->hal.dev, clk_src); + lcd_ll_set_group_clock_coeff(bus->hal.dev, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0); switch (clk_src) { case LCD_CLK_SRC_PLL160M: bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE; diff --git a/components/esp_lcd/src/esp_lcd_rgb_panel.c b/components/esp_lcd/src/esp_lcd_rgb_panel.c index 40fc6778a0..44687dd19f 100644 --- a/components/esp_lcd/src/esp_lcd_rgb_panel.c +++ b/components/esp_lcd/src/esp_lcd_rgb_panel.c @@ -61,7 +61,7 @@ static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mi static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool off); -static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src); +static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src); static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel); static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config); static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel); @@ -81,7 +81,7 @@ struct esp_rgb_panel_t { uint8_t *fb; // Frame buffer size_t fb_size; // Size of frame buffer int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color" - size_t resolution_hz; // Peripheral clock resolution + uint32_t src_clk_hz; // Peripheral source clock resolution esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width) gdma_channel_handle_t dma_chan; // DMA channel handle esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done @@ -159,9 +159,11 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf rgb_panel->flags.fb_in_psram = alloc_from_psram; // initialize HAL layer, so we can call LL APIs later lcd_hal_init(&rgb_panel->hal, panel_id); - // set peripheral clock resolution - ret = lcd_rgb_panel_select_periph_clock(rgb_panel, rgb_panel_config->clk_src); - ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed"); + // enable clock gating + lcd_ll_enable_clock(rgb_panel->hal.dev, true); + // 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"); // install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask) int isr_flags = LCD_RGB_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED; ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags, @@ -232,6 +234,7 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel) gdma_disconnect(rgb_panel->dma_chan); gdma_del_channel(rgb_panel->dma_chan); esp_intr_free(rgb_panel->intr); + lcd_ll_enable_clock(rgb_panel->hal.dev, false); periph_module_disable(lcd_periph_signals.panels[panel_id].module); lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id); free(rgb_panel->fb); @@ -256,14 +259,8 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) { esp_err_t ret = ESP_OK; esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - // configure clock - lcd_ll_enable_clock(rgb_panel->hal.dev, true); - // set PCLK frequency - uint32_t pclk_prescale = rgb_panel->resolution_hz / rgb_panel->timings.pclk_hz; - ESP_GOTO_ON_FALSE(pclk_prescale <= LCD_LL_CLOCK_PRESCALE_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG, - "prescaler can't satisfy PCLK clock %uHz", rgb_panel->timings.pclk_hz); - lcd_ll_set_pixel_clock_prescale(rgb_panel->hal.dev, pclk_prescale); - rgb_panel->timings.pclk_hz = rgb_panel->resolution_hz / pclk_prescale; + // set pixel clock frequency + rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz); // pixel clock phase and polarity lcd_ll_set_clock_idle_level(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_idle_high); lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg); @@ -299,7 +296,6 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) lcd_rgb_panel_start_transmission(rgb_panel); } ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%uHz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz); -err: return ret; } @@ -442,14 +438,12 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_ return ESP_OK; } -static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src) +static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src) { esp_err_t ret = ESP_OK; - // force to use integer division, as fractional division might lead to clock jitter - lcd_ll_set_group_clock_src(panel->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0); switch (clk_src) { case LCD_CLK_SRC_PLL160M: - panel->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE; + panel->src_clk_hz = 160000000; #if CONFIG_PM_ENABLE ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "rgb_panel", &panel->pm_lock); ESP_RETURN_ON_ERROR(ret, TAG, "create ESP_PM_APB_FREQ_MAX lock failed"); @@ -459,12 +453,13 @@ static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_c #endif break; case LCD_CLK_SRC_XTAL: - panel->resolution_hz = esp_clk_xtal_freq() / LCD_PERIPH_CLOCK_PRE_SCALE; + panel->src_clk_hz = esp_clk_xtal_freq(); break; default: - ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported clock source: %d", clk_src); + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported clock source: %d", clk_src); break; } + lcd_ll_select_clk_src(panel->hal.dev, clk_src); return ret; } diff --git a/components/hal/esp32s3/include/hal/lcd_ll.h b/components/hal/esp32s3/include/hal/lcd_ll.h index 89b7674f7e..bf92306754 100644 --- a/components/hal/esp32s3/include/hal/lcd_ll.h +++ b/components/hal/esp32s3/include/hal/lcd_ll.h @@ -23,8 +23,9 @@ extern "C" { #define LCD_LL_EVENT_VSYNC_END (1 << 0) #define LCD_LL_EVENT_TRANS_DONE (1 << 1) -// Maximum coefficient of clock prescaler -#define LCD_LL_CLOCK_PRESCALE_MAX (64) +#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 /** * @brief Enable clock gating @@ -38,25 +39,13 @@ static inline void lcd_ll_enable_clock(lcd_cam_dev_t *dev, bool en) } /** - * @brief Set clock source for LCD peripheral + * @brief Select clock source for LCD peripheral * * @param dev LCD register base address * @param src Clock source - * @param div_num Integer part of the divider - * @param div_a denominator of the divider - * @param div_b numerator of the divider */ -static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_source_t src, int div_num, int div_a, int div_b) +static inline void lcd_ll_select_clk_src(lcd_cam_dev_t *dev, lcd_clock_source_t src) { - // lcd_clk = module_clock_src / (div_num + div_b / div_a) - HAL_ASSERT(div_num >= 2 && div_num <= 256); - // dic_num == 0 means 256 divider in hardware - if (div_num >= 256) { - div_num = 0; - } - HAL_FORCE_MODIFY_U32_REG_FIELD(dev->lcd_clock, lcd_clkm_div_num, div_num); - dev->lcd_clock.lcd_clkm_div_a = div_a; - dev->lcd_clock.lcd_clkm_div_b = div_b; switch (src) { case LCD_CLK_SRC_PLL160M: dev->lcd_clock.lcd_clk_sel = 3; @@ -68,13 +57,34 @@ static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_sour dev->lcd_clock.lcd_clk_sel = 1; break; default: - // disble LCD clock source + // disable LCD clock source dev->lcd_clock.lcd_clk_sel = 0; - HAL_ASSERT(false && "unsupported clock source"); + HAL_ASSERT(false); break; } } +/** + * @brief Set clock coefficient of LCD peripheral + * + * @param dev LCD register base address + * @param div_num Integer part of the divider + * @param div_a denominator of the divider + * @param div_b numerator of the divider + */ +static inline void lcd_ll_set_group_clock_coeff(lcd_cam_dev_t *dev, int div_num, int div_a, int div_b) +{ + // lcd_clk = module_clock_src / (div_num + div_b / div_a) + HAL_ASSERT(div_num >= 2 && div_num <= LCD_LL_CLK_FRAC_DIV_N_MAX); + // dic_num == 0 means LCD_LL_CLK_FRAC_DIV_N_MAX divider in hardware + if (div_num >= LCD_LL_CLK_FRAC_DIV_N_MAX) { + div_num = 0; + } + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->lcd_clock, lcd_clkm_div_num, div_num); + dev->lcd_clock.lcd_clkm_div_a = div_a; + dev->lcd_clock.lcd_clkm_div_b = div_b; +} + /** * @brief Set the PCLK clock level state when there's no transaction undergoing @@ -109,6 +119,7 @@ static inline void lcd_ll_set_pixel_clock_edge(lcd_cam_dev_t *dev, bool active_o __attribute__((always_inline)) static inline void lcd_ll_set_pixel_clock_prescale(lcd_cam_dev_t *dev, uint32_t prescale) { + HAL_ASSERT(prescale <= LCD_LL_PCLK_DIV_MAX); // Formula: pixel_clk = lcd_clk / (1 + clkcnt_n) // clkcnt_n can't be zero uint32_t scale = 1; diff --git a/components/hal/include/hal/lcd_hal.h b/components/hal/include/hal/lcd_hal.h index db255b3d1e..0f0018e2e7 100644 --- a/components/hal/include/hal/lcd_hal.h +++ b/components/hal/include/hal/lcd_hal.h @@ -1,23 +1,48 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif +/** + * @brief LCD peripheral SOC layer handle + */ typedef struct lcd_cam_dev_t *lcd_soc_handle_t; +/** + * @brief LCD HAL layer context + */ typedef struct { - lcd_soc_handle_t dev; + lcd_soc_handle_t dev; // SOC layer handle } lcd_hal_context_t; +/** + * @brief LCD HAL layer initialization + * + * @param hal LCD HAL layer context + * @param id LCD peripheral ID + */ void lcd_hal_init(lcd_hal_context_t *hal, int id); +/** + * @brief LCD PCLK clock calculation + * @note Currently this function is only used by RGB LCD driver, I80 driver still uses a fixed clock division + * + * @param hal LCD HAL layer context + * @param src_freq_hz LCD source clock frequency in Hz + * @param expect_pclk_freq_hz Expected LCD PCLK frequency in Hz + * @return Actual LCD PCLK frequency in Hz + */ +uint32_t lcd_hal_cal_pclk_freq(lcd_hal_context_t *hal, uint32_t src_freq_hz, uint32_t expect_pclk_freq_hz); + #ifdef __cplusplus } #endif diff --git a/components/hal/lcd_hal.c b/components/hal/lcd_hal.c index 60da41f571..73a19cff2c 100644 --- a/components/hal/lcd_hal.c +++ b/components/hal/lcd_hal.c @@ -1,13 +1,65 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "hal/lcd_hal.h" #include "hal/lcd_ll.h" +#include "hal/log.h" void lcd_hal_init(lcd_hal_context_t *hal, int id) { hal->dev = LCD_LL_GET_HW(id); } + +/** + * @brief helper function, calculate the Greatest Common Divisor + * @note gcd(a, b) = gcd(b, a % b) + * @param a bigger value + * @param b smaller value + * @return result of gcd(a, b) + */ +static inline uint32_t _gcd(uint32_t a, uint32_t b) +{ + uint32_t c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + return b; +} + +uint32_t lcd_hal_cal_pclk_freq(lcd_hal_context_t *hal, uint32_t src_freq_hz, uint32_t expect_pclk_freq_hz) +{ + // lcd_clk = module_clock_src / (n + b / a) + // pixel_clk = lcd_clk / mo + uint32_t mo = src_freq_hz / expect_pclk_freq_hz / LCD_LL_CLK_FRAC_DIV_N_MAX + 1; + uint32_t n = src_freq_hz / expect_pclk_freq_hz / mo; + uint32_t a = 0; + uint32_t b = 0; + // delta_hz / expect_pclk_freq_hz <==> b / a + uint32_t delta_hz = src_freq_hz - expect_pclk_freq_hz * mo * n; + // fractional divider + if (delta_hz) { + uint32_t gcd = _gcd(expect_pclk_freq_hz, delta_hz); + a = expect_pclk_freq_hz / gcd; + b = delta_hz / gcd; + // normalize div_a and div_b + uint32_t d = a / LCD_LL_CLK_FRAC_DIV_AB_MAX + 1; + a /= d; + b /= d; + } + + HAL_LOGD("lcd_hal", "n=%d,a=%d,b=%d,mo=%d", n, a, b, mo); + + lcd_ll_set_group_clock_coeff(hal->dev, n, a, b); + lcd_ll_set_pixel_clock_prescale(hal->dev, mo); + + if (delta_hz) { + return ((uint64_t)src_freq_hz * a) / (n * a + b) / mo; + } else { + return src_freq_hz / n / mo; + } +} From b2bb8fd3c477794e3e06fe6f57510a58bb28b4f4 Mon Sep 17 00:00:00 2001 From: morris Date: Wed, 1 Jun 2022 11:00:00 +0800 Subject: [PATCH 3/4] rgb_lcd: support update pclk at runtime --- .../esp_lcd/include/esp_lcd_panel_rgb.h | 20 +++++++++++++++- components/esp_lcd/src/esp_lcd_rgb_panel.c | 24 +++++++++++++++++++ components/hal/CMakeLists.txt | 5 +++- components/hal/linker.lf | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/components/esp_lcd/include/esp_lcd_panel_rgb.h b/components/esp_lcd/include/esp_lcd_panel_rgb.h index 73d8c0d65f..ed856379b7 100644 --- a/components/esp_lcd/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/include/esp_lcd_panel_rgb.h @@ -78,7 +78,7 @@ typedef struct { /** * @brief Declare the prototype of the function that will be invoked when panel IO finishes transferring color data * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` + * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()` * @param[in] edata Panel event data, fed by driver * @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_config_t` * @return Whether a high priority task has been waken up by this function @@ -122,6 +122,24 @@ typedef struct { */ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel); +/** + * @brief Set frequency of PCLK for RGB LCD panel + * + * @note The PCLK frequency is set in the `esp_lcd_rgb_timing_t` and gets configured during LCD panel initialization. + * Usually you don't need to call this function to set the PCLK again, but in some cases, you may need to change the PCLK frequency. + * e.g. to slow down the PCLK frequency to reduce power consumption or to reduce the memory throughput. + * @note This function doesn't cause the hardware to update the PCLK immediately but to record the new frequency and set a flag internally. + * Next time when start a new transaction, the driver will update the PCLK automatically. + * + * @param panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()` + * @param freq_hz Frequency of pixel clock, in Hz + * @return + * - ESP_ERR_NOT_SUPPORTED if frequency is unreachable + * - ESP_ERR_INVALID_ARG if parameter panel is invalid + * - ESP_OK on success + */ +esp_err_t esp_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz); + #endif // SOC_LCD_RGB_SUPPORTED #ifdef __cplusplus diff --git a/components/esp_lcd/src/esp_lcd_rgb_panel.c b/components/esp_lcd/src/esp_lcd_rgb_panel.c index 44687dd19f..5aa7720f40 100644 --- a/components/esp_lcd/src/esp_lcd_rgb_panel.c +++ b/components/esp_lcd/src/esp_lcd_rgb_panel.c @@ -88,10 +88,12 @@ struct esp_rgb_panel_t { void *user_ctx; // Reserved user's data of callback functions int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window + portMUX_TYPE spinlock; // to protect panel specific resource from concurrent access (e.g. between task and ISR) struct { unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num` unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done unsigned int fb_in_psram: 1; // Whether the frame buffer is in PSRAM + unsigned int need_update_pclk: 1; // Whether to update the PCLK before start a new transaction } flags; dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes` }; @@ -187,6 +189,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low; rgb_panel->on_frame_trans_done = rgb_panel_config->on_frame_trans_done; rgb_panel->user_ctx = rgb_panel_config->user_ctx; + rgb_panel->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; // fill function table rgb_panel->base.del = rgb_panel_del; rgb_panel->base.reset = rgb_panel_reset; @@ -227,6 +230,18 @@ err: return ret; } +esp_err_t esp_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz) +{ + ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); + // the pclk frequency will be updated in `lcd_rgb_panel_start_transmission()` + portENTER_CRITICAL(&rgb_panel->spinlock); + rgb_panel->flags.need_update_pclk = true; + rgb_panel->timings.pclk_hz = freq_hz; + portEXIT_CRITICAL(&rgb_panel->spinlock); + return ESP_OK; +} + static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel) { esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); @@ -499,6 +514,15 @@ static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel) // reset FIFO of DMA and LCD, incase there remains old frame data gdma_reset(rgb_panel->dma_chan); lcd_ll_stop(rgb_panel->hal.dev); + + // check whether to update the PCLK frequency + portENTER_CRITICAL_SAFE(&rgb_panel->spinlock); + if (unlikely(rgb_panel->flags.need_update_pclk)) { + rgb_panel->flags.need_update_pclk = false; + rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz); + } + portEXIT_CRITICAL_SAFE(&rgb_panel->spinlock); + lcd_ll_fifo_reset(rgb_panel->hal.dev); gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes); // delay 1us is sufficient for DMA to pass data to LCD FIFO diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index bb6ef9238a..f47c67b937 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -85,6 +85,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "emac_hal.c") endif() + if(CONFIG_SOC_LCDCAM_SUPPORTED) + list(APPEND srcs "lcd_hal.c") + endif() + if(${target} STREQUAL "esp32") list(APPEND srcs "dac_hal.c" @@ -119,7 +123,6 @@ if(NOT BOOTLOADER_BUILD) if(${target} STREQUAL "esp32s3") list(APPEND srcs "ds_hal.c" - "lcd_hal.c" "spi_flash_hal_gpspi.c" "spi_slave_hd_hal.c" "touch_sensor_hal.c" diff --git a/components/hal/linker.lf b/components/hal/linker.lf index a2fa83ba21..51d054b5e4 100644 --- a/components/hal/linker.lf +++ b/components/hal/linker.lf @@ -28,3 +28,5 @@ entries: timer_hal_iram (noflash) if GPIO_CTRL_FUNC_IN_IRAM = y: gpio_hal: gpio_hal_intr_disable (noflash) + if LCD_RGB_ISR_IRAM_SAFE = y: + lcd_hal: lcd_hal_cal_pclk_freq (noflash) From 09c192c7f1647768a38a5b45b3933b5e67c53bcb Mon Sep 17 00:00:00 2001 From: TDA2030 <1932489836@qq.com> Date: Sat, 7 May 2022 11:27:05 +0800 Subject: [PATCH 4/4] rgb_lcd: optimise rgb_panel_draw_bitmap by using memcpy instead of coping in a nested for loop --- components/esp_lcd/src/esp_lcd_rgb_panel.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/components/esp_lcd/src/esp_lcd_rgb_panel.c b/components/esp_lcd/src/esp_lcd_rgb_panel.c index 5aa7720f40..46424c4323 100644 --- a/components/esp_lcd/src/esp_lcd_rgb_panel.c +++ b/components/esp_lcd/src/esp_lcd_rgb_panel.c @@ -332,19 +332,21 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int // convert the frame buffer to 3D array int bytes_per_pixel = rgb_panel->data_width / 8; int pixels_per_line = rgb_panel->timings.h_res; + uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line; const uint8_t *from = (const uint8_t *)color_data; - uint8_t (*to)[pixels_per_line][bytes_per_pixel] = (uint8_t (*)[pixels_per_line][bytes_per_pixel])rgb_panel->fb; // manipulate the frame buffer - for (int j = y_start; j < y_end; j++) { - for (int i = x_start; i < x_end; i++) { - for (int k = 0; k < bytes_per_pixel; k++) { - to[j][i][k] = *from++; - } - } + uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel; + uint8_t *to = rgb_panel->fb + (y_start * pixels_per_line + x_start) * bytes_per_pixel; + for (int y = y_start; y < y_end; y++) { + memcpy(to, from, copy_bytes_per_line); + to += bytes_per_line; + from += copy_bytes_per_line; } + if (rgb_panel->flags.fb_in_psram) { // CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back - Cache_WriteBack_Addr((uint32_t)&to[y_start][0][0], (y_end - y_start) * rgb_panel->timings.h_res * bytes_per_pixel); + uint32_t bytes_to_flush = (y_end - y_start) * bytes_per_line; + Cache_WriteBack_Addr((uint32_t)(rgb_panel->fb + y_start * bytes_per_line), bytes_to_flush); } // restart the new transmission