Merge branch 'feature/lcd_driver_follow_up' into 'master'

lcd: add PM lock

Closes IDF-3631, IDF-3704, and IDF-3317

See merge request espressif/esp-idf!14880
pull/7554/head
morris 2021-09-10 03:14:50 +00:00
commit 25f723765e
18 zmienionych plików z 577 dodań i 239 usunięć

Wyświetl plik

@ -633,7 +633,7 @@ UT_046:
UT_047:
extends: .unit_test_esp32s2_template
parallel: 4
parallel: 5
tags:
- ESP32S2_IDF
- UT_T1_1

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 */
@ -163,7 +165,7 @@ esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus);
* @brief Panel IO configuration structure, for intel 8080 interface
*/
typedef struct {
int cs_gpio_num; /*!< GPIO used for CS line */
int cs_gpio_num; /*!< GPIO used for CS line, set to -1 will declaim exclusively use of I80 bus */
unsigned int pclk_hz; /*!< Frequency of pixel clock */
size_t trans_queue_depth; /*!< Transaction queue size, larger queue, higher throughput */
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); /*!< Callback, invoked when color data was tranferred done */

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,12 +64,16 @@ 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
struct {
unsigned int exclusive: 1; // Indicate whether the I80 bus is owned by one device (whose CS GPIO is not assigned) exclusively
} flags;
dma_descriptor_t dma_nodes[]; // DMA descriptor pool, the descriptors are shared by all i80 devices
};
@ -114,9 +119,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 +132,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 +161,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 +185,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 +220,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);
@ -220,7 +236,17 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i80_t *i80_device = NULL;
bool bus_exclusive = false;
ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// check if the bus has been configured as exclusive
portENTER_CRITICAL(&bus->spinlock);
if (!bus->flags.exclusive) {
bus->flags.exclusive = io_config->cs_gpio_num < 0;
} else {
bus_exclusive = true;
}
portEXIT_CRITICAL(&bus->spinlock);
ESP_GOTO_ON_FALSE(!bus_exclusive, ESP_ERR_INVALID_STATE, err, TAG, "bus has been exclusively owned by device");
// because we set the I2S's left channel data same to right channel, so f_pclk = f_i2s/pclk_div/2
uint32_t pclk_prescale = bus->resolution_hz / 2 / io_config->pclk_hz;
ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= I2S_LL_BCK_MAX_PRESCALE, ESP_ERR_NOT_SUPPORTED, err, TAG,
@ -256,10 +282,12 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
i80_device->base.del = panel_io_i80_del;
i80_device->base.tx_param = panel_io_i80_tx_param;
i80_device->base.tx_color = panel_io_i80_tx_color;
// CS signal is controlled by software
gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high); // de-assert by default
gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO);
if (io_config->cs_gpio_num >= 0) {
// CS signal is controlled by software
gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high); // de-assert by default
gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO);
}
*ret_io = &(i80_device->base);
ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d), pclk=%uHz", i80_device, bus->bus_id, i80_device->pclk_hz);
return ESP_OK;
@ -452,6 +480,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 +502,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 +542,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 +569,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)
@ -611,10 +661,12 @@ static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_
if (next_device != cur_device) {
// reconfigure PCLK for the new device
i2s_ll_tx_set_bck_div_num(bus->hal.dev, next_device->clock_prescale);
if (cur_device) { // de-assert current device
if (cur_device && cur_device->cs_gpio_num >= 0) { // de-assert current device
gpio_set_level(cur_device->cs_gpio_num, !cur_device->flags.cs_active_high);
}
gpio_set_level(next_device->cs_gpio_num, next_device->flags.cs_active_high); // select the next device
if (next_device->cs_gpio_num >= 0) {
gpio_set_level(next_device->cs_gpio_num, next_device->flags.cs_active_high); // select the next device
}
// the WR signal (a.k.a the PCLK) generated by I2S is low level in idle stage
// but most of 8080 LCDs require the WR line to be in high level during idle stage
esp_rom_gpio_connect_out_signal(bus->wr_gpio_num, lcd_periph_signals.buses[bus->bus_id].wr_sig, !next_device->flags.pclk_idle_low, false);
@ -639,6 +691,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 +735,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
@ -62,6 +65,9 @@ struct esp_lcd_i80_bus_t {
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
struct {
unsigned int exclusive: 1; // Indicate whether the I80 bus is owned by one device (whose CS GPIO is not assigned) exclusively
} flags;
dma_descriptor_t dma_nodes[]; // DMA descriptor pool, the descriptors are shared by all i80 devices
};
@ -110,17 +116,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 +137,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 +165,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 +173,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 +203,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:
@ -204,7 +222,17 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i80_t *i80_device = NULL;
bool bus_exclusive = false;
ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// check if the bus has been configured as exclusive
portENTER_CRITICAL(&bus->spinlock);
if (!bus->flags.exclusive) {
bus->flags.exclusive = io_config->cs_gpio_num < 0;
} else {
bus_exclusive = true;
}
portEXIT_CRITICAL(&bus->spinlock);
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,
@ -246,9 +274,11 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
i80_device->base.tx_color = panel_io_i80_tx_color;
// we only configure the CS GPIO as output, don't connect to the peripheral signal at the moment
// we will connect the CS GPIO to peripheral signal when switching devices in lcd_i80_switch_devices()
gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high);
gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO);
if (io_config->cs_gpio_num >= 0) {
gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high);
gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO);
}
*ret_io = &(i80_device->base);
ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d)", i80_device, bus->bus_id);
return ESP_OK;
@ -365,9 +395,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 +444,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;
@ -495,13 +556,15 @@ static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_
// configure DC line level for the new device
lcd_ll_set_dc_level(bus->hal.dev, next_device->dc_levels.dc_idle_level, next_device->dc_levels.dc_cmd_level,
next_device->dc_levels.dc_dummy_level, next_device->dc_levels.dc_data_level);
if (cur_device) {
if (cur_device && cur_device->cs_gpio_num >= 0) {
// disconnect current CS GPIO from peripheral signal
esp_rom_gpio_connect_out_signal(cur_device->cs_gpio_num, SIG_GPIO_OUT_IDX, false, false);
}
// connect CS signal to the new device
esp_rom_gpio_connect_out_signal(next_device->cs_gpio_num, lcd_periph_signals.buses[bus->bus_id].cs_sig,
next_device->flags.cs_active_high, false);
if (next_device->cs_gpio_num >= 0) {
// connect CS signal to the new device
esp_rom_gpio_connect_out_signal(next_device->cs_gpio_num, lcd_periph_signals.buses[bus->bus_id].cs_sig,
next_device->flags.cs_active_high, false);
}
}
}
@ -522,6 +585,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 +626,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

@ -15,6 +15,7 @@
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_common.h"
static const char *TAG = "lcd_panel.io.spi";
@ -131,6 +132,34 @@ err:
return ret;
}
static void spi_lcd_prepare_cmd_buffer(esp_lcd_panel_io_spi_t *panel_io, const void *cmd)
{
uint8_t *from = (uint8_t *)cmd;
// LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first
// However, the SPI peripheral will send 0x34 first, so we reversed the order below
if (panel_io->lcd_cmd_bits > 8) {
int start = 0;
int end = panel_io->lcd_cmd_bits / 8 - 1;
lcd_com_reverse_buffer_bytes(from, start, end);
}
}
static void spi_lcd_prepare_param_buffer(esp_lcd_panel_io_spi_t *panel_io, const void *param, size_t param_size)
{
uint8_t *from = (uint8_t *)param;
int param_width = panel_io->lcd_param_bits / 8;
size_t param_num = param_size / param_width;
// LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first
// However, the SPI peripheral will send 0x34 first, so we reversed the order below
if (panel_io->lcd_param_bits > 8) {
for (size_t i = 0; i < param_num; i++) {
int start = i * param_width;
int end = start + param_width - 1;
lcd_com_reverse_buffer_bytes(from, start, end);
}
}
}
static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
{
esp_err_t ret = ESP_OK;
@ -146,6 +175,7 @@ static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
spi_panel_io->num_trans_inflight = 0;
lcd_trans = &spi_panel_io->trans_pool[0];
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd);
lcd_trans->base.user = spi_panel_io;
lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode
lcd_trans->base.length = spi_panel_io->lcd_cmd_bits;
@ -162,6 +192,7 @@ static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
if (param && param_size) {
spi_lcd_prepare_param_buffer(spi_panel_io, param, param_size);
lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode
lcd_trans->base.length = param_size * 8; // transaction length is in bits
lcd_trans->base.tx_buffer = param;
@ -192,6 +223,7 @@ static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
spi_panel_io->num_trans_inflight = 0;
lcd_trans = &spi_panel_io->trans_pool[0];
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd);
lcd_trans->base.user = spi_panel_io;
lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode
lcd_trans->base.length = spi_panel_io->lcd_cmd_bits;

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

@ -176,6 +176,41 @@ TEST_CASE("lcd i80 bus and device allocation", "[lcd]")
}
}
TEST_CASE("lcd i80 bus exclusively owned by one device", "[lcd]")
{
esp_lcd_i80_bus_handle_t i80_bus_handle = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.wr_gpio_num = TEST_LCD_PCLK_GPIO,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
TEST_LCD_DATA1_GPIO,
TEST_LCD_DATA2_GPIO,
TEST_LCD_DATA3_GPIO,
TEST_LCD_DATA4_GPIO,
TEST_LCD_DATA5_GPIO,
TEST_LCD_DATA6_GPIO,
TEST_LCD_DATA7_GPIO,
},
.bus_width = 8,
.max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t)
};
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus_handle));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = -1, // own the bus exclusively
.pclk_hz = 5000000,
.trans_queue_depth = 4,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus_handle, &io_config, &io_handle));
io_config.cs_gpio_num = 0;
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_lcd_new_panel_io_i80(i80_bus_handle, &io_config, &io_handle));
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus_handle));
}
TEST_CASE("lcd panel i80 io test", "[lcd]")
{
esp_lcd_i80_bus_handle_t i80_bus = NULL;

Wyświetl plik

@ -8,27 +8,28 @@
#define TEST_LCD_H_RES (480)
#define TEST_LCD_V_RES (272)
#define TEST_LCD_VSYNC_GPIO (1)
#define TEST_LCD_HSYNC_GPIO (2)
#define TEST_LCD_DE_GPIO (-1)
#define TEST_LCD_PCLK_GPIO (3)
#define TEST_LCD_DATA0_GPIO (4) // B0
#define TEST_LCD_DATA1_GPIO (5) // B1
#define TEST_LCD_DATA2_GPIO (6) // B2
#define TEST_LCD_DATA3_GPIO (7) // B3
#define TEST_LCD_DATA4_GPIO (8) // B4
#define TEST_LCD_DATA5_GPIO (9) // G0
#define TEST_LCD_DATA6_GPIO (10) // G1
#define TEST_LCD_DATA7_GPIO (11) // G2
#define TEST_LCD_DATA8_GPIO (12) // G3
#define TEST_LCD_DATA9_GPIO (13) // G4
#define TEST_LCD_DATA10_GPIO (14) // G5
#define TEST_LCD_DATA11_GPIO (15) // R0
#define TEST_LCD_DATA12_GPIO (16) // R1
#define TEST_LCD_DATA13_GPIO (17) // R2
#define TEST_LCD_DATA14_GPIO (18) // R3
#define TEST_LCD_DATA15_GPIO (19) // R4
#define TEST_LCD_DISP_EN_GPIO (-1)
#define TEST_LCD_VSYNC_GPIO (48)
#define TEST_LCD_HSYNC_GPIO (47)
#define TEST_LCD_DE_GPIO (45)
#define TEST_LCD_PCLK_GPIO (21)
#define TEST_LCD_DATA0_GPIO (3) // B0
#define TEST_LCD_DATA1_GPIO (4) // B1
#define TEST_LCD_DATA2_GPIO (5) // B2
#define TEST_LCD_DATA3_GPIO (6) // B3
#define TEST_LCD_DATA4_GPIO (7) // B4
#define TEST_LCD_DATA5_GPIO (8) // G0
#define TEST_LCD_DATA6_GPIO (9) // G1
#define TEST_LCD_DATA7_GPIO (10) // G2
#define TEST_LCD_DATA8_GPIO (11) // G3
#define TEST_LCD_DATA9_GPIO (12) // G4
#define TEST_LCD_DATA10_GPIO (13) // G5
#define TEST_LCD_DATA11_GPIO (14) // R0
#define TEST_LCD_DATA12_GPIO (15) // R1
#define TEST_LCD_DATA13_GPIO (16) // R2
#define TEST_LCD_DATA14_GPIO (17) // R3
#define TEST_LCD_DATA15_GPIO (18) // R4
#define TEST_LCD_DISP_EN_GPIO (39)
#if SOC_LCD_RGB_SUPPORTED
// RGB driver consumes a huge memory to save frame buffer, only test it with PSRAM enabled
@ -66,7 +67,7 @@ TEST_CASE("lcd rgb lcd panel", "[lcd]")
TEST_LCD_DATA15_GPIO,
},
.timings = {
.pclk_hz = 6000000,
.pclk_hz = 12000000,
.h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES,
.hsync_back_porch = 43,

Wyświetl plik

@ -17,7 +17,7 @@
typedef bool (*trans_done_callback_t)(esp_lcd_panel_io_handle_t, void *, void *);
static void lcd_initialize_spi(esp_lcd_panel_io_handle_t *io_handle, esp_lcd_panel_handle_t *panel_handle, trans_done_callback_t on_color_trans_done, void *user_data, bool oct_mode)
static void lcd_initialize_spi(esp_lcd_panel_io_handle_t *io_handle, trans_done_callback_t on_color_trans_done, void *user_data, int cmd_bits, int param_bits, bool oct_mode)
{
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
@ -51,8 +51,8 @@ static void lcd_initialize_spi(esp_lcd_panel_io_handle_t *io_handle, esp_lcd_pan
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.spi_mode = 0,
.trans_queue_depth = 10,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.lcd_cmd_bits = cmd_bits,
.lcd_param_bits = param_bits,
.on_color_trans_done = on_color_trans_done,
.user_data = user_data
};
@ -61,13 +61,6 @@ static void lcd_initialize_spi(esp_lcd_panel_io_handle_t *io_handle, esp_lcd_pan
io_config.spi_mode = 3;
}
TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TEST_SPI_HOST_ID, &io_config, io_handle));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(*io_handle, &panel_config, panel_handle));
}
static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle)
@ -103,12 +96,74 @@ static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_ha
#undef TEST_IMG_SIZE
}
TEST_CASE("lcd panel spi io test", "[lcd]")
{
esp_lcd_panel_io_handle_t io_handle = NULL;
lcd_initialize_spi(&io_handle, NULL, NULL, 8, 8, false);
esp_lcd_panel_io_tx_param(io_handle, 0x1A, NULL, 0);
esp_lcd_panel_io_tx_param(io_handle, 0x1B, (uint8_t[]) {
0x11, 0x22, 0x33
}, 3);
esp_lcd_panel_io_tx_param(io_handle, 0x1C, NULL, 0);
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID));
lcd_initialize_spi(&io_handle, NULL, NULL, 16, 16, false);
esp_lcd_panel_io_tx_param(io_handle, 0x1A01, NULL, 0);
esp_lcd_panel_io_tx_param(io_handle, 0x1B02, (uint16_t[]) {
0x11, 0x22, 0x33
}, 6);
esp_lcd_panel_io_tx_param(io_handle, 0x1C03, NULL, 0);
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID));
#if SOC_SPI_SUPPORT_OCT
lcd_initialize_spi(&io_handle, NULL, NULL, 8, 8, true);
esp_lcd_panel_io_tx_param(io_handle, 0x1A, NULL, 0);
esp_lcd_panel_io_tx_param(io_handle, 0x1B, (uint8_t[]) {
0x11, 0x22, 0x33
}, 3);
esp_lcd_panel_io_tx_param(io_handle, 0x1C, NULL, 0);
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID));
lcd_initialize_spi(&io_handle, NULL, NULL, 16, 16, true);
esp_lcd_panel_io_tx_param(io_handle, 0x1A01, NULL, 0);
esp_lcd_panel_io_tx_param(io_handle, 0x1B02, (uint16_t[]) {
0x11, 0x22, 0x33
}, 6);
esp_lcd_panel_io_tx_param(io_handle, 0x1C03, NULL, 0);
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID));
#endif // SOC_SPI_SUPPORT_OCT
}
#if SOC_SPI_SUPPORT_OCT
TEST_CASE("lcd panel with 8-line spi interface (st7789)", "[lcd]")
{
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, &panel_handle, NULL, NULL, true);
lcd_initialize_spi(&io_handle, NULL, NULL, 8, 8, true);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lcd_panel_test(io_handle, panel_handle);
}
TEST_CASE("lcd panel with 8-line spi interface (nt35510)", "[lcd]")
{
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, NULL, NULL, 16, 16, true);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle));
lcd_panel_test(io_handle, panel_handle);
}
#endif // SOC_SPI_SUPPORT_OCT
@ -117,7 +172,13 @@ TEST_CASE("lcd panel with 1-line spi interface (st7789)", "[lcd]")
{
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, &panel_handle, NULL, NULL, false);
lcd_initialize_spi(&io_handle, NULL, NULL, 8, 8, false);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lcd_panel_test(io_handle, panel_handle);
}
@ -156,8 +217,28 @@ TEST_CASE("lvgl gui with 8-line spi interface (st7789)", "[lcd][lvgl][ignore]")
lv_disp_t *disp = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, &panel_handle, notify_lvgl_ready_to_flush, &disp, true);
lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 8, 8, true);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lvgl_gui_test(io_handle, panel_handle, &disp);
}
TEST_CASE("lvgl gui with 8-line spi interface (nt35510)", "[lcd][lvgl][ignore]")
{
lv_disp_t *disp = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 16, 16, true);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle));
lvgl_gui_test(io_handle, panel_handle, &disp);
}
#endif // SOC_SPI_SUPPORT_OCT
@ -167,8 +248,13 @@ TEST_CASE("lvgl gui with 1-line spi interface (st7789)", "[lcd][lvgl][ignore]")
lv_disp_t *disp = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, &panel_handle, notify_lvgl_ready_to_flush, &disp, false);
lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 8, 8, false);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lvgl_gui_test(io_handle, panel_handle, &disp);
}

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;

Wyświetl plik

@ -158,49 +158,49 @@ Supported line modes for {IDF_TARGET_NAME} are listed as follows, to make use of
.. only:: not SOC_SPI_SUPPORT_OCT
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Mode name | Command Line Width | Address Line Width | Data Line Width | Transaction Flag | Bus IO setting Flag |
+==============+====================+====================+=================+============================+=========================+
| Normal SPI | 1 | 1 | 1 | 0 | 0 |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual Output | 1 | 1 | 2 | SPI_TRANS_MODE_DIO | |
| | | | | | |
| | | | | | SPICOMMON_BUSFLAG_DUAL |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual I/O | 1 | 2 | 2 | SPI_TRANS_MODE_DIO | | |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Quad Output | 1 | 1 | 4 | SPI_TRANS_MODE_QIO | |
+--------------+--------------------+--------------------+-----------------+----------------------------+ |
| Quad I/O | 1 | 4 | 4 | SPI_TRANS_MODE_QIO | | SPICOMMON_BUSFLAG_QUAD |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Mode name | Command Line Width | Address Line Width | Data Line Width | Transaction Flag | Bus IO setting Flag |
+==============+====================+====================+=================+============================+=========================+
| Normal SPI | 1 | 1 | 1 | 0 | 0 |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual Output | 1 | 1 | 2 | SPI_TRANS_MODE_DIO | |
| | | | | | |
| | | | | | SPICOMMON_BUSFLAG_DUAL |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual I/O | 1 | 2 | 2 | SPI_TRANS_MODE_DIO | | |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Quad Output | 1 | 1 | 4 | SPI_TRANS_MODE_QIO | |
+--------------+--------------------+--------------------+-----------------+----------------------------+ |
| Quad I/O | 1 | 4 | 4 | SPI_TRANS_MODE_QIO | | SPICOMMON_BUSFLAG_QUAD |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
.. only:: SOC_SPI_SUPPORT_OCT
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Mode name | Command Line Width | Address Line Width | Data Line Width | Transaction Flag | Bus IO setting Flag |
+==============+====================+====================+=================+============================+=========================+
| Normal SPI | 1 | 1 | 1 | 0 | 0 |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual Output | 1 | 1 | 2 | SPI_TRANS_MODE_DIO | |
| | | | | | |
| | | | | | SPICOMMON_BUSFLAG_DUAL |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual I/O | 1 | 2 | 2 | SPI_TRANS_MODE_DIO | | |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Quad Output | 1 | 1 | 4 | SPI_TRANS_MODE_QIO | |
+--------------+--------------------+--------------------+-----------------+----------------------------+ |
| Quad I/O | 1 | 4 | 4 | SPI_TRANS_MODE_QIO | | SPICOMMON_BUSFLAG_QUAD |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Octal Output | 1 | 1 | 8 | SPI_TRANS_MODE_OCT | |
+--------------+--------------------+--------------------+-----------------+----------------------------+ |
| OPI | 8 | 8 | 8 | SPI_TRANS_MODE_OCT | | SPICOMMON_BUSFLAG_OCTAL |
| | | | | SPI_TRANS_MULTILINE_ADDR | | |
| | | | | SPI_TRANS_MULTILINE_CMD | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Mode name | Command Line Width | Address Line Width | Data Line Width | Transaction Flag | Bus IO setting Flag |
+==============+====================+====================+=================+============================+=========================+
| Normal SPI | 1 | 1 | 1 | 0 | 0 |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual Output | 1 | 1 | 2 | SPI_TRANS_MODE_DIO | |
| | | | | | |
| | | | | | SPICOMMON_BUSFLAG_DUAL |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Dual I/O | 1 | 2 | 2 | SPI_TRANS_MODE_DIO | | |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Quad Output | 1 | 1 | 4 | SPI_TRANS_MODE_QIO | |
+--------------+--------------------+--------------------+-----------------+----------------------------+ |
| Quad I/O | 1 | 4 | 4 | SPI_TRANS_MODE_QIO | | SPICOMMON_BUSFLAG_QUAD |
| | | | | SPI_TRANS_MULTILINE_ADDR | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
| Octal Output | 1 | 1 | 8 | SPI_TRANS_MODE_OCT | |
+--------------+--------------------+--------------------+-----------------+----------------------------+ |
| OPI | 8 | 8 | 8 | SPI_TRANS_MODE_OCT | | SPICOMMON_BUSFLAG_OCTAL |
| | | | | SPI_TRANS_MULTILINE_ADDR | | |
| | | | | SPI_TRANS_MULTILINE_CMD | |
+--------------+--------------------+--------------------+-----------------+----------------------------+-------------------------+
Command and Address Phases
^^^^^^^^^^^^^^^^^^^^^^^^^^