mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-11-03 22:08:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: Apache-2.0
 | 
						|
 */
 | 
						|
 | 
						|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
						|
// Although we're manipulating Parlio peripheral, it has nothing to do with the Parlio.
 | 
						|
// In fact, we're simulating the Intel 8080 (8 data-width) or SPI(1 data-width) interface with Parlio peripheral.
 | 
						|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <sys/cdefs.h>
 | 
						|
#include <sys/param.h>
 | 
						|
#include <sys/queue.h>
 | 
						|
#include "sdkconfig.h"
 | 
						|
#if CONFIG_LCD_ENABLE_DEBUG_LOG
 | 
						|
// The local log level must be defined before including esp_log.h
 | 
						|
// Set the maximum log level for this source file
 | 
						|
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 | 
						|
#endif
 | 
						|
#include "freertos/FreeRTOS.h"
 | 
						|
#include "freertos/task.h"
 | 
						|
#include "freertos/queue.h"
 | 
						|
#include "esp_attr.h"
 | 
						|
#include "esp_check.h"
 | 
						|
#include "esp_heap_caps.h"
 | 
						|
#include "soc/soc_caps.h"
 | 
						|
#include "soc/lcd_periph.h"
 | 
						|
#include "hal/gpio_hal.h"
 | 
						|
#include "driver/gpio.h"
 | 
						|
#include "driver/parlio_tx.h"
 | 
						|
#include "driver/parlio_types.h"
 | 
						|
#include "esp_private/gpio.h"
 | 
						|
#include "esp_private/parlio_private.h"
 | 
						|
#include "esp_lcd_panel_io_interface.h"
 | 
						|
#include "esp_lcd_panel_io.h"
 | 
						|
#include "esp_lcd_common.h"
 | 
						|
 | 
						|
static const char *TAG = "lcd_panel.io.parl";
 | 
						|
 | 
						|
typedef struct lcd_panel_io_parlio_t lcd_panel_io_parlio_t;
 | 
						|
typedef struct parlio_trans_descriptor_t parlio_trans_descriptor_t;
 | 
						|
 | 
						|
static esp_err_t panel_io_parl_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
 | 
						|
static esp_err_t panel_io_parl_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
 | 
						|
static esp_err_t panel_io_parl_del(esp_lcd_panel_io_t *io);
 | 
						|
static bool lcd_default_isr_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx);
 | 
						|
static esp_err_t panel_io_parl_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
 | 
						|
 | 
						|
struct parlio_trans_descriptor_t {
 | 
						|
    lcd_panel_io_parlio_t *parlio_device; // parlio device issuing this transaction
 | 
						|
    const void *data;     // Data buffer
 | 
						|
    uint32_t data_length; // Data buffer size
 | 
						|
    struct {
 | 
						|
        unsigned int en_trans_done_cb: 1;
 | 
						|
    } flags;
 | 
						|
};
 | 
						|
 | 
						|
struct lcd_panel_io_parlio_t {
 | 
						|
    esp_lcd_panel_io_t base;   // Base class of generic lcd panel io
 | 
						|
    parlio_tx_unit_handle_t tx_unit; // Parlio TX unit
 | 
						|
    size_t data_width;      // Number of data lines
 | 
						|
    int dc_gpio_num;       // GPIO used for DC line
 | 
						|
    int cs_gpio_num;           // GPIO used for CS line
 | 
						|
    int lcd_cmd_bits;          // Bit width of LCD command
 | 
						|
    int lcd_param_bits;        // Bit width of LCD parameter
 | 
						|
    void *user_ctx;            // private data used when transfer color data
 | 
						|
    esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback
 | 
						|
    struct {
 | 
						|
        unsigned int dc_cmd_level: 1;  // Level of DC line in CMD phase
 | 
						|
        unsigned int dc_data_level: 1; // Level of DC line in DATA phase
 | 
						|
    } dc_levels;
 | 
						|
    parlio_trans_descriptor_t trans_desc; // Transaction description
 | 
						|
};
 | 
						|
 | 
						|
esp_err_t esp_lcd_new_panel_io_parl(const esp_lcd_panel_io_parl_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
 | 
						|
{
 | 
						|
    esp_err_t ret = ESP_OK;
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = NULL;
 | 
						|
 | 
						|
    ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
 | 
						|
    // Only support 1-bit(SPI) or 8-bit(I80) data width
 | 
						|
#if SOC_PARLIO_SUPPORT_I80_LCD
 | 
						|
    ESP_GOTO_ON_FALSE(io_config->data_width == 1 || io_config->data_width == 8, ESP_ERR_INVALID_ARG, err,
 | 
						|
                      TAG, "invalid bus width:%d", io_config->data_width);
 | 
						|
#else
 | 
						|
    ESP_GOTO_ON_FALSE(io_config->data_width == 1, ESP_ERR_INVALID_ARG, err,
 | 
						|
                      TAG, "invalid bus width:%d", io_config->data_width);
 | 
						|
#endif
 | 
						|
 | 
						|
    // allocate parlio device memory
 | 
						|
    parlio_device = heap_caps_calloc(1, sizeof(lcd_panel_io_parlio_t), MALLOC_CAP_DEFAULT);
 | 
						|
    ESP_GOTO_ON_FALSE(parlio_device, ESP_ERR_NO_MEM, err, TAG, "no mem for parlio device");
 | 
						|
 | 
						|
    // initialize the parlio unit
 | 
						|
    parlio_tx_unit_handle_t tx_unit = NULL;
 | 
						|
    parlio_tx_unit_config_t pio_config = {
 | 
						|
        .clk_src = io_config->clk_src,
 | 
						|
        .data_width = io_config->data_width,
 | 
						|
        .clk_in_gpio_num = -1, // use internal clock source
 | 
						|
        .valid_gpio_num =  io_config->cs_gpio_num,
 | 
						|
        .clk_out_gpio_num = io_config->clk_gpio_num,
 | 
						|
        .data_gpio_nums = {
 | 
						|
            io_config->data_gpio_nums[0],
 | 
						|
            io_config->data_gpio_nums[1],
 | 
						|
            io_config->data_gpio_nums[2],
 | 
						|
            io_config->data_gpio_nums[3],
 | 
						|
            io_config->data_gpio_nums[4],
 | 
						|
            io_config->data_gpio_nums[5],
 | 
						|
            io_config->data_gpio_nums[6],
 | 
						|
            io_config->data_gpio_nums[7],
 | 
						|
        },
 | 
						|
        .output_clk_freq_hz = io_config->pclk_hz,
 | 
						|
        .trans_queue_depth = io_config->trans_queue_depth ? io_config->trans_queue_depth : 4,
 | 
						|
        .max_transfer_size = io_config->max_transfer_bytes,
 | 
						|
        .sample_edge = PARLIO_SAMPLE_EDGE_POS,
 | 
						|
        .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
 | 
						|
        .dma_burst_size = io_config->dma_burst_size,
 | 
						|
        .flags.invert_valid_out = !io_config->flags.cs_active_high,
 | 
						|
    };
 | 
						|
    ESP_GOTO_ON_ERROR(parlio_new_tx_unit(&pio_config, &tx_unit), err, TAG, "config parlio tx unit failed");
 | 
						|
 | 
						|
    parlio_tx_event_callbacks_t parlio_cbs = {
 | 
						|
        .on_trans_done = lcd_default_isr_callback,
 | 
						|
    };
 | 
						|
    ESP_GOTO_ON_ERROR(parlio_tx_unit_register_event_callbacks(tx_unit, &parlio_cbs, parlio_device), err, TAG, "register parlio tx callback failed");
 | 
						|
 | 
						|
    // DC signal is controlled by software, set as general purpose IO
 | 
						|
    gpio_func_sel(io_config->dc_gpio_num, PIN_FUNC_GPIO);
 | 
						|
    gpio_output_enable(io_config->dc_gpio_num);
 | 
						|
 | 
						|
    parlio_device->dc_gpio_num = io_config->dc_gpio_num;
 | 
						|
    parlio_device->tx_unit = tx_unit;
 | 
						|
    parlio_device->data_width = io_config->data_width;
 | 
						|
    parlio_device->lcd_cmd_bits = io_config->lcd_cmd_bits;
 | 
						|
    parlio_device->lcd_param_bits = io_config->lcd_param_bits;
 | 
						|
    parlio_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level;
 | 
						|
    parlio_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level;
 | 
						|
    parlio_device->cs_gpio_num = io_config->cs_gpio_num;
 | 
						|
    parlio_device->trans_desc.parlio_device = parlio_device;
 | 
						|
 | 
						|
    // fill panel io function table
 | 
						|
    parlio_device->base.del = panel_io_parl_del;
 | 
						|
    parlio_device->base.tx_param = panel_io_parl_tx_param;
 | 
						|
    parlio_device->base.tx_color = panel_io_parl_tx_color;
 | 
						|
    parlio_device->base.register_event_callbacks = panel_io_parl_register_event_callbacks;
 | 
						|
 | 
						|
    // enable parlio tx unit finally
 | 
						|
    ESP_GOTO_ON_ERROR(parlio_tx_unit_enable(tx_unit), err, TAG, "enable parlio tx unit failed");
 | 
						|
 | 
						|
    *ret_io = &(parlio_device->base);
 | 
						|
    ESP_LOGD(TAG, "new parlio lcd panel io @%p", parlio_device);
 | 
						|
    return ESP_OK;
 | 
						|
 | 
						|
err:
 | 
						|
    if (parlio_device) {
 | 
						|
        if (parlio_device->tx_unit) {
 | 
						|
            parlio_del_tx_unit(parlio_device->tx_unit);
 | 
						|
        }
 | 
						|
        free(parlio_device);
 | 
						|
    }
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
void *esp_lcd_parlio_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps)
 | 
						|
{
 | 
						|
    ESP_RETURN_ON_FALSE(io, NULL, TAG, "invalid argument");
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
 | 
						|
    size_t int_mem_align = 0;
 | 
						|
    size_t ext_mem_align = 0;
 | 
						|
    void *buf = NULL;
 | 
						|
    parlio_tx_get_alignment_constraints(parlio_device->tx_unit, &int_mem_align, &ext_mem_align);
 | 
						|
    // alloc from external memory
 | 
						|
    if (caps & MALLOC_CAP_SPIRAM) {
 | 
						|
        buf = heap_caps_aligned_calloc(ext_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
 | 
						|
    } else {
 | 
						|
        buf = heap_caps_aligned_calloc(int_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
 | 
						|
    }
 | 
						|
    return buf;
 | 
						|
}
 | 
						|
 | 
						|
static esp_err_t panel_io_parl_del(esp_lcd_panel_io_t *io)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
 | 
						|
    // wait all pending transaction to finish
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
 | 
						|
    ESP_LOGD(TAG, "del parlio lcd panel io @%p", parlio_device);
 | 
						|
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_tx_unit_disable(parlio_device->tx_unit), TAG, "disable parlio tx unit failed");
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_del_tx_unit(parlio_device->tx_unit), TAG, "del parlio tx unit failed");
 | 
						|
 | 
						|
    free(parlio_device);
 | 
						|
    return ESP_OK;
 | 
						|
}
 | 
						|
 | 
						|
static esp_err_t panel_io_parl_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
 | 
						|
    if (parlio_device->on_color_trans_done != NULL) {
 | 
						|
        ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
 | 
						|
    }
 | 
						|
    parlio_device->on_color_trans_done = cbs->on_color_trans_done;
 | 
						|
    parlio_device->user_ctx = user_ctx;
 | 
						|
    return ESP_OK;
 | 
						|
}
 | 
						|
 | 
						|
static void parlio_lcd_prepare_cmd_buffer(parlio_trans_descriptor_t *trans_desc, const void *cmd)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = trans_desc->parlio_device;
 | 
						|
    uint8_t *from = (uint8_t *)cmd;
 | 
						|
    // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the data bus first
 | 
						|
    // However, the Parlio peripheral will send 0x34 first, so we reversed the order below
 | 
						|
    if (parlio_device->data_width < parlio_device->lcd_cmd_bits) {
 | 
						|
        int start = 0;
 | 
						|
        int end = parlio_device->lcd_cmd_bits / 8 - 1;
 | 
						|
        lcd_com_reverse_buffer_bytes(from, start, end);
 | 
						|
    }
 | 
						|
    trans_desc->data = cmd;
 | 
						|
    trans_desc->data_length = MAX(parlio_device->lcd_cmd_bits, parlio_device->data_width) / 8;
 | 
						|
}
 | 
						|
 | 
						|
static void parlio_lcd_prepare_param_buffer(parlio_trans_descriptor_t *trans_desc, const void *param, size_t param_num)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = trans_desc->parlio_device;
 | 
						|
    uint8_t *from = (uint8_t *)param;
 | 
						|
    int param_size = parlio_device->lcd_param_bits / 8;
 | 
						|
    // LCD is big-endian, e.g. to send param 0x1234, byte 0x12 should appear on the data bus first
 | 
						|
    // However, the Parlio peripheral will send 0x34 first, so we reversed the order below
 | 
						|
    if (parlio_device->data_width < parlio_device->lcd_param_bits) {
 | 
						|
        for (size_t i = 0; i < param_num; i++) {
 | 
						|
            int start = i * param_size;
 | 
						|
            int end = start + param_size - 1;
 | 
						|
            lcd_com_reverse_buffer_bytes(from, start, end);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    trans_desc->data = param;
 | 
						|
    trans_desc->data_length = param_num;
 | 
						|
}
 | 
						|
 | 
						|
static void parlio_lcd_prepare_color_buffer(parlio_trans_descriptor_t *trans_desc, const void *color, size_t color_size)
 | 
						|
{
 | 
						|
    trans_desc->data = color;
 | 
						|
    trans_desc->data_length = color_size;
 | 
						|
}
 | 
						|
 | 
						|
static esp_err_t panel_io_parl_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
 | 
						|
    parlio_trans_descriptor_t *trans_desc = NULL;
 | 
						|
 | 
						|
    // before issue a polling transaction, need to wait queued transactions finished
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
 | 
						|
    trans_desc = &parlio_device->trans_desc;
 | 
						|
    trans_desc->flags.en_trans_done_cb = false;
 | 
						|
    parlio_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
 | 
						|
 | 
						|
    gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_cmd_level);
 | 
						|
    parlio_transmit_config_t transmit_config = {
 | 
						|
        .idle_value = 0x00,
 | 
						|
    };
 | 
						|
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
 | 
						|
    if (param && param_size) {
 | 
						|
        parlio_lcd_prepare_param_buffer(trans_desc, param, param_size * 8 / parlio_device->lcd_param_bits);
 | 
						|
        // wait transmit done before changing DC level
 | 
						|
        ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
 | 
						|
        gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_data_level);
 | 
						|
        ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
 | 
						|
    }
 | 
						|
    // In case the lcd_cmd/param data is on the stack, wait transmit done to prevent it from being recycled
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
 | 
						|
    return ESP_OK;
 | 
						|
}
 | 
						|
 | 
						|
static esp_err_t panel_io_parl_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
 | 
						|
    parlio_trans_descriptor_t *trans_desc = NULL;
 | 
						|
    trans_desc = &parlio_device->trans_desc;
 | 
						|
 | 
						|
    if (lcd_cmd != -1) {
 | 
						|
        // wait transmit done to prevent skipping previous color transfer callback
 | 
						|
        ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
 | 
						|
        trans_desc->flags.en_trans_done_cb = false; // no callback for command transfer
 | 
						|
        parlio_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
 | 
						|
        gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_cmd_level);
 | 
						|
        parlio_transmit_config_t transmit_config = {
 | 
						|
            .idle_value = 0x00,
 | 
						|
        };
 | 
						|
        ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
 | 
						|
        // wait transmit done before changing DC level
 | 
						|
        ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
 | 
						|
    }
 | 
						|
 | 
						|
    parlio_lcd_prepare_color_buffer(trans_desc, color, color_size);
 | 
						|
    trans_desc->flags.en_trans_done_cb = true;
 | 
						|
 | 
						|
    gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_data_level);
 | 
						|
    parlio_transmit_config_t transmit_config = {
 | 
						|
        .idle_value = 0x00,
 | 
						|
    };
 | 
						|
    ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
 | 
						|
    return ESP_OK;
 | 
						|
}
 | 
						|
 | 
						|
IRAM_ATTR static bool lcd_default_isr_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx)
 | 
						|
{
 | 
						|
    lcd_panel_io_parlio_t *parlio_device = (lcd_panel_io_parlio_t *)user_ctx;
 | 
						|
    parlio_trans_descriptor_t *trans_desc = &parlio_device->trans_desc;
 | 
						|
    bool need_yield = false;
 | 
						|
 | 
						|
    // device callback
 | 
						|
    if (trans_desc->flags.en_trans_done_cb) {
 | 
						|
        if (parlio_device->on_color_trans_done) {
 | 
						|
            if (parlio_device->on_color_trans_done(&parlio_device->base, NULL, parlio_device->user_ctx)) {
 | 
						|
                need_yield = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return need_yield;
 | 
						|
}
 |