340 lines
12 KiB
C
340 lines
12 KiB
C
/**
|
|
* @file ra8875.c
|
|
*
|
|
*/
|
|
|
|
/*********************
|
|
* INCLUDES
|
|
*********************/
|
|
#include "ra8875.h"
|
|
#include "disp_spi.h"
|
|
#include "driver/gpio.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
/*********************
|
|
* DEFINES
|
|
*********************/
|
|
#define DEBUG false
|
|
#define TAG "RA8875"
|
|
|
|
#define DIV_ROUND_UP(n, d) (((n)+(d)-1)/(d))
|
|
|
|
#define SPI_CLOCK_SPEED_SLOW_HZ 1000000
|
|
|
|
#define RA8875_MODE_DATA_WRITE (0x00)
|
|
#define RA8875_MODE_DATA_READ (0x40)
|
|
#define RA8875_MODE_CMD_WRITE (0x80)
|
|
#define RA8875_MODE_STATUS_READ (0xC0)
|
|
|
|
#if (LV_COLOR_DEPTH == 8)
|
|
#define SYSR_VAL (0x00)
|
|
#elif (LV_COLOR_DEPTH == 16)
|
|
#define SYSR_VAL (0x08)
|
|
#else
|
|
#error "Unsupported color depth (LV_COLOR_DEPTH)"
|
|
#endif
|
|
#define BYTES_PER_PIXEL (LV_COLOR_DEPTH / 8)
|
|
|
|
#define HDWR_VAL (LV_HOR_RES_MAX/8 - 1)
|
|
#define VDHR_VAL (LV_VER_RES_MAX - 1)
|
|
|
|
#define VDIR_MASK (1 << 2)
|
|
#define HDIR_MASK (1 << 3)
|
|
|
|
#if ( CONFIG_LV_DISPLAY_ORIENTATION_PORTRAIT_INVERTED || CONFIG_LV_DISPLAY_ORIENTATION_LANDSCAPE_INVERTED )
|
|
#if CONFIG_LV_INVERT_DISPLAY
|
|
#define DPCR_VAL (VDIR_MASK)
|
|
#else
|
|
#define DPCR_VAL (VDIR_MASK | HDIR_MASK)
|
|
#endif
|
|
#else
|
|
#if CONFIG_LV_INVERT_DISPLAY
|
|
#define DPCR_VAL (HDIR_MASK)
|
|
#else
|
|
#define DPCR_VAL (0x00)
|
|
#endif
|
|
#endif
|
|
|
|
#if CONFIG_LV_DISP_RA8875_PCLK_INVERT
|
|
#define PCSR_VAL (0x80 | CONFIG_LV_DISP_RA8875_PCLK_MULTIPLIER)
|
|
#else
|
|
#define PCSR_VAL (CONFIG_LV_DISP_RA8875_PCLK_MULTIPLIER)
|
|
#endif
|
|
|
|
// Calculate horizontal display parameters
|
|
#if (CONFIG_LV_DISP_RA8875_HORI_NON_DISP_PERIOD >= 260)
|
|
#define HNDR_VAL (31)
|
|
#else
|
|
#define HNDR_VAL ((CONFIG_LV_DISP_RA8875_HORI_NON_DISP_PERIOD-12) / 8)
|
|
#endif
|
|
#define HNDFT (CONFIG_LV_DISP_RA8875_HORI_NON_DISP_PERIOD-(8*HNDR_VAL)-12)
|
|
#if LVGL_DISP_RA8875_DE_POLARITY
|
|
#define HNDFTR_VAL (0x80 | HNDFT)
|
|
#else
|
|
#define HNDFTR_VAL (HNDFT)
|
|
#endif
|
|
#define HSTR_VAL (CONFIG_LV_DISP_RA8875_HSYNC_START/8 - 1)
|
|
#define HPW (CONFIG_LV_DISP_RA8875_HSYNC_PW/8 - 1)
|
|
#if LVGL_DISP_RA8875_HSYNC_POLARITY
|
|
#define HPWR_VAL (0x80 | HPW)
|
|
#else
|
|
#define HPWR_VAL (HPW)
|
|
#endif
|
|
|
|
// Calculate vertical display parameters
|
|
#define VNDR_VAL (CONFIG_LV_DISP_RA8875_VERT_NON_DISP_PERIOD - 1)
|
|
#define VSTR_VAL (CONFIG_LV_DISP_RA8875_VSYNC_START - 1)
|
|
#define VPW (CONFIG_LV_DISP_RA8875_VSYNC_PW - 1)
|
|
#if LVGL_DISP_RA8875_VSYNC_POLARITY
|
|
#define VPWR_VAL (0x80 | VPW)
|
|
#else
|
|
#define VPWR_VAL (VPW)
|
|
#endif
|
|
|
|
/**********************
|
|
* TYPEDEFS
|
|
**********************/
|
|
|
|
/**********************
|
|
* STATIC PROTOTYPES
|
|
**********************/
|
|
static void ra8875_configure_clocks(bool high_speed);
|
|
static void ra8875_set_memory_write_cursor(unsigned int x, unsigned int y);
|
|
static void ra8875_set_window(unsigned int xs, unsigned int xe, unsigned int ys, unsigned int ye);
|
|
static void ra8875_send_buffer(uint8_t * data, size_t length, bool signal_flush);
|
|
|
|
/**********************
|
|
* STATIC VARIABLES
|
|
**********************/
|
|
|
|
/**********************
|
|
* MACROS
|
|
**********************/
|
|
|
|
/**********************
|
|
* GLOBAL FUNCTIONS
|
|
**********************/
|
|
|
|
void ra8875_init(void)
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
struct {
|
|
uint8_t cmd; // Register address of command
|
|
uint8_t data; // Value to write to register
|
|
} init_cmds[] = {
|
|
{RA8875_REG_SYSR, SYSR_VAL}, // System Configuration Register (SYSR)
|
|
{RA8875_REG_HDWR, HDWR_VAL}, // LCD Horizontal Display Width Register (HDWR)
|
|
{RA8875_REG_HNDFTR, HNDFTR_VAL}, // Horizontal Non-Display Period Fine Tuning Option Register (HNDFTR)
|
|
{RA8875_REG_HNDR, HNDR_VAL}, // Horizontal Non-Display Period Register (HNDR)
|
|
{RA8875_REG_HSTR, HSTR_VAL}, // HSYNC Start Position Register (HSTR)
|
|
{RA8875_REG_HPWR, HPWR_VAL}, // HSYNC Pulse Width Register (HPWR)
|
|
{RA8875_REG_VDHR0, VDHR_VAL & 0x0FF}, // LCD Vertical Display Height Register (VDHR0)
|
|
{RA8875_REG_VDHR1, VDHR_VAL >> 8}, // LCD Vertical Display Height Register0 (VDHR1)
|
|
{RA8875_REG_VNDR0, VNDR_VAL & 0x0FF}, // LCD Vertical Non-Display Period Register (VNDR0)
|
|
{RA8875_REG_VNDR1, VNDR_VAL >> 8}, // LCD Vertical Non-Display Period Register (VNDR1)
|
|
{RA8875_REG_VSTR0, VSTR_VAL & 0x0FF}, // VSYNC Start Position Register (VSTR0)
|
|
{RA8875_REG_VSTR1, VSTR_VAL >> 8}, // VSYNC Start Position Register (VSTR1)
|
|
{RA8875_REG_VPWR, VPWR_VAL}, // VSYNC Pulse Width Register (VPWR)
|
|
{RA8875_REG_DPCR, DPCR_VAL}, // Display Configuration Register (DPCR)
|
|
{RA8875_REG_MWCR0, 0x00}, // Memory Write Control Register 0 (MWCR0)
|
|
{RA8875_REG_MWCR1, 0x00}, // Memory Write Control Register 1 (MWCR1)
|
|
{RA8875_REG_LTPR0, 0x00}, // Layer Transparency Register0 (LTPR0)
|
|
{RA8875_REG_LTPR1, 0x00}, // Layer Transparency Register1 (LTPR1)
|
|
};
|
|
#define INIT_CMDS_SIZE (sizeof(init_cmds)/sizeof(init_cmds[0]))
|
|
|
|
ESP_LOGI(TAG, "Initializing RA8875...");
|
|
|
|
// Initialize non-SPI GPIOs
|
|
|
|
#if RA8875_USE_RST
|
|
gpio_reset_pin(RA8875_RST);
|
|
gpio_set_direction(RA8875_RST, GPIO_MODE_OUTPUT);
|
|
|
|
// Reset the RA8875
|
|
gpio_set_level(RA8875_RST, 0);
|
|
vTaskDelay(DIV_ROUND_UP(100, portTICK_PERIOD_MS));
|
|
gpio_set_level(RA8875_RST, 1);
|
|
vTaskDelay(DIV_ROUND_UP(100, portTICK_PERIOD_MS));
|
|
#endif
|
|
|
|
#ifdef CONFIG_LV_DISP_PIN_BCKL
|
|
gpio_reset_pin(CONFIG_LV_DISP_PIN_BCKL);
|
|
gpio_set_direction(CONFIG_LV_DISP_PIN_BCKL, GPIO_MODE_OUTPUT);
|
|
#endif
|
|
|
|
// Initalize RA8875 clocks (SPI must be decelerated before initializing clocks)
|
|
disp_spi_change_device_speed(SPI_CLOCK_SPEED_SLOW_HZ);
|
|
ra8875_configure_clocks(true);
|
|
disp_spi_change_device_speed(-1);
|
|
|
|
// Send all the commands
|
|
for (i = 0; i < INIT_CMDS_SIZE; i++) {
|
|
ra8875_write_cmd(init_cmds[i].cmd, init_cmds[i].data);
|
|
}
|
|
|
|
// Perform a memory clear (wait maximum of 100 ticks)
|
|
ra8875_write_cmd(RA8875_REG_MCLR, 0x80);
|
|
for(i = 100; i != 0; i--) {
|
|
if ((ra8875_read_cmd(RA8875_REG_MCLR) & 0x80) == 0x00) {
|
|
break;
|
|
}
|
|
vTaskDelay(1);
|
|
}
|
|
if (i == 0) {
|
|
ESP_LOGW(TAG, "WARNING: Memory clear timed out; RA8875 may be unresponsive.");
|
|
}
|
|
|
|
// Enable the display
|
|
ra8875_enable_display(true);
|
|
}
|
|
|
|
void ra8875_enable_display(bool enable)
|
|
{
|
|
ESP_LOGI(TAG, "%s display.", enable ? "Enabling" : "Disabling");
|
|
uint8_t val = enable ? (0x80) : (0x00);
|
|
ra8875_write_cmd(RA8875_REG_PWRR, val); // Power and Display Control Register (PWRR)
|
|
}
|
|
|
|
|
|
void ra8875_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map)
|
|
{
|
|
static lv_coord_t x1 = LV_COORD_MIN;
|
|
static lv_coord_t x2 = LV_COORD_MIN;
|
|
static lv_coord_t x = LV_COORD_MIN;
|
|
static lv_coord_t y = LV_COORD_MIN;
|
|
|
|
size_t linelen = (area->x2 - area->x1 + 1);
|
|
uint8_t * buffer = (uint8_t*)color_map;
|
|
|
|
#if DEBUG
|
|
ESP_LOGI(TAG, "flush: %d,%d at %d,%d", area->x1, area->x2, area->y1, area->y2 );
|
|
#endif
|
|
|
|
// Get lock
|
|
disp_spi_acquire();
|
|
|
|
// Set window if needed
|
|
if ((x1 != area->x1) || (x2 != area->x2)) {
|
|
#if DEBUG
|
|
ESP_LOGI(TAG, "flush: set window (x1,x2): %d,%d -> %d,%d", x1, x2, area->x1, area->x2);
|
|
#endif
|
|
ra8875_set_window(area->x1, area->x2, 0, LV_VER_RES_MAX-1);
|
|
x1 = area->x1;
|
|
x2 = area->x2;
|
|
}
|
|
|
|
// Set cursor if needed
|
|
if ((x != area->x1) || (y != area->y1)) {
|
|
#if DEBUG
|
|
ESP_LOGI(TAG, "flush: set cursor (x,y): %d,%d -> %d,%d", x, y, area->x1, area->y1);
|
|
#endif
|
|
ra8875_set_memory_write_cursor(area->x1, area->y1);
|
|
x = area->x1;
|
|
}
|
|
|
|
// Update to future cursor location
|
|
y = area->y2 + 1;
|
|
if (y >= LV_VER_RES_MAX) {
|
|
y = 0;
|
|
}
|
|
|
|
// Write data
|
|
ra8875_send_buffer(buffer, (area->y2 - area->y1 + 1)*BYTES_PER_PIXEL*linelen, true);
|
|
|
|
// Release lock
|
|
disp_spi_release();
|
|
}
|
|
|
|
void ra8875_sleep_in(void)
|
|
{
|
|
disp_spi_change_device_speed(SPI_CLOCK_SPEED_SLOW_HZ);
|
|
|
|
ra8875_configure_clocks(false);
|
|
|
|
ra8875_write_cmd(RA8875_REG_PWRR, 0x00); // Power and Display Control Register (PWRR)
|
|
vTaskDelay(DIV_ROUND_UP(20, portTICK_PERIOD_MS));
|
|
ra8875_write_cmd(RA8875_REG_PWRR, 0x02); // Power and Display Control Register (PWRR)
|
|
}
|
|
|
|
void ra8875_sleep_out(void)
|
|
{
|
|
ra8875_write_cmd(RA8875_REG_PWRR, 0x00); // Power and Display Control Register (PWRR)
|
|
vTaskDelay(DIV_ROUND_UP(20, portTICK_PERIOD_MS));
|
|
|
|
ra8875_configure_clocks(true);
|
|
|
|
disp_spi_change_device_speed(-1);
|
|
|
|
ra8875_write_cmd(RA8875_REG_PWRR, 0x80); // Power and Display Control Register (PWRR)
|
|
vTaskDelay(DIV_ROUND_UP(20, portTICK_PERIOD_MS));
|
|
}
|
|
|
|
uint8_t ra8875_read_cmd(uint8_t cmd)
|
|
{
|
|
uint8_t buf[4] = {RA8875_MODE_CMD_WRITE, cmd, RA8875_MODE_DATA_READ, 0x00};
|
|
disp_spi_transaction(buf, sizeof(buf), (disp_spi_send_flag_t)(DISP_SPI_RECEIVE | DISP_SPI_SEND_POLLING), buf, 0, 0);
|
|
return buf[3];
|
|
}
|
|
|
|
void ra8875_write_cmd(uint8_t cmd, uint8_t data)
|
|
{
|
|
uint8_t buf[4] = {RA8875_MODE_CMD_WRITE, cmd, RA8875_MODE_DATA_WRITE, data};
|
|
disp_spi_send_data(buf, sizeof(buf));
|
|
}
|
|
|
|
/**********************
|
|
* STATIC FUNCTIONS
|
|
**********************/
|
|
|
|
void ra8875_configure_clocks(bool high_speed)
|
|
{
|
|
uint8_t val;
|
|
|
|
val = high_speed ? ((CONFIG_LV_DISP_RA8875_PLLDIVM << 7) | CONFIG_LV_DISP_RA8875_PLLDIVN) : 0x07;
|
|
ra8875_write_cmd(RA8875_REG_PLLC1, val); // PLL Control Register 1 (PLLC1)
|
|
vTaskDelay(1);
|
|
|
|
val = high_speed ? CONFIG_LV_DISP_RA8875_PLLDIVK : 0x03;
|
|
ra8875_write_cmd(RA8875_REG_PLLC2, val); // PLL Control Register 2 (PLLC2)
|
|
vTaskDelay(1);
|
|
|
|
ra8875_write_cmd(RA8875_REG_PCSR, PCSR_VAL); // Pixel Clock Setting Register (PCSR)
|
|
vTaskDelay(DIV_ROUND_UP(20, portTICK_PERIOD_MS));
|
|
}
|
|
|
|
static void ra8875_set_window(unsigned int xs, unsigned int xe, unsigned int ys, unsigned int ye)
|
|
{
|
|
ra8875_write_cmd(RA8875_REG_HSAW0, (uint8_t)(xs & 0x0FF)); // Horizontal Start Point 0 of Active Window (HSAW0)
|
|
ra8875_write_cmd(RA8875_REG_HSAW1, (uint8_t)(xs >> 8)); // Horizontal Start Point 1 of Active Window (HSAW1)
|
|
ra8875_write_cmd(RA8875_REG_VSAW0, (uint8_t)(ys & 0x0FF)); // Vertical Start Point 0 of Active Window (VSAW0)
|
|
ra8875_write_cmd(RA8875_REG_VSAW1, (uint8_t)(ys >> 8)); // Vertical Start Point 1 of Active Window (VSAW1)
|
|
ra8875_write_cmd(RA8875_REG_HEAW0, (uint8_t)(xe & 0x0FF)); // Horizontal End Point 0 of Active Window (HEAW0)
|
|
ra8875_write_cmd(RA8875_REG_HEAW1, (uint8_t)(xe >> 8)); // Horizontal End Point 1 of Active Window (HEAW1)
|
|
ra8875_write_cmd(RA8875_REG_VEAW0, (uint8_t)(ye & 0x0FF)); // Vertical End Point of Active Window 0 (VEAW0)
|
|
ra8875_write_cmd(RA8875_REG_VEAW1, (uint8_t)(ye >> 8)); // Vertical End Point of Active Window 1 (VEAW1)
|
|
}
|
|
|
|
static void ra8875_set_memory_write_cursor(unsigned int x, unsigned int y)
|
|
{
|
|
ra8875_write_cmd(RA8875_REG_CURH0, (uint8_t)(x & 0x0FF)); // Memory Write Cursor Horizontal Position Register 0 (CURH0)
|
|
ra8875_write_cmd(RA8875_REG_CURH1, (uint8_t)(x >> 8)); // Memory Write Cursor Horizontal Position Register 1 (CURH1)
|
|
ra8875_write_cmd(RA8875_REG_CURV0, (uint8_t)(y & 0x0FF)); // Memory Write Cursor Vertical Position Register 0 (CURV0)
|
|
ra8875_write_cmd(RA8875_REG_CURV1, (uint8_t)(y >> 8)); // Memory Write Cursor Vertical Position Register 1 (CURV1)
|
|
}
|
|
|
|
static void ra8875_send_buffer(uint8_t * data, size_t length, bool signal_flush)
|
|
{
|
|
disp_spi_send_flag_t flags = DISP_SPI_SEND_QUEUED | DISP_SPI_ADDRESS_24;
|
|
if (signal_flush) {
|
|
flags |= DISP_SPI_SIGNAL_FLUSH;
|
|
}
|
|
const uint64_t prefix = (RA8875_MODE_CMD_WRITE << 16) // Command write mode
|
|
| (RA8875_REG_MRWC << 8) // Memory Read/Write Command (MRWC)
|
|
| (RA8875_MODE_DATA_WRITE); // Data write mode
|
|
disp_spi_transaction(data, length, flags, NULL, prefix, 0);
|
|
}
|