i80_lcd: support PSRAM buffer for esp32s3

pull/8934/head
morris 2022-03-23 18:34:35 +08:00
rodzic 3e7448d0f3
commit 5f28db177d
2 zmienionych plików z 53 dodań i 22 usunięć

Wyświetl plik

@ -151,6 +151,8 @@ typedef struct {
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 */
size_t psram_trans_align; /*!< DMA transfer alignment for data allocated from PSRAM */
size_t sram_trans_align; /*!< DMA transfer alignment for data allocated from SRAM */
} esp_lcd_i80_bus_config_t;
/**

Wyświetl plik

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -16,13 +16,13 @@
#include "freertos/queue.h"
#include "esp_attr.h"
#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"
#include "soc/soc_caps.h"
#include "soc/rtc.h" // for `rtc_clk_xtal_freq_get()`
#include "soc/soc_memory_types.h"
#include "hal/dma_types.h"
#include "hal/gpio_hal.h"
#include "esp_private/gdma.h"
@ -40,6 +40,9 @@ typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t;
typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t;
typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t;
// This function is located in ROM (also see esp_rom/${target}/ld/${target}.rom.ld)
extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size);
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);
@ -49,7 +52,7 @@ static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_c
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);
static IRAM_ATTR void lcd_default_isr_handler(void *args);
static void lcd_default_isr_handler(void *args);
struct esp_lcd_i80_bus_t {
int bus_id; // Bus ID, index from 0
@ -62,6 +65,8 @@ struct esp_lcd_i80_bus_t {
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
gdma_channel_handle_t dma_chan; // DMA channel handle
size_t psram_trans_align; // DMA transfer alignment for data allocated from PSRAM
size_t sram_trans_align; // DMA transfer alignment for data allocated from SRAM
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
@ -119,11 +124,11 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
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);
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, 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);
bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
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);
@ -142,7 +147,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
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;
int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | 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);
@ -150,12 +155,14 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
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
bus->psram_trans_align = bus_config->psram_trans_align;
bus->sram_trans_align = bus_config->sram_trans_align;
ret = lcd_i80_init_dma_link(bus);
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);
bus->bus_width = lcd_ll_get_data_width(bus->hal.dev);
bus->bus_width = bus_config->bus_width;
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(bus->hal.dev, true);
// enable trans done interrupt
@ -237,7 +244,7 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
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,
"prescaler can't satisfy PCLK clock %u", io_config->pclk_hz);
i80_device = calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t));
i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io");
// create two queues for i80 device
i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
@ -302,8 +309,11 @@ static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io)
esp_lcd_i80_bus_t *bus = i80_device->bus;
lcd_i80_trans_descriptor_t *trans_desc = NULL;
// wait all pending transaction to finish
for (size_t i = 0; i < i80_device->num_trans_inflight; i++) {
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
size_t num_trans_inflight = i80_device->num_trans_inflight;
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
i80_device->num_trans_inflight--;
}
// remove from device list
portENTER_CRITICAL(&bus->spinlock);
@ -370,19 +380,20 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
i80_lcd_prepare_cmd_buffer(bus, next_device, &lcd_cmd);
uint32_t param_len = i80_lcd_prepare_param_buffer(bus, next_device, param, param_size);
// wait all pending transaction in the queue to finish
for (size_t i = 0; i < next_device->num_trans_inflight; i++) {
xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY);
size_t num_trans_inflight = next_device->num_trans_inflight;
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE( xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
next_device->num_trans_inflight--;
}
next_device->num_trans_inflight = 0;
uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev);
lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status);
// switch devices if necessary
lcd_i80_switch_devices(cur_device, next_device);
// set data format
lcd_ll_reverse_data_byte_order(bus->hal.dev, false);
lcd_ll_reverse_data_bit_order(bus->hal.dev, false);
lcd_ll_reverse_data_8bits_order(bus->hal.dev, next_device->lcd_param_bits > bus->bus_width);
lcd_ll_reverse_bit_order(bus->hal.dev, false);
lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->lcd_param_bits > bus->bus_width);
bus->cur_trans = NULL;
bus->cur_device = next_device;
// package a transaction
@ -425,7 +436,8 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
trans_desc = &i80_device->trans_pool[i80_device->num_trans_inflight];
} else {
// transaction pool has used up, recycle one from done_queue
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
i80_device->num_trans_inflight--;
}
trans_desc->i80_device = i80_device;
@ -435,6 +447,12 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
trans_desc->data_length = color_size;
trans_desc->trans_done_cb = i80_device->on_color_trans_done;
trans_desc->user_ctx = i80_device->user_ctx;
if (esp_ptr_external_ram(color)) {
// flush framebuffer from cache to the physical PSRAM
Cache_WriteBack_Addr((uint32_t)color, color_size);
}
// send transaction to trans_queue
xQueueSend(i80_device->trans_queue, &trans_desc, portMAX_DELAY);
i80_device->num_trans_inflight++;
@ -447,7 +465,8 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
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);
// force to use integer division, as fractional division might lead to clock jitter
lcd_ll_set_group_clock_src(bus->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0);
switch (clk_src) {
case LCD_CLK_SRC_PLL160M:
bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
@ -488,6 +507,12 @@ static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus)
.owner_check = true
};
gdma_apply_strategy(bus->dma_chan, &strategy_config);
// set DMA transfer ability
gdma_transfer_ability_t ability = {
.psram_trans_align = bus->psram_trans_align,
.sram_trans_align = bus->sram_trans_align,
};
gdma_set_transfer_ability(bus->dma_chan, &ability);
return ESP_OK;
err:
if (bus->dma_chan) {
@ -540,6 +565,9 @@ static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descript
lcd_ll_set_command(bus->hal.dev, bus->bus_width, trans_desc->cmd_value);
if (trans_desc->data) { // some specific LCD commands can have no parameters
gdma_start(bus->dma_chan, (intptr_t)(bus->dma_nodes));
// delay 1us is sufficient for DMA to pass data to LCD FIFO
// in fact, this is only needed when LCD pixel clock is set too high
esp_rom_delay_us(1);
}
lcd_ll_start(bus->hal.dev);
}
@ -612,14 +640,15 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
if (high_task_woken == pdTRUE) {
need_yield = true;
}
// sanity check
assert(trans_desc);
// only clear the interrupt status when we're sure there still remains transaction to handle
lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status);
// switch devices if necessary
lcd_i80_switch_devices(cur_device, next_device);
// only reverse data bit/bytes for color data
lcd_ll_reverse_data_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
lcd_ll_reverse_data_byte_order(bus->hal.dev, next_device->flags.swap_color_bytes);
lcd_ll_reverse_data_8bits_order(bus->hal.dev, false);
lcd_ll_reverse_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->flags.swap_color_bytes);
bus->cur_trans = trans_desc;
bus->cur_device = next_device;
// mount data to DMA links