You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/trezorhal/stm32f4/display/ug-2828/display_driver.c

394 lines
12 KiB

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <string.h>
#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);
*/