mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
261 lines
7.5 KiB
C
261 lines
7.5 KiB
C
/*
|
|
* Copyright (C) 2023 Gunar Schorcht
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_esp32
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Peripheral low-Level parallel interface implementation for LCDs
|
|
*
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
* @}
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "lcd.h"
|
|
#include "lcd_internal.h"
|
|
#include "log.h"
|
|
#include "macros/units.h"
|
|
#include "periph/gpio.h"
|
|
#include "ztimer.h"
|
|
|
|
#include "esp_lcd_panel_io.h"
|
|
#include "soc/gpio_reg.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#if !defined(CPU_FAM_ESP32) && !defined(CPU_FAM_ESP32S2) && !defined(CPU_FAM_ESP32S3)
|
|
#error "ESP32x SoC family not supported"
|
|
#endif
|
|
|
|
#ifndef CONFIG_LCD_WRITE_CLOCK_MHZ
|
|
#if CONFIG_LCD_I80_COLOR_IN_PSRAM
|
|
/* PCLK has to be low enough for SPI RAM */
|
|
#define CONFIG_LCD_WRITE_CLOCK_MHZ 2
|
|
#else
|
|
|
|
#if defined(CPU_FAM_ESP32S3)
|
|
#define CONFIG_LCD_WRITE_CLOCK_MHZ 20
|
|
#elif defined(CPU_FAM_ESP32S2)
|
|
#define CONFIG_LCD_WRITE_CLOCK_MHZ 40
|
|
#else /* ESP32 */
|
|
#define CONFIG_LCD_WRITE_CLOCK_MHZ 10
|
|
#endif
|
|
|
|
#endif /* CONFIG_LCD_I80_COLOR_IN_PSRAM */
|
|
#endif /* CONFIG_LCD_WRITE_CLOCK_MHZ */
|
|
|
|
static_assert(CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE >= 32,
|
|
"CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE mus be at least 32");
|
|
|
|
/* ESP32x SoCs support only one LCD peripheral so we can use single instances
|
|
* of the following variables */
|
|
|
|
int _cmd = -1; /* means no command needed in ESP-IDF */
|
|
|
|
size_t _idx_bytes = 0;
|
|
uint8_t _data_bytes[CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE];
|
|
uint8_t _trans_bytes[CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE];
|
|
|
|
esp_lcd_i80_bus_handle_t _i80_bus_handle = NULL;
|
|
esp_lcd_panel_io_handle_t _i80_io_handle = NULL;
|
|
|
|
/* indicates that a transfer of the data buffer is still in progress and must
|
|
* not be overwritten */
|
|
static bool _dma_transfer_in_progress = false;
|
|
|
|
static bool _dma_transfer_done(esp_lcd_panel_io_handle_t io_handle,
|
|
esp_lcd_panel_io_event_data_t *io_event_data,
|
|
void *user_ctx)
|
|
{
|
|
(void)io_handle;
|
|
(void)io_event_data;
|
|
(void)user_ctx;
|
|
|
|
_dma_transfer_in_progress = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void _lcd_ll_mcu_init(lcd_t *dev)
|
|
{
|
|
esp_lcd_i80_bus_config_t i80_bus_config = {
|
|
.dc_gpio_num = dev->params->dcx_pin,
|
|
.wr_gpio_num = dev->params->wrx_pin,
|
|
.clk_src = LCD_CLK_SRC_PLL160M,
|
|
#if IS_USED(MODULE_LCD_PARALLEL_16BIT)
|
|
.data_gpio_nums = {
|
|
dev->params->d0_pin,
|
|
dev->params->d1_pin,
|
|
dev->params->d2_pin,
|
|
dev->params->d3_pin,
|
|
dev->params->d4_pin,
|
|
dev->params->d5_pin,
|
|
dev->params->d6_pin,
|
|
dev->params->d7_pin,
|
|
dev->params->d8_pin,
|
|
dev->params->d9_pin,
|
|
dev->params->d10_pin,
|
|
dev->params->d11_pin,
|
|
dev->params->d12_pin,
|
|
dev->params->d13_pin,
|
|
dev->params->d14_pin,
|
|
dev->params->d15_pin,
|
|
},
|
|
.bus_width = 16,
|
|
#else
|
|
.data_gpio_nums = {
|
|
dev->params->d0_pin,
|
|
dev->params->d1_pin,
|
|
dev->params->d2_pin,
|
|
dev->params->d3_pin,
|
|
dev->params->d4_pin,
|
|
dev->params->d5_pin,
|
|
dev->params->d6_pin,
|
|
dev->params->d7_pin,
|
|
},
|
|
.bus_width = 8,
|
|
#endif
|
|
.max_transfer_bytes = dev->params->rgb_channels * 40 * sizeof(uint16_t),
|
|
};
|
|
|
|
esp_lcd_panel_io_i80_config_t i80_io_config = {
|
|
.cs_gpio_num = gpio_is_valid(dev->params->cs_pin) ? (int)dev->params->cs_pin
|
|
: -1,
|
|
.pclk_hz = MHZ(CONFIG_LCD_WRITE_CLOCK_MHZ),
|
|
.trans_queue_depth = 10,
|
|
.dc_levels = {
|
|
.dc_idle_level = 0,
|
|
.dc_cmd_level = 0,
|
|
.dc_dummy_level = 1,
|
|
.dc_data_level = 1,
|
|
},
|
|
.on_color_trans_done = _dma_transfer_done,
|
|
.user_ctx = NULL,
|
|
.lcd_cmd_bits = 8,
|
|
.lcd_param_bits = 8,
|
|
};
|
|
|
|
esp_lcd_new_i80_bus(&i80_bus_config, &_i80_bus_handle);
|
|
esp_lcd_new_panel_io_i80(_i80_bus_handle, &i80_io_config, &_i80_io_handle);
|
|
|
|
if (gpio_is_valid(dev->params->rdx_pin)) {
|
|
gpio_init(dev->params->rdx_pin, GPIO_IN_PU);
|
|
gpio_set(dev->params->rdx_pin);
|
|
}
|
|
|
|
if (gpio_is_valid(dev->params->cs_pin)) {
|
|
gpio_init(dev->params->cs_pin, GPIO_OUT);
|
|
gpio_clear(dev->params->cs_pin);
|
|
}
|
|
}
|
|
|
|
static void _lcd_ll_mcu_set_data_dir(lcd_t *dev, bool output)
|
|
{
|
|
(void)dev;
|
|
(void)output;
|
|
LOG_ERROR("[lcd_ll_mcu] set dir: %d is not supported\n", output);
|
|
/* not supported yet */
|
|
}
|
|
|
|
static void _lcd_ll_mcu_cmd_start(lcd_t *dev, uint8_t cmd, bool cont)
|
|
{
|
|
DEBUG("[lcd_ll_mcu] write cmd: %02x\n", cmd);
|
|
|
|
if (!cont) {
|
|
/* cmd without parameters */
|
|
esp_lcd_panel_io_tx_param(_i80_io_handle, cmd, NULL, 0);
|
|
_cmd = -1;
|
|
}
|
|
else {
|
|
/* cmd with parameters */
|
|
_cmd = cmd;
|
|
}
|
|
}
|
|
|
|
static void _lcd_ll_mcu_transfer(lcd_t *dev, bool cont)
|
|
{
|
|
if (!cont) {
|
|
/* if no further data follow, send the command with the data in the buffer */
|
|
esp_lcd_panel_io_tx_param(_i80_io_handle, _cmd, _data_bytes, _idx_bytes);
|
|
_cmd = -1;
|
|
_idx_bytes = 0;
|
|
}
|
|
else if (_idx_bytes == CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE) {
|
|
/* spin lock as long as a DMA data transfer is still in progress */
|
|
while (_dma_transfer_in_progress) {}
|
|
|
|
/* copy data buffer to the DMA transfer buffer */
|
|
memcpy(_trans_bytes, _data_bytes, _idx_bytes);
|
|
/* start DMA data transfer */
|
|
_dma_transfer_in_progress = true;
|
|
esp_lcd_panel_io_tx_color(_i80_io_handle, _cmd, _data_bytes, _idx_bytes);
|
|
|
|
/* It should only be possible to follow more data than
|
|
* CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE with the RAMWR command.
|
|
* Transferring more data to continue the operation with cmd=-1 does
|
|
* not seem to work. Therefore a RAMWRC generated in this
|
|
* case for further data */
|
|
_cmd = (_cmd == LCD_CMD_RAMWR) ? LCD_CMD_RAMWRC : _cmd;
|
|
_idx_bytes = 0;
|
|
}
|
|
}
|
|
|
|
static void _lcd_ll_mcu_write_byte(lcd_t *dev, bool cont, uint8_t out)
|
|
{
|
|
DEBUG("[lcd_ll_mcu] write byte: %02x\n", out);
|
|
|
|
_data_bytes[_idx_bytes++] = out;
|
|
/* transfer the data if necessary */
|
|
_lcd_ll_mcu_transfer(dev, cont);
|
|
}
|
|
|
|
static uint8_t _lcd_ll_mcu_read_byte(lcd_t *dev, bool cont)
|
|
{
|
|
LOG_ERROR("[lcd_ll_mcu] read from LCD is not supported\n");
|
|
return 0;
|
|
}
|
|
|
|
#if IS_USED(MODULE_LCD_PARALLEL_16BIT)
|
|
|
|
static void _lcd_ll_mcu_write_word(lcd_t *dev, bool cont, uint16_t out)
|
|
{
|
|
DEBUG("[lcd_ll_mcu] write word: %04x\n", out);
|
|
|
|
/* out is given in BE order */
|
|
_data_bytes[_idx_bytes++] = out >> 8;
|
|
_data_bytes[_idx_bytes++] = out & 0xff;
|
|
/* transfer the data if necessary */
|
|
_lcd_ll_mcu_transfer(dev, cont);
|
|
}
|
|
|
|
static uint16_t _lcd_ll_mcu_read_word(lcd_t *dev, bool cont)
|
|
{
|
|
LOG_ERROR("[lcd_ll_mcu] read from LCD is not supported\n");
|
|
/* not supported yet */
|
|
return 0;
|
|
}
|
|
|
|
#endif /* IS_USED(MODULE_LCD_PARALLEL_16BIT) */
|
|
|
|
const lcd_ll_par_driver_t lcd_ll_par_driver = {
|
|
.init = _lcd_ll_mcu_init,
|
|
.set_data_dir = _lcd_ll_mcu_set_data_dir,
|
|
.cmd_start = _lcd_ll_mcu_cmd_start,
|
|
.write_byte = _lcd_ll_mcu_write_byte,
|
|
.read_byte = _lcd_ll_mcu_read_byte,
|
|
#if IS_USED(MODULE_LCD_PARALLEL_16BIT)
|
|
.write_word = _lcd_ll_mcu_write_word,
|
|
.read_word = _lcd_ll_mcu_read_word,
|
|
#endif
|
|
};
|