rgb_lcd: support fractional clock divisor

This commit is contained in:
morris 2022-05-30 16:09:40 +08:00
parent 8dd26e9e9f
commit 843279d287
5 changed files with 127 additions and 43 deletions

View File

@ -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;

View File

@ -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);
break;
}
lcd_ll_select_clk_src(panel->hal.dev, clk_src);
return ret;
}

View File

@ -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;

View File

@ -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 <stdint.h>
#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

View File

@ -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;
}
}