mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-07 14:44:32 +02:00
Merge branch 'feature/little_improvement_for_rgb_panel' into 'master'
rgb_lcd: optimize draw_bitmap && PCLK fractional divisor See merge request espressif/esp-idf!18148
This commit is contained in:
@@ -78,7 +78,7 @@ typedef struct {
|
|||||||
/**
|
/**
|
||||||
* @brief Declare the prototype of the function that will be invoked when panel IO finishes transferring color data
|
* @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] edata Panel event data, fed by driver
|
||||||
* @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_config_t`
|
* @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
|
* @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);
|
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
|
#endif // SOC_LCD_RGB_SUPPORTED
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@@ -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");
|
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
|
// check if pixel clock setting is valid
|
||||||
uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz;
|
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);
|
"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);
|
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");
|
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;
|
esp_err_t ret = ESP_OK;
|
||||||
// force to use integer division, as fractional division might lead to clock jitter
|
// 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) {
|
switch (clk_src) {
|
||||||
case LCD_CLK_SRC_PLL160M:
|
case LCD_CLK_SRC_PLL160M:
|
||||||
bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
|
bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
|
||||||
|
@@ -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_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_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 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_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 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);
|
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel);
|
||||||
@@ -81,17 +81,19 @@ struct esp_rgb_panel_t {
|
|||||||
uint8_t *fb; // Frame buffer
|
uint8_t *fb; // Frame buffer
|
||||||
size_t fb_size; // Size of 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"
|
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)
|
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
|
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
|
esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done
|
||||||
void *user_ctx; // Reserved user's data of callback functions
|
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 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
|
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 {
|
struct {
|
||||||
unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
|
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 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 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;
|
} flags;
|
||||||
dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes`
|
dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes`
|
||||||
};
|
};
|
||||||
@@ -159,9 +161,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;
|
rgb_panel->flags.fb_in_psram = alloc_from_psram;
|
||||||
// initialize HAL layer, so we can call LL APIs later
|
// initialize HAL layer, so we can call LL APIs later
|
||||||
lcd_hal_init(&rgb_panel->hal, panel_id);
|
lcd_hal_init(&rgb_panel->hal, panel_id);
|
||||||
// set peripheral clock resolution
|
// enable clock gating
|
||||||
ret = lcd_rgb_panel_select_periph_clock(rgb_panel, rgb_panel_config->clk_src);
|
lcd_ll_enable_clock(rgb_panel->hal.dev, true);
|
||||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed");
|
// 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)
|
// 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;
|
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,
|
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags,
|
||||||
@@ -185,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->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->on_frame_trans_done = rgb_panel_config->on_frame_trans_done;
|
||||||
rgb_panel->user_ctx = rgb_panel_config->user_ctx;
|
rgb_panel->user_ctx = rgb_panel_config->user_ctx;
|
||||||
|
rgb_panel->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||||
// fill function table
|
// fill function table
|
||||||
rgb_panel->base.del = rgb_panel_del;
|
rgb_panel->base.del = rgb_panel_del;
|
||||||
rgb_panel->base.reset = rgb_panel_reset;
|
rgb_panel->base.reset = rgb_panel_reset;
|
||||||
@@ -225,6 +230,18 @@ err:
|
|||||||
return ret;
|
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)
|
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);
|
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
|
||||||
@@ -232,6 +249,7 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
|
|||||||
gdma_disconnect(rgb_panel->dma_chan);
|
gdma_disconnect(rgb_panel->dma_chan);
|
||||||
gdma_del_channel(rgb_panel->dma_chan);
|
gdma_del_channel(rgb_panel->dma_chan);
|
||||||
esp_intr_free(rgb_panel->intr);
|
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);
|
periph_module_disable(lcd_periph_signals.panels[panel_id].module);
|
||||||
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
|
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
|
||||||
free(rgb_panel->fb);
|
free(rgb_panel->fb);
|
||||||
@@ -256,14 +274,8 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
|
|||||||
{
|
{
|
||||||
esp_err_t ret = ESP_OK;
|
esp_err_t ret = ESP_OK;
|
||||||
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
|
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
|
||||||
// configure clock
|
// set pixel clock frequency
|
||||||
lcd_ll_enable_clock(rgb_panel->hal.dev, true);
|
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz);
|
||||||
// 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;
|
|
||||||
// pixel clock phase and polarity
|
// 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_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);
|
lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg);
|
||||||
@@ -299,7 +311,6 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
|
|||||||
lcd_rgb_panel_start_transmission(rgb_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);
|
ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%uHz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz);
|
||||||
err:
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,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
|
// convert the frame buffer to 3D array
|
||||||
int bytes_per_pixel = rgb_panel->data_width / 8;
|
int bytes_per_pixel = rgb_panel->data_width / 8;
|
||||||
int pixels_per_line = rgb_panel->timings.h_res;
|
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;
|
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
|
// manipulate the frame buffer
|
||||||
for (int j = y_start; j < y_end; j++) {
|
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
|
||||||
for (int i = x_start; i < x_end; i++) {
|
uint8_t *to = rgb_panel->fb + (y_start * pixels_per_line + x_start) * bytes_per_pixel;
|
||||||
for (int k = 0; k < bytes_per_pixel; k++) {
|
for (int y = y_start; y < y_end; y++) {
|
||||||
to[j][i][k] = *from++;
|
memcpy(to, from, copy_bytes_per_line);
|
||||||
}
|
to += bytes_per_line;
|
||||||
}
|
from += copy_bytes_per_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rgb_panel->flags.fb_in_psram) {
|
if (rgb_panel->flags.fb_in_psram) {
|
||||||
// CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
|
// 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
|
// restart the new transmission
|
||||||
@@ -442,14 +455,12 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_
|
|||||||
return ESP_OK;
|
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;
|
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) {
|
switch (clk_src) {
|
||||||
case LCD_CLK_SRC_PLL160M:
|
case LCD_CLK_SRC_PLL160M:
|
||||||
panel->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
|
panel->src_clk_hz = 160000000;
|
||||||
#if CONFIG_PM_ENABLE
|
#if CONFIG_PM_ENABLE
|
||||||
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "rgb_panel", &panel->pm_lock);
|
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");
|
ESP_RETURN_ON_ERROR(ret, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
|
||||||
@@ -459,12 +470,13 @@ static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_c
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case LCD_CLK_SRC_XTAL:
|
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;
|
break;
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
lcd_ll_select_clk_src(panel->hal.dev, clk_src);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +516,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
|
// reset FIFO of DMA and LCD, incase there remains old frame data
|
||||||
gdma_reset(rgb_panel->dma_chan);
|
gdma_reset(rgb_panel->dma_chan);
|
||||||
lcd_ll_stop(rgb_panel->hal.dev);
|
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);
|
lcd_ll_fifo_reset(rgb_panel->hal.dev);
|
||||||
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes);
|
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes);
|
||||||
// delay 1us is sufficient for DMA to pass data to LCD FIFO
|
// delay 1us is sufficient for DMA to pass data to LCD FIFO
|
||||||
|
@@ -85,6 +85,10 @@ if(NOT BOOTLOADER_BUILD)
|
|||||||
list(APPEND srcs "emac_hal.c")
|
list(APPEND srcs "emac_hal.c")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_SOC_LCDCAM_SUPPORTED)
|
||||||
|
list(APPEND srcs "lcd_hal.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(${target} STREQUAL "esp32")
|
if(${target} STREQUAL "esp32")
|
||||||
list(APPEND srcs
|
list(APPEND srcs
|
||||||
"dac_hal.c"
|
"dac_hal.c"
|
||||||
@@ -119,7 +123,6 @@ if(NOT BOOTLOADER_BUILD)
|
|||||||
if(${target} STREQUAL "esp32s3")
|
if(${target} STREQUAL "esp32s3")
|
||||||
list(APPEND srcs
|
list(APPEND srcs
|
||||||
"ds_hal.c"
|
"ds_hal.c"
|
||||||
"lcd_hal.c"
|
|
||||||
"spi_flash_hal_gpspi.c"
|
"spi_flash_hal_gpspi.c"
|
||||||
"spi_slave_hd_hal.c"
|
"spi_slave_hd_hal.c"
|
||||||
"touch_sensor_hal.c"
|
"touch_sensor_hal.c"
|
||||||
|
@@ -23,8 +23,9 @@ extern "C" {
|
|||||||
#define LCD_LL_EVENT_VSYNC_END (1 << 0)
|
#define LCD_LL_EVENT_VSYNC_END (1 << 0)
|
||||||
#define LCD_LL_EVENT_TRANS_DONE (1 << 1)
|
#define LCD_LL_EVENT_TRANS_DONE (1 << 1)
|
||||||
|
|
||||||
// Maximum coefficient of clock prescaler
|
#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_CLOCK_PRESCALE_MAX (64)
|
#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
|
* @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 dev LCD register base address
|
||||||
* @param src Clock source
|
* @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) {
|
switch (src) {
|
||||||
case LCD_CLK_SRC_PLL160M:
|
case LCD_CLK_SRC_PLL160M:
|
||||||
dev->lcd_clock.lcd_clk_sel = 3;
|
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;
|
dev->lcd_clock.lcd_clk_sel = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// disble LCD clock source
|
// disable LCD clock source
|
||||||
dev->lcd_clock.lcd_clk_sel = 0;
|
dev->lcd_clock.lcd_clk_sel = 0;
|
||||||
HAL_ASSERT(false && "unsupported clock source");
|
HAL_ASSERT(false);
|
||||||
break;
|
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
|
* @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))
|
__attribute__((always_inline))
|
||||||
static inline void lcd_ll_set_pixel_clock_prescale(lcd_cam_dev_t *dev, uint32_t prescale)
|
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)
|
// Formula: pixel_clk = lcd_clk / (1 + clkcnt_n)
|
||||||
// clkcnt_n can't be zero
|
// clkcnt_n can't be zero
|
||||||
uint32_t scale = 1;
|
uint32_t scale = 1;
|
||||||
|
@@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief LCD peripheral SOC layer handle
|
||||||
|
*/
|
||||||
typedef struct lcd_cam_dev_t *lcd_soc_handle_t;
|
typedef struct lcd_cam_dev_t *lcd_soc_handle_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief LCD HAL layer context
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
lcd_soc_handle_t dev;
|
lcd_soc_handle_t dev; // SOC layer handle
|
||||||
} lcd_hal_context_t;
|
} 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);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "hal/lcd_hal.h"
|
#include "hal/lcd_hal.h"
|
||||||
#include "hal/lcd_ll.h"
|
#include "hal/lcd_ll.h"
|
||||||
|
#include "hal/log.h"
|
||||||
|
|
||||||
void lcd_hal_init(lcd_hal_context_t *hal, int id)
|
void lcd_hal_init(lcd_hal_context_t *hal, int id)
|
||||||
{
|
{
|
||||||
hal->dev = LCD_LL_GET_HW(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -28,3 +28,5 @@ entries:
|
|||||||
timer_hal_iram (noflash)
|
timer_hal_iram (noflash)
|
||||||
if GPIO_CTRL_FUNC_IN_IRAM = y:
|
if GPIO_CTRL_FUNC_IN_IRAM = y:
|
||||||
gpio_hal: gpio_hal_intr_disable (noflash)
|
gpio_hal: gpio_hal_intr_disable (noflash)
|
||||||
|
if LCD_RGB_ISR_IRAM_SAFE = y:
|
||||||
|
lcd_hal: lcd_hal_cal_pclk_freq (noflash)
|
||||||
|
@@ -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
|
## LCD tjpgd example
|
||||||
|
|
||||||
|
@@ -7,4 +7,11 @@ menu "Example Configuration"
|
|||||||
help
|
help
|
||||||
This option can be chosen when using 8-line lcd.
|
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
|
endmenu
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "sdkconfig.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "esp_lcd_panel_io.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.
|
// 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
|
// More means more memory use, but less overhead for setting up / finishing transfers. Make sure 240
|
||||||
// is dividable by this.
|
// 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
|
// The number of frames to show before rotate the graph
|
||||||
#define ROTATE_FRAME 30
|
#define ROTATE_FRAME 30
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user