feat(lcd): pre-support rgb and i80 lcd driver on esp32p4

added LL functions for LCD_CAM module, only the LCD part
This commit is contained in:
morris
2023-11-13 17:50:29 +08:00
parent 4bb8f5c577
commit e86acbe556
19 changed files with 1109 additions and 133 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -24,6 +24,12 @@ extern "C" {
#define LCD_PERIPH_CLOCK_PRE_SCALE (2) // This is the minimum divider that can be applied to LCD peripheral
#if SOC_PERIPH_CLK_CTRL_SHARED
#define LCD_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define LCD_CLOCK_SRC_ATOMIC()
#endif
#if SOC_LCDCAM_SUPPORTED
typedef enum {

View File

@@ -128,7 +128,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
esp_err_t ret = ESP_OK;
esp_lcd_i80_bus_t *bus = NULL;
ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// although I2S bus supports up to 24 parallel data lines, we restrict users to only use 8 or 16 bit width, due to limited GPIO numbers
// although I2S bus supports up to 24 parallel data lines, we restrict users to only use 8 or 16 bit width
ESP_GOTO_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG, err,
TAG, "invalid bus width:%d", bus_config->bus_width);
size_t max_transfer_bytes = (bus_config->max_transfer_bytes + 3) & ~0x03; // align up to 4 bytes

View File

@@ -131,7 +131,10 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
#endif
esp_err_t ret = ESP_OK;
esp_lcd_i80_bus_t *bus = NULL;
ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// although LCD_CAM can support up to 24 data lines, we restrict users to only use 8 or 16 bit width
ESP_RETURN_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG,
TAG, "invalid bus width:%d", bus_config->bus_width);
size_t num_dma_nodes = bus_config->max_transfer_bytes / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1;
// DMA descriptors must be placed in internal SRAM
bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
@@ -153,13 +156,15 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
}
// initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&bus->hal, bus_id);
// reset peripheral and FIFO
lcd_ll_reset(bus->hal.dev);
lcd_ll_fifo_reset(bus->hal.dev);
lcd_ll_enable_clock(bus->hal.dev, true);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_enable_clock(bus->hal.dev, true);
}
// set peripheral clock resolution
ret = lcd_i80_select_periph_clock(bus, bus_config->clk_src);
ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock %d failed", bus_config->clk_src);
// reset peripheral and FIFO after we select a correct clock source
lcd_ll_reset(bus->hal.dev);
lcd_ll_fifo_reset(bus->hal.dev);
// install interrupt service, (LCD peripheral shares the same interrupt source with Camera peripheral with different mask)
// interrupt is disabled by default
int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED;
@@ -172,12 +177,17 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
// install DMA service
bus->psram_trans_align = bus_config->psram_trans_align;
bus->sram_trans_align = bus_config->sram_trans_align;
bus->bus_width = bus_config->bus_width;
ret = lcd_i80_init_dma_link(bus);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
// enable 8080 mode and set bus width
// disable RGB-LCD mode
lcd_ll_enable_rgb_mode(bus->hal.dev, false);
lcd_ll_set_data_width(bus->hal.dev, bus_config->bus_width);
bus->bus_width = bus_config->bus_width;
// disable YUV-RGB converter
lcd_ll_enable_rgb_yuv_convert(bus->hal.dev, false);
// set how much data to read from DMA each time
lcd_ll_set_dma_read_stride(bus->hal.dev, bus->bus_width);
// sometime, we need to change the output data order: ABAB->BABA
lcd_ll_set_swizzle_mode(bus->hal.dev, LCD_LL_SWIZZLE_AB2BA);
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(bus->hal.dev, true);
// enable trans done interrupt
@@ -435,8 +445,9 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
// switch devices if necessary
lcd_i80_switch_devices(cur_device, next_device);
// set data format
lcd_ll_reverse_bit_order(bus->hal.dev, false);
lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->lcd_param_bits > bus->bus_width);
lcd_ll_reverse_dma_data_bit_order(bus->hal.dev, false);
// whether to swap the adjacent data bytes
lcd_ll_enable_swizzle(bus->hal.dev, next_device->lcd_param_bits > bus->bus_width);
bus->cur_trans = NULL;
bus->cur_device = next_device;
// package a transaction
@@ -514,9 +525,11 @@ static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_c
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");
// force to use integer division, as fractional division might lead to clock jitter
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);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_select_clk_src(bus->hal.dev, clk_src);
// force to use integer division, as fractional division might lead to clock jitter
lcd_ll_set_group_clock_coeff(bus->hal.dev, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0);
}
// save the resolution of the i80 bus
bus->resolution_hz = src_clk_hz / LCD_PERIPH_CLOCK_PRE_SCALE;
@@ -578,6 +591,8 @@ static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const
if (!valid_gpio) {
return ESP_ERR_INVALID_ARG;
}
// Set the number of output data lines
lcd_ll_set_data_wire_width(bus->hal.dev, bus_config->bus_width);
// connect peripheral signals via GPIO matrix
for (size_t i = 0; i < bus_config->bus_width; i++) {
gpio_set_direction(bus_config->data_gpio_nums[i], GPIO_MODE_OUTPUT);
@@ -700,8 +715,8 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
// switch devices if necessary
lcd_i80_switch_devices(cur_device, next_device);
// only reverse data bit/bytes for color data
lcd_ll_reverse_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->flags.swap_color_bytes);
lcd_ll_reverse_dma_data_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
lcd_ll_enable_swizzle(bus->hal.dev, next_device->flags.swap_color_bytes);
bus->cur_trans = trans_desc;
bus->cur_device = next_device;
// mount data to DMA links

View File

@@ -35,6 +35,7 @@
#include "esp_private/periph_ctrl.h"
#include "esp_psram.h"
#include "esp_lcd_common.h"
#include "esp_memory_utils.h"
#include "soc/lcd_periph.h"
#include "hal/lcd_hal.h"
#include "hal/lcd_ll.h"
@@ -176,7 +177,9 @@ static esp_err_t lcd_rgb_panel_alloc_frame_buffers(const esp_lcd_rgb_panel_confi
static esp_err_t lcd_rgb_panel_destory(esp_rgb_panel_t *rgb_panel)
{
lcd_ll_enable_clock(rgb_panel->hal.dev, false);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_enable_clock(rgb_panel->hal.dev, false);
}
if (rgb_panel->panel_id >= 0) {
PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_signals.panels[rgb_panel->panel_id].module, ref_count) {
if (ref_count == 0) {
@@ -218,19 +221,20 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
#endif
esp_err_t ret = ESP_OK;
esp_rgb_panel_t *rgb_panel = NULL;
ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16 || rgb_panel_config->data_width == 8,
ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported data width %d", rgb_panel_config->data_width);
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, err, TAG, "double_fb conflicts with no_fb");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->num_fbs != 2 && rgb_panel_config->flags.double_fb),
ESP_ERR_INVALID_ARG, err, TAG, "num_fbs conflicts with double_fb");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, err, TAG, "num_fbs conflicts with no_fb");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
ESP_ERR_INVALID_ARG, err, TAG, "must set bounce buffer if there's no frame buffer");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px),
ESP_ERR_INVALID_ARG, err, TAG, "refresh on demand is not supported under bounce buffer mode");
ESP_RETURN_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid parameter");
size_t data_width = rgb_panel_config->data_width;
ESP_RETURN_ON_FALSE((data_width >= 8) && (data_width <= SOC_LCD_RGB_DATA_WIDTH) && ((data_width & (data_width - 1)) == 0), ESP_ERR_INVALID_ARG,
TAG, "unsupported data width %d", data_width);
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, TAG, "double_fb conflicts with no_fb");
ESP_RETURN_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->num_fbs != 2 && rgb_panel_config->flags.double_fb),
ESP_ERR_INVALID_ARG, TAG, "num_fbs conflicts with double_fb");
ESP_RETURN_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, TAG, "num_fbs conflicts with no_fb");
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
ESP_ERR_INVALID_ARG, TAG, "must set bounce buffer if there's no frame buffer");
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px),
ESP_ERR_INVALID_ARG, TAG, "refresh on demand is not supported under bounce buffer mode");
// determine number of framebuffers
size_t num_fbs = 1;
@@ -241,11 +245,11 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
} else if (rgb_panel_config->num_fbs > 0) {
num_fbs = rgb_panel_config->num_fbs;
}
ESP_GOTO_ON_FALSE(num_fbs <= RGB_LCD_PANEL_MAX_FB_NUM, ESP_ERR_INVALID_ARG, err, TAG, "too many frame buffers");
ESP_RETURN_ON_FALSE(num_fbs <= RGB_LCD_PANEL_MAX_FB_NUM, ESP_ERR_INVALID_ARG, TAG, "too many frame buffers");
// bpp defaults to the number of data lines, but for serial RGB interface, they're not equal
// e.g. for serial RGB 8-bit interface, data lines are 8, whereas the bpp is 24 (RGB888)
size_t fb_bits_per_pixel = rgb_panel_config->data_width;
size_t fb_bits_per_pixel = data_width;
if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set
fb_bits_per_pixel = rgb_panel_config->bits_per_pixel;
}
@@ -255,8 +259,8 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
size_t expect_bb_eof_count = 0;
if (bb_size) {
// we want the bounce can always end in the second buffer
ESP_GOTO_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, err, TAG,
"fb size must be even multiple of bounce buffer size");
ESP_RETURN_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, TAG,
"fb size must be even multiple of bounce buffer size");
expect_bb_eof_count = fb_size / bb_size;
}
@@ -299,11 +303,16 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
// initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&rgb_panel->hal, panel_id);
// enable clock gating
lcd_ll_enable_clock(rgb_panel->hal.dev, true);
// enable clock
LCD_CLOCK_SRC_ATOMIC() {
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");
// reset peripheral and FIFO after we select a correct clock source
lcd_ll_fifo_reset(rgb_panel->hal.dev);
lcd_ll_reset(rgb_panel->hal.dev);
// set minimal PCLK divider
// A limitation in the hardware, if the LCD_PCLK == LCD_CLK, then the PCLK polarity can't be adjustable
if (!(rgb_panel_config->timings.flags.pclk_active_neg || rgb_panel_config->timings.flags.pclk_idle_high)) {
@@ -512,13 +521,17 @@ 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);
// 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, rgb_panel->lcd_clk_flags);
hal_utils_clk_div_t lcd_clk_div = {};
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz, rgb_panel->lcd_clk_flags, &lcd_clk_div);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_set_group_clock_coeff(rgb_panel->hal.dev, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator);
}
// 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);
// enable RGB mode and set data width
lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true);
lcd_ll_set_data_width(rgb_panel->hal.dev, rgb_panel->data_width);
lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->data_width);
lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1); // enable data phase only
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(rgb_panel->hal.dev, true);
@@ -829,6 +842,8 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_
if (!valid_gpio) {
return ESP_ERR_INVALID_ARG;
}
// Set the number of output data lines
lcd_ll_set_data_wire_width(panel->hal.dev, panel_config->data_width);
// connect peripheral signals via GPIO matrix
for (size_t i = 0; i < panel_config->data_width; i++) {
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->data_gpio_nums[i]], PIN_FUNC_GPIO);
@@ -878,7 +893,9 @@ static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_cloc
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");
panel->src_clk_hz = src_clk_hz;
lcd_ll_select_clk_src(panel->hal.dev, clk_src);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_select_clk_src(panel->hal.dev, clk_src);
}
// create pm lock based on different clock source
// clock sources like PLL and XTAL will be turned off in light sleep
@@ -1099,10 +1116,14 @@ static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
IRAM_ATTR static void lcd_rgb_panel_try_update_pclk(esp_rgb_panel_t *rgb_panel)
{
hal_utils_clk_div_t lcd_clk_div = {};
portENTER_CRITICAL_ISR(&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, rgb_panel->lcd_clk_flags);
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz, rgb_panel->lcd_clk_flags, &lcd_clk_div);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_set_group_clock_coeff(rgb_panel->hal.dev, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator);
}
}
portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
}