/** * @file ra8875.c * */ /********************* * INCLUDES *********************/ #include "ra8875.h" #include "disp_spi.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" /********************* * DEFINES *********************/ #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) #define BYTES_PER_PIXEL (LV_COLOR_DEPTH / 8) #if defined (LV_HOR_RES_MAX) #define HDWR_VAL (LV_HOR_RES_MAX/8 - 1) #else /* ToDo Remove magic number 256u */ #define HDWR_VAL (256u/8u - 1u) #endif #if defined (LV_VER_RES_MAX) #define VDHR_VAL (LV_VER_RES_MAX - 1) #else /* ToDo Remove magic number 128u */ #define VDHR_VAL (128u - 1u) #endif #define VDIR_MASK (1 << 2) #define HDIR_MASK (1 << 3) #ifndef CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 // Use this settings if there is no Kconfig settings defined #define SYSR_VAL (0x00) #define DPCR_VAL (0x00) #define PCSR_VAL (0x00) #define HNDR_VAL (0x00) #define HNDFTR_VAL (0x00) #define HSTR_VAL (0x00) #define HPW (0x00) #define HPWR_VAL (0x00) #define VNDR_VAL (0x00) #define VSTR_VAL (0x00) #define VPW (0x00) #define VPWR_VAL (0x00) #define CONFIG_LV_DISP_RA8875_PLLDIVM (0x00) #define CONFIG_LV_DISP_RA8875_PLLDIVN (0x00) #define CONFIG_LV_DISP_RA8875_PLLDIVK (0x00) #else #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 #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 #endif // CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 /********************** * 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 void ra8875_reset(void); /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void ra8875_init(void) { #ifndef CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 assert(false); // This driver is not properly configured #endif 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])) LV_LOG_INFO("Initializing RA8875..."); ra8875_reset(); // 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) { LV_LOG_WARN("WARNING: Memory clear timed out; RA8875 may be unresponsive."); } // Enable the display ra8875_enable_display(true); } void ra8875_enable_display(bool enable) { LV_LOG_INFO("%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; LV_LOG_INFO("flush: %d,%d at %d,%d", area->x1, area->x2, area->y1, area->y2 ); // Get lock disp_spi_acquire(); // Set window if needed if ((x1 != area->x1) || (x2 != area->x2)) { LV_LOG_INFO("flush: set window (x1,x2): %d,%d -> %d,%d", x1, x2, area->x1, area->x2); unsigned int ye = 0; #if LVGL_VERSION_MAJOR < 8 ye = LV_VER_RES_MAX - 1; #else /* ToDo Get y end from driver information */ #endif ra8875_set_window(area->x1, area->x2, 0, ye); x1 = area->x1; x2 = area->x2; } // Set cursor if needed if ((x != area->x1) || (y != area->y1)) { LV_LOG_INFO("flush: set cursor (x,y): %d,%d -> %d,%d", x, y, area->x1, area->y1); ra8875_set_memory_write_cursor(area->x1, area->y1); x = area->x1; } // Update to future cursor location y = area->y2 + 1; lv_coord_t ver_max = 0; #if LVGL_VERSION_MAJOR < 8 ver_max = LV_VER_RES_MAX; #else /* ToDo Get vertical max from driver information */ ver_max = lv_disp_get_ver_res((lv_disp_t *) drv); #endif if (y >= ver_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); } static void ra8875_reset(void) { #if RA8875_USE_RST 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 }