mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-31 06:12:42 +00:00
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:
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user