/* * This file is part of the Trezor project, https://trezor.io/ * * Copyright (c) SatoshiLabs * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include TREZOR_BOARD #include STM32_HAL_H #include "xdisplay.h" #if (DISPLAY_RESX != 128) || (DISPLAY_RESY != 128) #error "Incompatible display resolution" #endif // This file implements display driver for monochromatic display V-2864KSWEG01 // with 128x128 resolution connected to CPU via SPI interface. // // This type of displayed was used on some preliminary dev kits for T3T1 (Trezor // TS3) // Display driver context. typedef struct { // Frame buffer (8-bit Mono) uint8_t framebuf[DISPLAY_RESX * DISPLAY_RESY]; // Current display orientation (0 or 180) int orientation_angle; // Current backlight level ranging from 0 to 255 int backlight_level; } display_driver_t; // Display driver instance static display_driver_t g_display_driver; // Macros to access display parallel interface // FSMC/FMC Bank 1 - NOR/PSRAM 1 #define DISPLAY_MEMORY_BASE 0x60000000 #define DISPLAY_MEMORY_PIN 16 #define CMD_ADDR *((__IO uint8_t *)((uint32_t)(DISPLAY_MEMORY_BASE))) #define DATA_ADDR \ (*((__IO uint8_t *)((uint32_t)(DISPLAY_MEMORY_BASE | \ (1 << DISPLAY_MEMORY_PIN))))) #define ISSUE_CMD_BYTE(X) \ do { \ (CMD_ADDR) = (X); \ } while (0) #define ISSUE_DATA_BYTE(X) \ do { \ (DATA_ADDR) = (X); \ } while (0) // --------------------------------------------------------------------------- // Display controller registers // --------------------------------------------------------------------------- #define OLED_SETCONTRAST 0x81 #define OLED_DISPLAYALLON_RESUME 0xA4 #define OLED_DISPLAYALLON 0xA5 #define OLED_NORMALDISPLAY 0xA6 #define OLED_INVERTDISPLAY 0xA7 #define OLED_DISPLAYOFF 0xAE #define OLED_DISPLAYON 0xAF #define OLED_SETDISPLAYOFFSET 0xD3 #define OLED_SETCOMPINS 0xDA #define OLED_SETVCOMDETECT 0xDB #define OLED_SETDISPLAYCLOCKDIV 0xD5 #define OLED_SETPRECHARGE 0xD9 #define OLED_SETMULTIPLEX 0xA8 #define OLED_SETLOWCOLUMN 0x00 #define OLED_SETHIGHCOLUMN 0x10 #define OLED_SETSTARTLINE 0x40 #define OLED_MEMORYMODE 0x20 #define OLED_COMSCANINC 0xC0 #define OLED_COMSCANDEC 0xC8 #define OLED_SEGREMAP 0xA0 #define OLED_CHARGEPUMP 0x8D // Dipslay specific initialization sequence static const uint8_t ug_2828tswig01_init_seq[] = { OLED_DISPLAYOFF, // Divide ratio 0, Oscillator Frequency +0% OLED_SETDISPLAYCLOCKDIV, 0x50, // Set Memory Addressing Mode - page addressing mode 0x20, // Set Contrast Control Register OLED_SETCONTRAST, 0x8F, // Set DC-DC Setting: (Double Bytes Command) 0xAD, 0x8A, // Set Segment Re-map OLED_SEGREMAP | 0x01, // Set COM Output Scan Direction OLED_COMSCANDEC, // Set Display Start Line:(Double Bytes Command) 0xDC, 0x00, // Set Display Offset:(Double Bytes Command) OLED_SETDISPLAYOFFSET, 0x00, // Set Discharge / Pre-Charge Period (Double Bytes Command) OLED_SETPRECHARGE, 0x22, // Set VCOM Deselect Level OLED_SETVCOMDETECT, 0x35, // Set Multiplex Ratio OLED_SETMULTIPLEX, 0x7F, // Set Page 0xB0, // Reset column OLED_SETLOWCOLUMN | 0, OLED_SETHIGHCOLUMN | 0, // Set Entire Display Off // to be clear, this command turns off the function // which turns entire display on, but it does not clear // the data in display RAM OLED_DISPLAYALLON_RESUME, // Set Normal Display OLED_NORMALDISPLAY}; static void __attribute__((unused)) display_sleep(void) { // Display OFF ISSUE_CMD_BYTE(OLED_DISPLAYOFF); HAL_Delay(5); // Vpp disable HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_RESET); } static void display_resume(void) { // Vpp enable HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_SET); // 100 ms mandatory wait HAL_Delay(100); // Display ON ISSUE_CMD_BYTE(OLED_DISPLAYON); } // Sets the display cursor to the specific row and column static void display_set_page_and_col(uint8_t page, uint8_t col) { if (page < (DISPLAY_RESY / 8)) { ISSUE_CMD_BYTE(0xB0 | (page & 0xF)); if (col < DISPLAY_RESX) { ISSUE_CMD_BYTE(OLED_SETHIGHCOLUMN | ((col & 0x70) >> 4)); ISSUE_CMD_BYTE(OLED_SETLOWCOLUMN | (col & 0x0F)); } else { // Reset column to start ISSUE_CMD_BYTE(OLED_SETHIGHCOLUMN); ISSUE_CMD_BYTE(OLED_SETLOWCOLUMN); } } } #define COLLECT_ROW_BYTE(src) \ (0 | (*(src + (0 * DISPLAY_RESX)) ? 128 : 0) | \ (*(src + (1 * DISPLAY_RESX)) ? 64 : 0) | \ (*(src + (2 * DISPLAY_RESX)) ? 32 : 0) | \ (*(src + (3 * DISPLAY_RESX)) ? 16 : 0) | \ (*(src + (4 * DISPLAY_RESX)) ? 8 : 0) | \ (*(src + (5 * DISPLAY_RESX)) ? 4 : 0) | \ (*(src + (6 * DISPLAY_RESX)) ? 2 : 0) | \ (*(src + (7 * DISPLAY_RESX)) ? 1 : 0)) // Copies the framebuffer to the display via SPI interface static void display_sync_with_fb(void) { display_driver_t *drv = &g_display_driver; for (int y = 0; y < DISPLAY_RESY / 8; y++) { display_set_page_and_col(y, 0); uint8_t *src = &drv->framebuf[y * DISPLAY_RESX * 8]; for (int x = 0; x < DISPLAY_RESX; x++) { ISSUE_DATA_BYTE(COLLECT_ROW_BYTE(src)); src++; } } } static void display_init_controller(void) { // LCD_RST/PC14 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); // wait 10 milliseconds. only needs to be low for 10 microseconds. // my dev display module ties display reset and touch panel reset together. // keeping this low for max(display_reset_time, ctpm_reset_time) aids // development and does not hurt. HAL_Delay(10); // LCD_RST/PC14 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); // max wait time for hardware reset is 120 milliseconds // (experienced display flakiness using only 5ms wait before sending commands) HAL_Delay(120); // Apply initialization sequence specific to this display controller/panel for (int i = 0; i < sizeof(ug_2828tswig01_init_seq); i++) { ISSUE_CMD_BYTE(ug_2828tswig01_init_seq[i]); } // Resume the suspended display display_resume(); // Clear display internal framebuffer display_sync_with_fb(); } static void display_init_interface(void) { // init peripherals __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_FMC_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStructure = {0}; // LCD_RST/PC14 GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStructure.Alternate = 0; GPIO_InitStructure.Pin = GPIO_PIN_14; // default to keeping display in reset HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); // VPP Enable GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLDOWN; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStructure.Alternate = 0; GPIO_InitStructure.Pin = GPIO_PIN_8; HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_RESET); HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Alternate = GPIO_AF12_FMC; // LCD_CS/PD7 LCD_RS/PD11 LCD_RD/PD4 LCD_WR/PD5 GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_4 | GPIO_PIN_5; HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); // LCD_D0/PD14 LCD_D1/PD15 LCD_D2/PD0 LCD_D3/PD1 GPIO_InitStructure.Pin = GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0 | GPIO_PIN_1; HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); // LCD_D4/PE7 LCD_D5/PE8 LCD_D6/PE9 LCD_D7/PE10 GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); // Reference UM1725 "Description of STM32F4 HAL and LL drivers", // section 64.2.1 "How to use this driver" SRAM_HandleTypeDef display_sram = {0}; display_sram.Instance = FMC_NORSRAM_DEVICE; display_sram.Extended = FMC_NORSRAM_EXTENDED_DEVICE; display_sram.Init.NSBank = FMC_NORSRAM_BANK1; display_sram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE; display_sram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM; display_sram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8; display_sram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE; display_sram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW; display_sram.Init.WrapMode = FMC_WRAP_MODE_DISABLE; display_sram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS; display_sram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE; display_sram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE; display_sram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE; display_sram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE; display_sram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE; display_sram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY; display_sram.Init.PageSize = FMC_PAGE_SIZE_NONE; // reference RM0090 section 37.5 Table 259, 37.5.4, Mode 1 SRAM, and 37.5.6 FMC_NORSRAM_TimingTypeDef normal_mode_timing = {0}; normal_mode_timing.AddressSetupTime = 10; normal_mode_timing.AddressHoldTime = 10; normal_mode_timing.DataSetupTime = 10; normal_mode_timing.BusTurnAroundDuration = 0; normal_mode_timing.CLKDivision = 2; normal_mode_timing.DataLatency = 2; normal_mode_timing.AccessMode = FMC_ACCESS_MODE_A; HAL_SRAM_Init(&display_sram, &normal_mode_timing, NULL); } void display_init(void) { display_driver_t *drv = &g_display_driver; memset(drv, 0, sizeof(display_driver_t)); // Initialize GPIO & FSMC controller display_init_interface(); // Initialize display controller display_init_controller(); } void display_reinit(void) { display_driver_t *drv = &g_display_driver; memset(drv, 0, sizeof(display_driver_t)); // !@# TODO backlight level?? } void display_finish_actions(void) { /// Not used and intentionally left empty } int display_set_backlight(int level) { display_driver_t *drv = &g_display_driver; if (level != drv->backlight_level) { if (level >= 0 && level <= 255) { drv->backlight_level = level; // Set Contrast Control Register: (Double Bytes Command) ISSUE_CMD_BYTE(OLED_SETCONTRAST); ISSUE_CMD_BYTE(level & 0xFF); } } return drv->backlight_level; } int display_get_backlight(void) { display_driver_t *drv = &g_display_driver; return drv->backlight_level; } int display_set_orientation(int angle) { display_driver_t *drv = &g_display_driver; if (angle != drv->orientation_angle) { if (angle == 0 || angle == 180) { drv->orientation_angle = angle; if (angle == 0) { // Set Segment Re-map: (A0H - A1H) ISSUE_CMD_BYTE(OLED_SEGREMAP | 0x01); // Set COM Output Scan Direction ISSUE_CMD_BYTE(OLED_COMSCANDEC); } else { // Set Segment Re-map: (A0H - A1H) ISSUE_CMD_BYTE(OLED_SEGREMAP | 0x00); // Set COM Output Scan Direction ISSUE_CMD_BYTE(OLED_COMSCANINC); } } } return drv->orientation_angle; } int display_get_orientation(void) { display_driver_t *drv = &g_display_driver; return drv->orientation_angle; } void *display_get_frame_addr(void) { display_driver_t *drv = &g_display_driver; return &drv->framebuf[0]; } void display_refresh(void) { display_sync_with_fb(); } const char *display_save(const char *prefix) { return NULL; } void display_clear_save(void) {} void display_set_compatible_settings() {} // Functions for drawing on display /* // Fills a rectangle with a specified color void display_fill(gdc_dma2d_t *dp); // Copies an RGB565 bitmap to specified rectangle void display_copy_rgb565(gdc_dma2d_t *dp); // Copies a MONO4 bitmap to specified rectangle void display_copy_mono4(gdc_dma2d_t *dp); // Copies a MONO1P bitmap to specified rectangle void display_copy_mono1p(gdc_dma2d_t *dp); */