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/vg-2864/display_driver.c

352 lines
11 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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include TREZOR_BOARD
#include STM32_HAL_H
#include "xdisplay.h"
#ifdef USE_CONSUMPTION_MASK
#include "consumption_mask.h"
#endif
#if (DISPLAY_RESX != 128) || (DISPLAY_RESY != 64)
#error "Incompatible display resolution"
#endif
// This file implements display driver for monochromatic display V-2864KSWEG01
// with 128x64 resolution connected to CPU via SPI interface.
//
// This type of display is used with T3T1 model (Trezor TS3)
// Display driver context.
typedef struct {
// SPI driver instance
SPI_HandleTypeDef spi;
// 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;
// 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
// Display controller initialization sequence
static const uint8_t vg_2864ksweg01_init_seq[] = {OLED_DISPLAYOFF,
OLED_SETDISPLAYCLOCKDIV,
0x80,
OLED_SETMULTIPLEX,
0x3F, // 128x64
OLED_SETDISPLAYOFFSET,
0x00,
OLED_SETSTARTLINE | 0x00,
OLED_CHARGEPUMP,
0x14,
OLED_MEMORYMODE,
0x00,
OLED_SEGREMAP | 0x01,
OLED_COMSCANDEC,
OLED_SETCOMPINS,
0x12, // 128x64
OLED_SETCONTRAST,
0xCF,
OLED_SETPRECHARGE,
0xF1,
OLED_SETVCOMDETECT,
0x40,
OLED_DISPLAYALLON_RESUME,
OLED_NORMALDISPLAY,
OLED_DISPLAYON};
// Configures SPI driver/controller
static bool display_init_spi(display_driver_t *drv) {
drv->spi.Instance = OLED_SPI;
drv->spi.State = HAL_SPI_STATE_RESET;
drv->spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
drv->spi.Init.Direction = SPI_DIRECTION_2LINES;
drv->spi.Init.CLKPhase = SPI_PHASE_1EDGE;
drv->spi.Init.CLKPolarity = SPI_POLARITY_LOW;
drv->spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
drv->spi.Init.CRCPolynomial = 7;
drv->spi.Init.DataSize = SPI_DATASIZE_8BIT;
drv->spi.Init.FirstBit = SPI_FIRSTBIT_MSB;
drv->spi.Init.NSS = SPI_NSS_HARD_OUTPUT;
drv->spi.Init.TIMode = SPI_TIMODE_DISABLE;
drv->spi.Init.Mode = SPI_MODE_MASTER;
return (HAL_OK == HAL_SPI_Init(&drv->spi)) ? true : false;
}
// Sends specified number of bytes to the display via SPI interface
static void display_send_bytes(display_driver_t *drv, const uint8_t *data,
size_t len) {
volatile int32_t timeout = 1000; // !@# why???
for (int i = 0; i < timeout; i++)
;
if (HAL_OK != HAL_SPI_Transmit(&drv->spi, (uint8_t *)data, len, 1000)) {
// TODO: error
return;
}
while (HAL_SPI_STATE_READY != HAL_SPI_GetState(&drv->spi)) {
}
}
#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(display_driver_t *drv) {
static const uint8_t cursor_set_seq[3] = {OLED_SETLOWCOLUMN | 0x00,
OLED_SETHIGHCOLUMN | 0x00,
OLED_SETSTARTLINE | 0x00};
// SPI select
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
// Set the cursor to the screen top-left corner
display_send_bytes(drv, &cursor_set_seq[0], sizeof(cursor_set_seq));
// SPI deselect
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
// Set to DATA
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET);
// SPI select
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
// Send whole framebuffer to the display
for (int y = 0; y < DISPLAY_RESY / 8; y++) {
uint8_t buff[DISPLAY_RESX];
uint8_t *src = &drv->framebuf[y * DISPLAY_RESX * 8];
if (drv->orientation_angle == 0) {
for (int x = 0; x < DISPLAY_RESX; x++) {
buff[x] = COLLECT_ROW_BYTE(src);
src++;
}
} else {
for (int x = DISPLAY_RESX - 1; x >= 0; x--) {
buff[x] = COLLECT_ROW_BYTE(src);
src++;
}
}
if (HAL_OK != HAL_SPI_Transmit(&drv->spi, &buff[0], sizeof(buff), 1000)) {
// TODO: error
return;
}
}
while (HAL_SPI_STATE_READY != HAL_SPI_GetState(&drv->spi)) {
}
// SPI deselect
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
// Set to CMD
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET);
}
void display_init(void) {
display_driver_t *drv = &g_display_driver;
memset(drv, 0, sizeof(display_driver_t));
drv->backlight_level = 255;
OLED_DC_CLK_ENA();
OLED_CS_CLK_ENA();
OLED_RST_CLK_ENA();
OLED_SPI_SCK_CLK_ENA();
OLED_SPI_MOSI_CLK_ENA();
OLED_SPI_CLK_ENA();
GPIO_InitTypeDef GPIO_InitStructure;
// Set GPIO for OLED display
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = 0;
GPIO_InitStructure.Pin = OLED_CS_PIN;
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
HAL_GPIO_Init(OLED_CS_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = OLED_DC_PIN;
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET);
HAL_GPIO_Init(OLED_DC_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = OLED_RST_PIN;
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET);
HAL_GPIO_Init(OLED_RST_PORT, &GPIO_InitStructure);
// Enable SPI 1 for OLED display
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = OLED_SPI_AF;
GPIO_InitStructure.Pin = OLED_SPI_SCK_PIN;
HAL_GPIO_Init(OLED_SPI_SCK_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = OLED_SPI_MOSI_PIN;
HAL_GPIO_Init(OLED_SPI_MOSI_PORT, &GPIO_InitStructure);
// Initialize SPI controller
display_init_spi(drv);
// Set to CMD
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET);
// SPI deselect
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
// Reset the LCD
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET);
// SPI select
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
// Send initialization command sequence
display_send_bytes(drv, &vg_2864ksweg01_init_seq[0],
sizeof(vg_2864ksweg01_init_seq));
// SPI deselect
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
// Clear display internal framebuffer
display_sync_with_fb(drv);
}
void display_reinit(void) {
display_driver_t *drv = &g_display_driver;
memset(drv, 0, sizeof(display_driver_t));
drv->backlight_level = 255;
display_init_spi(drv);
}
void display_finish_actions(void) {
/// Not used and intentionally left empty
}
int display_set_backlight(int level) {
display_driver_t *drv = &g_display_driver;
drv->backlight_level = 255;
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;
display_sync_with_fb(drv);
}
}
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_driver_t *drv = &g_display_driver;
#if defined USE_CONSUMPTION_MASK && !defined BOARDLOADER
// This is an intentional randomization of the consumption masking algorithm
// after every change on the display
consumption_mask_randomize();
#endif
// Sends the current frame buffer to the display
display_sync_with_fb(drv);
}
const char *display_save(const char *prefix) { return NULL; }
void display_clear_save(void) {}
void display_set_compatible_settings() {}
/*
// Fills a rectangle with a specified color
void display_fill(dma2d_params_t *dp) {
}
// Copies a MONO1P bitmap to specified rectangle
void display_copy_mono1p(dma2d_params_t *dp) {
}
*/