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.
352 lines
11 KiB
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) {
|
|
|
|
}
|
|
|
|
*/
|