lcd: add pm lock

pull/7554/head
morris 2021-08-20 11:48:33 +08:00
rodzic a6661bdf90
commit 02e470bc50
12 zmienionych plików z 297 dodań i 147 usunięć

Wyświetl plik

@ -1,20 +1,5 @@
menu "LCD and Touch Panel"
menu "LCD Peripheral Configuration"
choice LCD_PERIPH_CLK_SRC
prompt "Select clock source for LCD peripheral"
default LCD_PERIPH_CLK_SRC_PLL160M
help
The peripheral clock is where LCD bus clock derives from.
Each clock source has its unique feature, e.g.
1. XTAL clock can help LCD work stable when DFS is enabled
2. PLL160M can achieve higher pixel clock resolution
config LCD_PERIPH_CLK_SRC_PLL160M
bool "PLL_160M clock"
config LCD_PERIPH_CLK_SRC_XTAL
bool "XTAL clock"
endchoice # LCD_PERIPH_CLK_SRC
config LCD_PANEL_IO_FORMAT_BUF_SIZE
int "LCD panel io format buffer size"
default 32

Wyświetl plik

@ -9,6 +9,7 @@
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "soc/soc_caps.h"
#include "hal/lcd_types.h"
#ifdef __cplusplus
extern "C" {
@ -130,6 +131,7 @@ esp_err_t esp_lcd_new_panel_io_i2c(esp_lcd_i2c_bus_handle_t bus, const esp_lcd_p
typedef struct {
int dc_gpio_num; /*!< GPIO used for D/C line */
int wr_gpio_num; /*!< GPIO used for WR line */
lcd_clock_source_t clk_src; /*!< Clock source for the I80 LCD peripheral */
int data_gpio_nums[SOC_LCD_I80_BUS_WIDTH]; /*!< GPIOs used for data lines */
size_t bus_width; /*!< Number of data lines, 8 or 16 */
size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */

Wyświetl plik

@ -9,6 +9,7 @@
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "soc/soc_caps.h"
#include "hal/lcd_types.h"
#ifdef __cplusplus
extern "C" {
@ -41,6 +42,7 @@ typedef struct {
* @brief LCD RGB panel configuration structure
*/
typedef struct {
lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters */
size_t data_width; /*!< Number of data lines */
int hsync_gpio_num; /*!< GPIO used for HSYNC signal */

Wyświetl plik

@ -5,7 +5,6 @@
*/
#include "freertos/FreeRTOS.h"
#include "soc/rtc.h" // for querying XTAL clock
#include "soc/soc_caps.h"
#include "esp_lcd_common.h"
#if SOC_LCDCAM_SUPPORTED
@ -78,25 +77,6 @@ void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id)
break;
}
}
unsigned long lcd_com_select_periph_clock(lcd_hal_context_t *hal)
{
unsigned long resolution_hz;
int clock_source;
#if CONFIG_LCD_PERIPH_CLK_SRC_PLL160M
resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
clock_source = LCD_LL_CLOCK_SRC_PLL160M;
#elif CONFIG_LCD_PERIPH_CLK_SRC_XTAL
resolution_hz = rtc_clk_xtal_freq_get() * 1000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
clock_source = LCD_LL_CLOCK_SRC_XTAL;
#else
#error "invalid LCD peripheral clock source"
#endif
lcd_ll_set_group_clock_src(hal->dev, clock_source, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0);
return resolution_hz;
}
#endif // SOC_LCDCAM_SUPPORTED
void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len)

Wyświetl plik

@ -41,18 +41,6 @@ int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj)
* @param member_id member ID
*/
void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id);
/**
* @brief Select clock source and return peripheral clock resolution (in Hz)
*
* @note The clock source selection is injected by the Kconfig system,
* dynamic switching peripheral clock source is not supported in driver.
*
* @param hal HAL object
* @return Peripheral clock resolution, in Hz
*/
unsigned long lcd_com_select_periph_clock(lcd_hal_context_t *hal);
#endif // SOC_LCDCAM_SUPPORTED
/**

Wyświetl plik

@ -23,6 +23,7 @@
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h"
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_common.h"
@ -48,7 +49,7 @@ typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t;
static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io);
static unsigned long i2s_lcd_select_periph_clock(i2s_hal_context_t *hal);
static esp_err_t i2s_lcd_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t src);
static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus);
static esp_err_t i2s_lcd_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus);
@ -63,9 +64,10 @@ struct esp_lcd_i80_bus_t {
int dc_gpio_num; // GPIO used for DC line
int wr_gpio_num; // GPIO used for WR line
intr_handle_t intr; // LCD peripheral interrupt handle
esp_pm_lock_handle_t pm_lock; // lock APB frequency when necessary
size_t num_dma_nodes; // Number of DMA descriptors
uint8_t *format_buffer;// The driver allocates an internal buffer for DMA to do data format transformer
size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source
unsigned long resolution_hz; // LCD_CLK resolution, determined by selected clock source
lcd_i80_trans_descriptor_t *cur_trans; // Current transaction
lcd_panel_io_i80_t *cur_device; // Current working device
LIST_HEAD(i80_device_list, lcd_panel_io_i80_t) device_list; // Head of i80 device list
@ -114,9 +116,9 @@ 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_arg, TAG, "invalid argument");
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
ESP_GOTO_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG, err_arg,
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
#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
@ -127,23 +129,25 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
size_t num_dma_nodes = 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);
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, no_mem_bus, TAG, "no mem for i80 bus");
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus");
bus->num_dma_nodes = num_dma_nodes;
bus->bus_id = -1;
#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
// transform format for LCD commands, parameters and color data, so we need a big buffer
bus->format_buffer = heap_caps_calloc(1, max_transfer_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
#else
// only transform format for LCD parameters, buffer size depends on specific LCD, set at compile time
bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
#endif
ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, no_mem_format, TAG, "no mem for format buffer");
#endif // SOC_I2S_TRANS_SIZE_ALIGN_WORD
ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer");
// I2S0 has the LCD mode, but the LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform
ESP_GOTO_ON_ERROR(i2s_priv_register_object(bus, 0), no_slot, TAG, "register to I2S platform failed");
ESP_GOTO_ON_ERROR(i2s_priv_register_object(bus, 0), err, TAG, "register to I2S platform failed");
bus->bus_id = 0;
// initialize HAL layer
i2s_hal_init(&bus->hal, bus->bus_id);
// set peripheral clock resolution
bus->resolution_hz = i2s_lcd_select_periph_clock(&bus->hal);
ret = i2s_lcd_select_periph_clock(bus, bus_config->clk_src);
ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed");
// reset peripheral, DMA channel and FIFO
i2s_ll_tx_reset(bus->hal.dev);
i2s_ll_tx_reset_dma(bus->hal.dev);
@ -154,7 +158,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus->bus_id].irq_id, isr_flags,
(uint32_t)i2s_ll_get_intr_status_reg(bus->hal.dev),
I2S_LL_EVENT_TX_EOF, lcd_default_isr_handler, bus, &bus->intr);
ESP_GOTO_ON_ERROR(ret, no_int, TAG, "install interrupt failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
i2s_ll_enable_intr(bus->hal.dev, I2S_LL_EVENT_TX_EOF, false); // disable interrupt temporarily
i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); // clear pending interrupt
// initialize DMA link
@ -178,26 +182,32 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
i2s_lcd_trigger_quick_trans_done_event(bus);
// configure GPIO
ret = i2s_lcd_configure_gpio(bus, bus_config);
ESP_GOTO_ON_ERROR(ret, invalid_gpio, TAG, "configure GPIO failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
// fill other i80 bus runtime parameters
LIST_INIT(&bus->device_list); // initialize device list head
bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
bus->dc_gpio_num = bus_config->dc_gpio_num;
bus->wr_gpio_num = bus_config->wr_gpio_num;
*ret_bus = bus;
ESP_LOGD(TAG, "new i80 bus(%d) @%p, %zu dma nodes, resolution %zuHz", bus->bus_id, bus, bus->num_dma_nodes, bus->resolution_hz);
ESP_LOGD(TAG, "new i80 bus(%d) @%p, %zu dma nodes, resolution %luHz", bus->bus_id, bus, bus->num_dma_nodes, bus->resolution_hz);
return ESP_OK;
invalid_gpio:
esp_intr_free(bus->intr);
no_int:
i2s_priv_deregister_object(bus->bus_id);
no_slot:
free(bus->format_buffer);
no_mem_format:
free(bus);
no_mem_bus:
err_arg:
err:
if (bus) {
if (bus->intr) {
esp_intr_free(bus->intr);
}
if (bus->bus_id >= 0) {
i2s_priv_deregister_object(bus->bus_id);
}
if (bus->format_buffer) {
free(bus->format_buffer);
}
if (bus->pm_lock) {
esp_pm_lock_delete(bus->pm_lock);
}
free(bus);
}
return ret;
}
@ -207,8 +217,11 @@ esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty");
int bus_id = bus->bus_id;
esp_intr_free(bus->intr);
i2s_priv_deregister_object(bus_id);
esp_intr_free(bus->intr);
if (bus->pm_lock) {
esp_pm_lock_delete(bus->pm_lock);
}
free(bus->format_buffer);
free(bus);
ESP_LOGD(TAG, "del i80 bus(%d)", bus_id);
@ -452,6 +465,10 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
// delay a while, wait for DMA data beeing feed to I2S FIFO
// in fact, this is only needed when LCD pixel clock is set too high
esp_rom_delay_us(1);
// increase the pm lock reference count before starting a new transaction
if (bus->pm_lock) {
esp_pm_lock_acquire(bus->pm_lock);
}
i2s_ll_tx_start(bus->hal.dev);
// polling the trans done event
while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
@ -470,6 +487,10 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
// polling the trans done event, but don't clear the event status
while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
}
// decrease pm lock reference count
if (bus->pm_lock) {
esp_pm_lock_release(bus->pm_lock);
}
bus->cur_trans = NULL;
return ESP_OK;
}
@ -506,9 +527,17 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first
i2s_ll_start_out_link(bus->hal.dev);
esp_rom_delay_us(1);
// increase the pm lock reference count before starting a new transaction
if (bus->pm_lock) {
esp_pm_lock_acquire(bus->pm_lock);
}
i2s_ll_tx_start(bus->hal.dev);
// polling the trans done event
while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
// decrease pm lock reference count
if (bus->pm_lock) {
esp_pm_lock_release(bus->pm_lock);
}
bus->cur_trans = NULL;
// sending LCD color data to queue
@ -525,24 +554,30 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
return ESP_OK;
}
static unsigned long i2s_lcd_select_periph_clock(i2s_hal_context_t *hal)
static esp_err_t i2s_lcd_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t src)
{
unsigned long resolution_hz = 0;
i2s_clock_src_t clock_source;
#if CONFIG_LCD_PERIPH_CLK_SRC_PLL160M
resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
clock_source = I2S_CLK_D2CLK;
#else
#error "invalid LCD peripheral clock source"
esp_err_t ret = ESP_OK;
switch (src) {
case LCD_CLK_SRC_PLL160M:
bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
i2s_ll_tx_clk_set_src(bus->hal.dev, I2S_CLK_D2CLK);
#if CONFIG_PM_ENABLE
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "i2s_bus_lcd", &bus->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
ESP_LOGD(TAG, "installed ESP_PM_APB_FREQ_MAX lock");
#endif
i2s_ll_tx_clk_set_src(hal->dev, clock_source);
break;
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported clock source: %d", src);
break;
}
i2s_ll_mclk_div_t clk_cal_config = {
.mclk_div = LCD_PERIPH_CLOCK_PRE_SCALE,
.a = 1,
.b = 0,
};
i2s_ll_tx_set_clk(hal->dev, &clk_cal_config);
return resolution_hz;
i2s_ll_tx_set_clk(bus->hal.dev, &clk_cal_config);
return ret;
}
static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus)
@ -639,6 +674,10 @@ static IRAM_ATTR void lcd_default_isr_handler(void *args)
// process finished transaction
if (trans_desc) {
assert(trans_desc->i80_device == cur_device && "transaction device mismatch");
// decrease pm lock reference count
if (bus->pm_lock) {
esp_pm_lock_release(bus->pm_lock);
}
// device callback
if (trans_desc->trans_done_cb) {
if (trans_desc->trans_done_cb(&cur_device->base, trans_desc->cb_user_data, NULL)) {
@ -679,6 +718,10 @@ static IRAM_ATTR void lcd_default_isr_handler(void *args)
i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first
i2s_ll_start_out_link(bus->hal.dev);
esp_rom_delay_us(1);
// increase the pm lock reference count before starting a new transaction
if (bus->pm_lock) {
esp_pm_lock_acquire(bus->pm_lock);
}
i2s_ll_tx_start(bus->hal.dev);
break; // exit for-each loop
}

Wyświetl plik

@ -18,6 +18,7 @@
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h"
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_rom_gpio.h"
@ -44,6 +45,7 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io);
static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus);
static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus);
static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src);
static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device);
static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc);
@ -55,6 +57,7 @@ struct esp_lcd_i80_bus_t {
lcd_hal_context_t hal; // Hal object
size_t bus_width; // Number of data lines
intr_handle_t intr; // LCD peripheral interrupt handle
esp_pm_lock_handle_t pm_lock; // Power management lock
size_t num_dma_nodes; // Number of DMA descriptors
uint8_t *format_buffer; // The driver allocates an internal buffer for DMA to do data format transformer
size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source
@ -110,17 +113,18 @@ 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_arg, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
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_DMA);
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, no_mem_bus, TAG, "no mem for i80 bus");
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus");
bus->num_dma_nodes = num_dma_nodes;
bus->bus_id = -1;
bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, no_mem_format, TAG, "no mem for format buffer");
ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer");
// register to platform
int bus_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_I80, bus);
ESP_GOTO_ON_FALSE(bus_id >= 0, ESP_ERR_NOT_FOUND, no_slot, TAG, "no free i80 bus slot");
ESP_GOTO_ON_FALSE(bus_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free i80 bus slot");
bus->bus_id = bus_id;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.buses[bus_id].module);
@ -130,20 +134,21 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
lcd_ll_reset(bus->hal.dev);
lcd_ll_fifo_reset(bus->hal.dev);
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);
// install interrupt service, (LCD peripheral shares the same interrupt source with Camera peripheral with different mask)
// interrupt is disabled by default
int isr_flags = ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus_id].irq_id, isr_flags,
(uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev),
LCD_LL_EVENT_TRANS_DONE, lcd_default_isr_handler, bus, &bus->intr);
ESP_GOTO_ON_ERROR(ret, no_int, TAG, "install interrupt failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, false); // disable all interrupts
lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
ret = lcd_i80_init_dma_link(bus);
ESP_GOTO_ON_ERROR(ret, no_dma, TAG, "install DMA failed");
// set peripheral clock resolution
bus->resolution_hz = lcd_com_select_periph_clock(&bus->hal);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
// enable 8080 mode and set bus width
lcd_ll_enable_rgb_mode(bus->hal.dev, false);
lcd_ll_set_data_width(bus->hal.dev, bus_config->bus_width);
@ -157,7 +162,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
lcd_periph_trigger_quick_trans_done_event(bus);
// configure GPIO
ret = lcd_i80_bus_configure_gpio(bus, bus_config);
ESP_GOTO_ON_ERROR(ret, no_gpio, TAG, "configure GPIO failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
// fill other i80 bus runtime parameters
LIST_INIT(&bus->device_list); // initialize device list head
bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
@ -165,20 +170,27 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
ESP_LOGD(TAG, "new i80 bus(%d) @%p, %zu dma nodes", bus_id, bus, bus->num_dma_nodes);
return ESP_OK;
no_gpio:
gdma_disconnect(bus->dma_chan);
gdma_del_channel(bus->dma_chan);
no_dma:
esp_intr_free(bus->intr);
no_int:
periph_module_disable(lcd_periph_signals.buses[bus_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus->bus_id);
no_slot:
free(bus->format_buffer);
no_mem_format:
free(bus);
no_mem_bus:
err_arg:
err:
if (bus) {
if (bus->intr) {
esp_intr_free(bus->intr);
}
if (bus->dma_chan) {
gdma_disconnect(bus->dma_chan);
gdma_del_channel(bus->dma_chan);
}
if (bus->bus_id >= 0) {
periph_module_disable(lcd_periph_signals.buses[bus->bus_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus->bus_id);
}
if (bus->format_buffer) {
free(bus->format_buffer);
}
if (bus->pm_lock) {
esp_pm_lock_delete(bus->pm_lock);
}
free(bus);
}
return ret;
}
@ -188,12 +200,15 @@ esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty");
int bus_id = bus->bus_id;
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus_id);
periph_module_disable(lcd_periph_signals.buses[bus_id].module);
gdma_disconnect(bus->dma_chan);
gdma_del_channel(bus->dma_chan);
esp_intr_free(bus->intr);
periph_module_disable(lcd_periph_signals.buses[bus_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus_id);
free(bus->format_buffer);
if (bus->pm_lock) {
esp_pm_lock_delete(bus->pm_lock);
}
free(bus);
ESP_LOGD(TAG, "del i80 bus(%d)", bus_id);
err:
@ -365,9 +380,17 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
trans_desc->trans_done_cb = NULL; // no callback for parameter transaction
// mount data to DMA links
lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length);
// increase the pm lock reference count before starting a new transaction
if (bus->pm_lock) {
esp_pm_lock_acquire(bus->pm_lock);
}
lcd_start_transaction(bus, trans_desc);
// polling the trans done event, but don't clear the event status
while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE)) {}
// decrease pm lock reference count
if (bus->pm_lock) {
esp_pm_lock_release(bus->pm_lock);
}
return ESP_OK;
}
@ -406,6 +429,29 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
return ESP_OK;
}
static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src)
{
esp_err_t ret = ESP_OK;
lcd_ll_set_group_clock_src(bus->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0);
switch (clk_src) {
case LCD_CLK_SRC_PLL160M:
bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
#if CONFIG_PM_ENABLE
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "i80_bus_lcd", &bus->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
ESP_LOGD(TAG, "installed ESP_PM_APB_FREQ_MAX lock");
#endif
break;
case LCD_CLK_SRC_XTAL:
bus->resolution_hz = rtc_clk_xtal_freq_get() * 1000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
break;
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported clock source: %d", clk_src);
break;
}
return ret;
}
static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus)
{
esp_err_t ret = ESP_OK;
@ -522,6 +568,10 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
// process finished transaction
if (trans_desc) {
assert(trans_desc->i80_device == cur_device && "transaction device mismatch");
// decrease pm lock reference count
if (bus->pm_lock) {
esp_pm_lock_release(bus->pm_lock);
}
// device callback
if (trans_desc->trans_done_cb) {
if (trans_desc->trans_done_cb(&cur_device->base, trans_desc->cb_user_data, NULL)) {
@ -559,6 +609,10 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length);
// enable interrupt again, because the new transaction can trigger new trans done event
esp_intr_enable(bus->intr);
// increase the pm lock reference count before starting a new transaction
if (bus->pm_lock) {
esp_pm_lock_acquire(bus->pm_lock);
}
lcd_start_transaction(bus, trans_desc);
break; // exit for-each loop
}

Wyświetl plik

@ -18,11 +18,13 @@
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "esp_rom_gpio.h"
#include "soc/soc_caps.h"
#include "soc/rtc.h" // for querying XTAL clock
#include "hal/dma_types.h"
#include "hal/gpio_hal.h"
#include "esp_private/gdma.h"
@ -53,6 +55,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_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_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 IRAM_ATTR void lcd_default_isr_handler(void *args);
@ -64,6 +67,7 @@ struct esp_rgb_panel_t {
size_t data_width; // Number of data lines (e.g. for RGB565, the data width is 16)
int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off"
intr_handle_t intr; // LCD peripheral interrupt handle
esp_pm_lock_handle_t pm_lock; // Power management lock
size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer
uint8_t *fb; // Frame buffer
size_t fb_size; // Size of frame buffer
@ -91,8 +95,8 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
{
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_arg, TAG, "invalid parameter");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err_arg, TAG,
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, ESP_ERR_NOT_SUPPORTED, err, TAG,
"unsupported data width %d", rgb_panel_config->data_width);
// calculate the number of DMA descriptors
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * rgb_panel_config->data_width / 8;
@ -102,8 +106,15 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
}
// DMA descriptors must be placed in internal SRAM (requested by DMA)
rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, no_mem_panel, TAG, "no mem for rgb panel");
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel");
rgb_panel->num_dma_nodes = num_dma_nodes;
rgb_panel->panel_id = -1;
// register to platform
int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel);
ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free rgb panel slot");
rgb_panel->panel_id = panel_id;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module);
// alloc frame buffer
bool alloc_from_psram = false;
// fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM
@ -119,36 +130,33 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
} else {
rgb_panel->fb = heap_caps_calloc(1, fb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
ESP_GOTO_ON_FALSE(rgb_panel->fb, ESP_ERR_NO_MEM, no_mem_fb, TAG, "no mem for frame buffer");
ESP_GOTO_ON_FALSE(rgb_panel->fb, ESP_ERR_NO_MEM, err, TAG, "no mem for frame buffer");
rgb_panel->fb_size = fb_size;
rgb_panel->flags.fb_in_psram = alloc_from_psram;
// semaphore indicates new frame trans done
rgb_panel->done_sem = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(rgb_panel->done_sem, ESP_ERR_NO_MEM, no_mem_sem, TAG, "create done sem failed");
ESP_GOTO_ON_FALSE(rgb_panel->done_sem, ESP_ERR_NO_MEM, err, TAG, "create done sem failed");
xSemaphoreGive(rgb_panel->done_sem); // initialize the semaphore count to 1
// register to platform
int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel);
ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, no_slot, TAG, "no free rgb panel slot");
rgb_panel->panel_id = panel_id;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module);
// 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");
// install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask)
int isr_flags = ESP_INTR_FLAG_SHARED;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags,
(uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev),
LCD_LL_EVENT_VSYNC_END, lcd_default_isr_handler, rgb_panel, &rgb_panel->intr);
ESP_GOTO_ON_ERROR(ret, no_int, TAG, "install interrupt failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, false); // disable all interrupts
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
rgb_panel->flags.stream_mode = !rgb_panel_config->flags.relax_on_idle;
ret = lcd_rgb_panel_create_trans_link(rgb_panel);
ESP_GOTO_ON_ERROR(ret, no_dma, TAG, "install DMA failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
// configure GPIO
ret = lcd_rgb_panel_configure_gpio(rgb_panel, rgb_panel_config);
ESP_GOTO_ON_ERROR(ret, no_gpio, TAG, "configure GPIO failed");
ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
// fill other rgb panel runtime parameters
memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, SOC_LCD_RGB_DATA_WIDTH);
rgb_panel->timings = rgb_panel_config->timings;
@ -172,22 +180,31 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb_size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb_size);
return ESP_OK;
no_gpio:
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
no_dma:
esp_intr_free(rgb_panel->intr);
no_int:
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
no_slot:
vSemaphoreDelete(rgb_panel->done_sem);
no_mem_sem:
free(rgb_panel->fb);
no_mem_fb:
free(rgb_panel);
no_mem_panel:
err_arg:
err:
if (rgb_panel) {
if (rgb_panel->panel_id >= 0) {
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
}
if (rgb_panel->fb) {
free(rgb_panel->fb);
}
if (rgb_panel->done_sem) {
vSemaphoreDelete(rgb_panel->done_sem);
}
if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
}
if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr);
}
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
}
return ret;
}
@ -203,6 +220,10 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
vSemaphoreDelete(rgb_panel->done_sem);
free(rgb_panel->fb);
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
ESP_LOGD(TAG, "del rgb panel(%d)", panel_id);
return ESP_OK;
@ -222,8 +243,6 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
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 peripheral clock resolution
rgb_panel->resolution_hz = lcd_com_select_periph_clock(&rgb_panel->hal);
// 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,
@ -403,6 +422,31 @@ 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)
{
esp_err_t ret = ESP_OK;
lcd_ll_set_group_clock_src(panel->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0);
switch (clk_src) {
case LCD_CLK_SRC_PLL160M:
panel->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
#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");
// hold the lock during the whole lifecycle of RGB panel
esp_pm_lock_acquire(panel->pm_lock);
ESP_LOGD(TAG, "installed ESP_PM_APB_FREQ_MAX lock and hold the lock during the whole panel lifecycle");
#endif
break;
case LCD_CLK_SRC_XTAL:
panel->resolution_hz = rtc_clk_xtal_freq_get() * 1000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
break;
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported clock source: %d", clk_src);
break;
}
return ret;
}
static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel)
{
esp_err_t ret = ESP_OK;

Wyświetl plik

@ -19,6 +19,7 @@
#include "soc/lcd_cam_reg.h"
#include "soc/lcd_cam_struct.h"
#include "hal/assert.h"
#include "hal/lcd_types.h"
#ifdef __cplusplus
extern "C" {
@ -30,11 +31,6 @@ extern "C" {
#define LCD_LL_EVENT_VSYNC_END (1 << 0)
#define LCD_LL_EVENT_TRANS_DONE (1 << 1)
// Clock source ID represented in register
#define LCD_LL_CLOCK_SRC_XTAL (1)
#define LCD_LL_CLOCK_SRC_APLL (2)
#define LCD_LL_CLOCK_SRC_PLL160M (3)
// Maximum coefficient of clock prescaler
#define LCD_LL_CLOCK_PRESCALE_MAX (64)
@ -43,7 +39,7 @@ static inline void lcd_ll_enable_clock(lcd_cam_dev_t *dev, bool en)
dev->lcd_clock.clk_en = en;
}
static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, int src, int div_num, int div_a, int div_b)
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)
{
// lcd_clk = module_clock_src / (div_num + div_b / div_a)
HAL_ASSERT(div_num >= 2);
@ -51,6 +47,20 @@ static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, int src, int d
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;
break;
case LCD_CLK_SRC_APLL:
dev->lcd_clock.lcd_clk_sel = 2;
break;
case LCD_CLK_SRC_XTAL:
dev->lcd_clock.lcd_clk_sel = 1;
break;
default:
HAL_ASSERT(false && "unsupported clock source");
break;
}
}
static inline void lcd_ll_set_clock_idle_level(lcd_cam_dev_t *dev, bool level)

Wyświetl plik

@ -24,10 +24,10 @@
extern "C" {
#endif
#include "soc/lcd_cam_struct.h"
typedef struct lcd_cam_dev_t *lcd_soc_handle_t;
typedef struct {
lcd_cam_dev_t *dev;
lcd_soc_handle_t dev;
} lcd_hal_context_t;
void lcd_hal_init(lcd_hal_context_t *hal, int id);

Wyświetl plik

@ -0,0 +1,42 @@
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LCD clock source
* @note User should select the clock source based on the real requirement:
*
* LCD clock source Features Power Management
*
* LCD_CLK_SRC_PLL160M High resolution, fixed ESP_PM_APB_FREQ_MAX lock
*
* LCD_CLK_SRC_APLL Configurable resolution ESP_PM_NO_LIGHT_SLEEP lock
*
* LCD_CLK_SRC_XTAL Medium resolution, fixed No PM lock
*
*/
typedef enum {
LCD_CLK_SRC_PLL160M, /*!< Select PLL160M as the source clock */
LCD_CLK_SRC_APLL, /*!< Select APLL as the source clock */
LCD_CLK_SRC_XTAL, /*!< Select XTAL as the source clock */
} lcd_clock_source_t;
#ifdef __cplusplus
}
#endif

Wyświetl plik

@ -762,7 +762,7 @@ typedef union {
} lcd_cam_lc_reg_date_reg_t;
typedef struct {
typedef struct lcd_cam_dev_t {
volatile lcd_cam_lcd_clock_reg_t lcd_clock;
volatile lcd_cam_cam_ctrl_reg_t cam_ctrl;
volatile lcd_cam_cam_ctrl1_reg_t cam_ctrl1;