feat(core): introduce new drawing library

cepetr/drawlib-integration
cepetr 3 months ago
parent c97d9c0fe0
commit 5a35a60856

@ -233,18 +233,20 @@ build_embed: build_boardloader build_bootloader build_firmware # build boardload
build_boardloader: ## build boardloader
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" $(BOARDLOADER_BUILD_DIR)/boardloader.bin
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOARDLOADER_BUILD_DIR)/boardloader.bin
build_bootloader: ## build bootloader
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin
CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" \
NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin
build_bootloader_ci: ## build CI device testing bootloader
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" $(BOOTLOADER_CI_BUILD_DIR)/bootloader.bin
build_bootloader_emu: ## build the unix bootloader emulator
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf
build_prodtest: ## build production test firmware
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
@ -252,7 +254,7 @@ build_prodtest: ## build production test firmware
build_reflash: ## build reflash firmware + reflash image
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" $(REFLASH_BUILD_DIR)/reflash.bin
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(REFLASH_BUILD_DIR)/reflash.bin
dd if=build/boardloader/boardloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=0
dd if=build/bootloader/bootloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=49152
@ -261,24 +263,26 @@ build_firmware: templates build_cross ## build firmware with frozen modules
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" \
BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" \
DISABLE_OPTIGA="$(DISABLE_OPTIGA)" \
DISABLE_OPTIGA="$(DISABLE_OPTIGA)" NEW_RENDERING="$(NEW_RENDERING)"\
$(FIRMWARE_BUILD_DIR)/firmware.bin
build_unix: templates ## build unix port
$(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)"
PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
NEW_RENDERING="$(NEW_RENDERING)"
build_unix_frozen: templates build_cross ## build unix port with frozen modules
$(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" TREZOR_EMULATOR_FROZEN=1
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" TREZOR_EMULATOR_FROZEN=1 NEW_RENDERING="$(NEW_RENDERING)"
build_unix_debug: templates ## build unix port
$(SCONS) --max-drift=1 CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1
BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1 \
NEW_RENDERING="$(NEW_RENDERING)"
build_cross: ## build mpy-cross port
$(MAKE) -C vendor/micropython/mpy-cross $(CROSS_PORT_OPTS)

@ -87,6 +87,9 @@ SOURCE_MOD += [
'embed/lib/display_utils.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_dma2d_mono8.c',
'embed/lib/gl_dma2d_rgb565.c',
'embed/lib/image.c',
'embed/lib/mini_printf.c',
'embed/lib/terminal.c',
@ -220,6 +223,9 @@ def cargo_build():
features.append("bootloader")
features.extend(FEATURES_AVAILABLE)
if TREZOR_MODEL in ('T',):
features.append('ui_antialiasing')
cargo_opts = [
f'--target={env.get("ENV")["RUST_TARGET"]}',
f'--target-dir=../../build/bootloader/rust',

@ -13,6 +13,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
PYOPT = ARGUMENTS.get('PYOPT', '1')
DISABLE_OPTIGA = ARGUMENTS.get('DISABLE_OPTIGA', '0') == '1'
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
FEATURE_FLAGS = {
@ -25,6 +26,8 @@ FEATURE_FLAGS = {
FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"]
if DISABLE_OPTIGA and PYOPT == '0':
FEATURES_WANTED.remove("optiga")
if NEW_RENDERING:
FEATURES_WANTED.append("new_rendering")
CCFLAGS_MOD = ''
CPPPATH_MOD = []
@ -202,10 +205,12 @@ SOURCE_MOD += [
'embed/extmod/modtrezorui/modtrezorui.c',
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_draw.c',
'embed/lib/display_utils.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_dma2d_rgb565.c',
'embed/lib/gl_dma2d_mono8.c',
'embed/lib/image.c',
'embed/lib/mini_printf.c',
'embed/lib/terminal.c',
@ -216,6 +221,17 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/tinflate.c',
]
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
]
CPPDEFINES_MOD += [
'TREZOR_UI2',
'TRANSLATIONS',
@ -744,8 +760,17 @@ def cargo_build():
features.append('universal_fw')
features.append('ui')
features.append('translations')
if NEW_RENDERING:
features.append('new_rendering')
if PYOPT == '0':
features.append('debug')
features.append('ui_debug')
if TREZOR_MODEL in ('T', 'T3T1', 'DISC1', 'DISC2'):
features.append('ui_antialiasing')
features.append('ui_blurring')
features.append('ui_jpeg_decoder')
features.extend(FEATURES_AVAILABLE)

@ -10,6 +10,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
DMA2D = TREZOR_MODEL in ('T', 'T3T1')
OPTIGA = TREZOR_MODEL in ('R', 'T3T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
if TREZOR_MODEL in ('DISC1', 'DISC2'):
# skip unix build
@ -204,10 +205,12 @@ SOURCE_MOD += [
'embed/extmod/modtrezorui/modtrezorui.c',
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_draw.c',
'embed/lib/display_utils.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_dma2d_rgb565.c',
'embed/lib/gl_dma2d_mono8.c',
'embed/lib/image.c',
'embed/lib/terminal.c',
'embed/lib/translations.c',
@ -217,6 +220,16 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/tinflate.c',
]
if NEW_RENDERING:
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
]
if TREZOR_MODEL in ('1', ):
SOURCE_MOD += [
'embed/models/model_T1B1_layout.c',
@ -250,6 +263,17 @@ if FROZEN:
if RASPI:
CPPDEFINES_MOD += ['TREZOR_EMULATOR_RASPI']
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
if TREZOR_MODEL in ('T',):
CPPDEFINES_MOD += ['DISPLAY_RGB565']
elif TREZOR_MODEL in ('R', '1',):
CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_MONO']
elif TREZOR_MODEL in ('T3T1',):
CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_RGB565']
# modtrezorutils
SOURCE_MOD += [
'embed/extmod/modtrezorutils/modtrezorutils.c',
@ -394,7 +418,6 @@ SOURCE_MICROPYTHON = [
SOURCE_UNIX = [
'embed/trezorhal/unix/boot_args.c',
'embed/trezorhal/unix/common.c',
'embed/trezorhal/unix/display-unix.c',
'embed/trezorhal/unix/flash.c',
'embed/trezorhal/unix/flash_otp.c',
'embed/trezorhal/unix/random_delays.c',
@ -410,6 +433,18 @@ SOURCE_UNIX = [
'vendor/micropython/ports/unix/input.c',
'vendor/micropython/ports/unix/unix_mphal.c',
]
if NEW_RENDERING:
SOURCE_MOD += [
'embed/trezorhal/unix/display_driver.c',
'embed/trezorhal/xdisplay_legacy.c',
]
else:
SOURCE_MOD += [
'embed/trezorhal/unix/display-unix.c',
]
if TREZOR_MODEL in ('T', 'R', 'T3T1'):
SOURCE_UNIX += [
'embed/trezorhal/unix/sbu.c',
@ -424,6 +459,7 @@ if DMA2D:
CPPDEFINES_MOD += [
'USE_DMA2D',
]
SOURCE_UNIX += [
'embed/lib/dma2d_emul.c',
]
@ -820,8 +856,10 @@ if ARGUMENTS.get('TREZOR_EMULATOR_DEBUGGABLE', '0') == '1':
RUST_PROFILE = 'dev'
RUST_LIBDIR = f'build/unix/rust/{TARGET}/debug'
else:
RUST_PROFILE = 'release'
RUST_LIBDIR = f'build/unix/rust/{TARGET}/release'
RUST_PROFILE = 'dev'
RUST_LIBDIR = f'build/unix/rust/{TARGET}/debug'
# RUST_PROFILE = 'release'
# RUST_LIBDIR = f'build/unix/rust/{TARGET}/release'
RUST_LIB = 'trezor_lib'
RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
@ -839,9 +877,24 @@ def cargo_build():
if TREZOR_MODEL in ('T', 'T3T1'):
features.append('touch')
features.append('sd_card')
features.append('ui_antialiasing')
features.append('ui_blurring')
features.append('ui_jpeg_decoder')
if TREZOR_MODEL in ('R', '1'):
features.append('button')
if NEW_RENDERING:
features.append('new_rendering')
if TREZOR_MODEL in ('T',):
features.append('display_rgb565')
elif TREZOR_MODEL in ('R', '1',):
features.append('display_mono')
features.append('xframebuffer')
elif TREZOR_MODEL in ('T3T1',):
features.append('display_rgb565')
features.append('xframebuffer')
env.get('ENV')['TREZOR_MODEL'] = TREZOR_MODEL
return f'cd embed/rust; cargo build --profile {RUST_PROFILE} --target-dir=../../build/unix/rust --no-default-features --features "{" ".join(features)}" --target {TARGET}'

@ -73,4 +73,10 @@ SECTIONS {
*(.boot_args*);
. = ALIGN(8);
} >BOOT_ARGS
.data_ccm : ALIGN(4) {
*(.no_dma_buffers*);
. = ALIGN(4);
} >CCMRAM
}

@ -18,6 +18,8 @@
*/
#include "display.h"
#include "display_draw.h"
#include "fonts/fonts.h"
/// class Display:
/// """

@ -61,8 +61,8 @@ void display_clear(void) {
// set MADCTL first so that we can set the window correctly next
display_orientation(0);
// address the complete frame memory
display_set_window(0, 0, MAX_DISPLAY_RESX - 1, MAX_DISPLAY_RESY - 1);
for (uint32_t i = 0; i < MAX_DISPLAY_RESX * MAX_DISPLAY_RESY; i++) {
display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
for (uint32_t i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
// 2 bytes per pixel because we're using RGB 5-6-5 format
PIXELDATA(0x0000);
}

@ -0,0 +1,49 @@
/*
* 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 "gl_color.h"
#include "colors.h"
const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg_color,
gl_color_t bg_color) {
static gl_color16_t cache[16] = {0};
if (gl_color_to_color16(bg_color) != cache[0] ||
gl_color_to_color16(fg_color) != cache[15]) {
for (int alpha = 0; alpha < 16; alpha++) {
cache[alpha] = gl_color16_blend_a4(fg_color, bg_color, alpha);
}
}
return (const gl_color16_t*)&cache[0];
}
const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg_color,
gl_color_t bg_color) {
static gl_color32_t cache[16] = {0};
if (bg_color != gl_color32_to_color(cache[0]) ||
fg_color != gl_color32_to_color(cache[15])) {
for (int alpha = 0; alpha < 16; alpha++) {
cache[alpha] = gl_color32_blend_a4(fg_color, bg_color, alpha);
}
}
return (const gl_color32_t*)&cache[0];
}

@ -0,0 +1,274 @@
/*
* 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/>.
*/
#ifndef GL_COLOR_H
#define GL_COLOR_H
#include <stdint.h>
#define GL_COLOR_16BIT
// #define GL_COLOR_32BIT
// Color in RGB565 format
//
// |15 8 | 7 0|
// |---------------------------------|
// |r r r r r g g g | g g g b b b b b|
// |---------------------------------|
typedef uint16_t gl_color16_t;
// Color in RGBA8888 format
//
// |31 24 |23 16 |15 8 | 7 0 |
// |----------------------------------------------------------------------|
// |a a a a a a a a | r r r r r r r r | g g g g g g g g | b b b b b b b b |
// |----------------------------------------------------------------------|
//
typedef uint32_t gl_color32_t;
#ifdef GL_COLOR_16BIT
#define gl_color_t gl_color16_t
#define gl_color_to_color16(c) (c)
#define gl_color16_to_color(c) (c)
#define gl_color_to_color32(c) (gl_color16_to_color32(c))
#define gl_color32_to_color(c) (gl_color32_to_color16(c))
#define gl_color_lum(c) (gl_color16_lum(c))
#elif GL_COLOR_32BIT
#define gl_color_t gl_color32_t
#define gl_color_to_color16(c) (gl_color32_to_color16(c))
#define gl_color16_to_color(c) (gl_color16_to_color32(c))
#define gl_color_to_color32(c) (c)
#define gl_color32_to_color(c) (c)
#else
#error "GL_COLOR_16BIT/32BIT not specified"
#endif
// Constructs a 16-bit color from the given red (r),
// green (g), and blue (b) values in the range 0..255
static inline gl_color16_t gl_color16_rgb(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | ((b & 0xF8U) >> 3);
}
// Constructs a 32-bit color from the given red (r),
// green (g), and blue (b) values in the range 0..255.
// Alpha is set to 255.
static inline gl_color32_t gl_color32_rgb(uint8_t r, uint8_t g, uint8_t b) {
return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
// Converts a 16-bit color to a 32-bit color; alpha is set to 255
static inline gl_color32_t gl_color16_to_color32(gl_color16_t color) {
uint32_t r = (color & 0xF800) >> 8;
uint32_t g = (color & 0x07E0) >> 3;
uint32_t b = (color & 0x001F) << 3;
r |= (r >> 5);
g |= (g >> 6);
b |= (b >> 5);
return (0xFFU << 24) | (r << 16) | (g << 8) | b;
}
// Converts 32-bit color to 16-bit color, alpha is ignored
static inline gl_color16_t gl_color32_to_color16(gl_color32_t color) {
uint16_t r = (color & 0x00F80000) >> 8;
uint16_t g = (color & 0x0000FC00) >> 5;
uint16_t b = (color & 0x000000F8) >> 3;
return r | g | b;
}
// Converts 16-bit color into luminance (ranging from 0 to 255)
static inline uint8_t gl_color_lum(gl_color16_t color) {
uint16_t r = (color & 0x00F80000) >> 8;
uint16_t g = (color & 0x0000FC00) >> 5;
uint16_t b = (color & 0x000000F8) >> 3;
return (r + g + b) / 3;
}
#ifdef GL_COLOR_16BIT
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a4(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 11;
uint16_t bg_r = (bg & 0xF800) >> 11;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x07E0) >> 5;
uint16_t bg_g = (bg & 0x07E0) >> 5;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x001F) >> 0;
uint16_t bg_b = (bg & 0x001F) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return (r << 11) | (g << 5) | b;
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a8(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 11;
uint16_t bg_r = (bg & 0xF800) >> 11;
uint16_t r = (fg_r * alpha + (bg_r * (255 - alpha))) / 255;
uint16_t fg_g = (fg & 0x07E0) >> 5;
uint16_t bg_g = (bg & 0x07E0) >> 5;
uint16_t g = (fg_g * alpha + (bg_g * (255 - alpha))) / 255;
uint16_t fg_b = (fg & 0x001F) >> 0;
uint16_t bg_b = (bg & 0x001F) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (255 - alpha))) / 255;
return (r << 11) | (g << 5) | b;
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 32-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gl_color32_t gl_color32_blend_a4(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 8;
fg_r |= fg_r >> 5;
uint16_t bg_r = (bg & 0xF800) >> 8;
bg_r |= bg_r >> 5;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x07E0) >> 2;
fg_g |= fg_g >> 6;
uint16_t bg_g = (bg & 0x07E0) >> 2;
bg_g |= bg_g >> 6;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x001F) << 3;
fg_b |= fg_b >> 5;
uint16_t bg_b = (bg & 0x001F) << 3;
bg_b |= bg_b >> 5;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
#elif GL_COLOR_32BIT
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a4(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0x00FF0000) >> 16;
uint16_t bg_r = (bg & 0x00FF0000) >> 16;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x0000FF00) >> 8;
uint16_t bg_g = (bg & 0x0000FF00) >> 8;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x000000FF) >> 0;
uint16_t bg_b = (bg & 0x000000FF) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return gl_color16_rgb(r, g, b)
}
// Blends foreground and background colors with 8-bit alpha
//
// Returns a color in 16-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 255, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a8(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0x00FF0000) >> 16;
uint16_t bg_r = (bg & 0x00FF0000) >> 16;
uint16_t r = (fg_r * alpha + (bg_r * (255 - alpha))) / 255;
uint16_t fg_g = (fg & 0x0000FF00) >> 8;
uint16_t bg_g = (bg & 0x0000FF00) >> 8;
uint16_t g = (fg_g * alpha + (bg_g * (255 - alpha))) / 255;
uint16_t fg_b = (fg & 0x000000FF) >> 0;
uint16_t bg_b = (bg & 0x000000FF) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (255 - alpha))) / 255;
return gl_color16_rgb(r, g, b)
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 32-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gl_color32_t gl_color32_blend_a4(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0x00FF0000) >> 16;
uint16_t bg_r = (bg & 0x00FF0000) >> 16;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x0000FF00) >> 8;
uint16_t bg_g = (bg & 0x0000FF00) >> 8;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x000000FF) >> 0;
uint16_t bg_b = (bg & 0x000000FF) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return gl_color32_rgb(r, g, b);
}
#else
#error "GL_COLOR_16BIT/32BIT not specified"
#endif
// Returns a gradient as an array of 16 consecutive 16-bit colors
//
// Each element in the array represents a color, with retval[0] being
// the background (bg) color and retval[15] the foreground (fg) color
const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg, gl_color_t bg);
// Returns a gradient as an array of 16 consecutive 32-bit colors
//
// Each element in the array represents a color, with retval[0] being
// the background (bg) color and retval[15] the foreground (fg) color
const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg, gl_color_t bg);
#endif // TREZORHAL_GL_COLOR_H

@ -0,0 +1,68 @@
/*
* 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/>.
*/
#ifndef GL_DMA2D_H
#define GL_DMA2D_H
#include <stdbool.h>
#include <stdint.h>
#include "gl_color.h"
typedef struct {
// Destination bitma[
// Following fields are used for all operations
uint16_t height;
uint16_t width;
void* dst_row;
uint16_t dst_x;
uint16_t dst_y;
uint16_t dst_stride;
// Source bitmap
// Used for copying and blending, but src_fg & src_alpha
// fields are also used for fill operation
void* src_row;
uint16_t src_x;
uint16_t src_y;
uint16_t src_stride;
gl_color_t src_fg;
gl_color_t src_bg;
uint8_t src_alpha;
} dma2d_params_t;
bool rgb565_fill(const dma2d_params_t* dp);
bool rgb565_copy_mono4(const dma2d_params_t* dp);
bool rgb565_copy_rgb565(const dma2d_params_t* dp);
bool rgb565_blend_mono4(const dma2d_params_t* dp);
bool rgba8888_fill(const dma2d_params_t* dp);
bool rgba8888_copy_mono4(const dma2d_params_t* dp);
bool rgba8888_copy_rgb565(const dma2d_params_t* dp);
bool rgba8888_copy_rgba8888(const dma2d_params_t* dp);
bool rgba8888_blend_mono4(const dma2d_params_t* dp);
bool mono8_fill(const dma2d_params_t* dp);
bool mono8_copy_mono1p(const dma2d_params_t* dp);
bool mono8_copy_mono4(const dma2d_params_t* dp);
bool mono8_blend_mono1p(const dma2d_params_t* dp);
bool mono8_blend_mono4(const dma2d_params_t* dp);
#endif // GL_DMA2D_H

@ -0,0 +1,120 @@
/*
* 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 "gl_dma2d.h"
bool mono8_fill(const dma2d_params_t* dp) {
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
uint16_t height = dp->height;
uint8_t fg = gl_color_lum(dp->src_fg);
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
dst_ptr[x] = fg;
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
}
return true;
}
bool mono8_copy_mono1p(const dma2d_params_t* dp) {
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
uint8_t* src = (uint8_t*)dp->src_row;
uint16_t src_ofs = dp->src_stride * dp->src_y + dp->src_x;
uint16_t height = dp->height;
uint8_t fg = gl_color_lum(dp->src_fg);
uint8_t bg = gl_color_lum(dp->src_bg);
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
uint8_t data = src[(src_ofs + x) / 8];
dst_ptr[x] = (data & mask) ? fg : bg;
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_ofs += dp->src_stride;
}
return true;
}
bool mono8_copy_mono4(const dma2d_params_t* dp) {
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
uint8_t* src_row = (uint8_t*)dp->src_row;
uint16_t height = dp->height;
uint8_t fg = gl_color_lum(dp->src_fg);
uint8_t bg = gl_color_lum(dp->src_bg);
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
uint8_t src_data = src_row[(x + dp->src_x) / 2];
uint8_t src_lum = (x + dp->src_x) & 1 ? src_data >> 4 : src_data & 0xF;
dst_ptr[x] = (fg * src_lum + bg * (15 - src_lum)) / 15;
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_row += dp->src_stride / sizeof(*src_row);
}
return true;
}
bool mono8_blend_mono1p(const dma2d_params_t* dp) {
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
uint8_t* src = (uint8_t*)dp->src_row;
uint16_t src_ofs = dp->src_stride * dp->src_y + dp->src_x;
uint16_t height = dp->height;
uint8_t fg = gl_color_lum(dp->src_fg);
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
uint8_t data = src[(src_ofs + x) / 8];
dst_ptr[x] = (data & mask) ? fg : dst_ptr[x];
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_ofs += dp->src_stride;
}
return true;
}
bool mono8_blend_mono4(const dma2d_params_t* dp) {
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
uint8_t* src_row = (uint8_t*)dp->src_row;
uint16_t height = dp->height;
uint8_t fg = gl_color_lum(dp->src_fg);
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
uint8_t src_data = src_row[(x + dp->src_x) / 2];
uint8_t src_alpha = (x + dp->src_x) & 1 ? src_data >> 4 : src_data & 0x0F;
dst_ptr[x] = (fg * src_alpha + dst_ptr[x] * (15 - src_alpha)) / 15;
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_row += dp->src_stride / sizeof(*src_row);
}
return true;
}

@ -0,0 +1,131 @@
/*
* 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 "gl_dma2d.h"
#if USE_DMA2D
#include "dma2d.h"
#endif
bool rgb565_fill(const dma2d_params_t* dp) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (dma2d_accessible(dp->dst_row)) {
return dma2d_rgb565_fill(dp);
} else
#endif
{
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
uint16_t height = dp->height;
if (dp->src_alpha == 255) {
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
dst_ptr[x] = dp->src_fg;
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
}
} else {
uint8_t alpha = dp->src_alpha;
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
dst_ptr[x] = gl_color16_blend_a8(dp->src_fg, dst_ptr[x], alpha);
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
}
}
return true;
}
}
bool rgb565_copy_mono4(const dma2d_params_t* dp) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (dma2d_accessible(dp->dst_row) && dma2d_accessible(dp->src_row)) {
return dma2d_rgb565_copy_mono4(dp);
} else
#endif
{
const gl_color16_t* gradient =
gl_color16_gradient_a4(dp->src_fg, dp->src_bg);
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
uint8_t* src_row = (uint8_t*)dp->src_row;
uint16_t height = dp->height;
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
uint8_t fg_data = src_row[(x + dp->src_x) / 2];
uint8_t fg_lum = (x + dp->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
dst_ptr[x] = gradient[fg_lum];
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_row += dp->src_stride / sizeof(*src_row);
}
return true;
}
}
bool rgb565_copy_rgb565(const dma2d_params_t* dp) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (dma2d_accessible(dp->dst_row) && dma2d_accessible(dp->src_row)) {
return dma2d_rgb565_copy_rgb565(dp);
} else
#endif
{
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
uint16_t* src_ptr = (uint16_t*)dp->src_row + dp->src_x;
uint16_t height = dp->height;
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
dst_ptr[x] = src_ptr[x];
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_ptr += dp->src_stride / sizeof(*src_ptr);
}
return true;
}
}
bool rgb565_blend_mono4(const dma2d_params_t* dp) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (dma2d_accessible(dp->dst_row) && dma2d_accessible(dp->src_row)) {
return dma2d_rgb565_blend_mono4(dp);
} else
#endif
{
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
uint8_t* src_row = (uint8_t*)dp->src_row;
uint16_t height = dp->height;
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
uint8_t fg_data = src_row[(x + dp->src_x) / 2];
uint8_t fg_alpha = (x + dp->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
dst_ptr[x] = gl_color16_blend_a4(
dp->src_fg, gl_color16_to_color(dst_ptr[x]), fg_alpha);
}
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
src_row += dp->src_stride / sizeof(*src_row);
}
return true;
}
}

@ -0,0 +1,226 @@
/*
* 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 <display.h>
#include "display_draw.h"
#include "fonts/fonts.h"
#include "gl_draw.h"
typedef struct {
int16_t dst_x;
int16_t dst_y;
int16_t src_x;
int16_t src_y;
int16_t width;
int16_t height;
} gl_clip_t;
static inline gl_clip_t gl_clip(gl_rect_t dst, const gl_bitmap_t* bitmap) {
int16_t dst_x = dst.x0;
int16_t dst_y = dst.y0;
int16_t src_x = 0;
int16_t src_y = 0;
if (bitmap != NULL) {
src_x += bitmap->offset.x;
src_y += bitmap->offset.y;
// Normalize negative x-offset of bitmap
if (src_x < 0) {
dst_x -= src_x;
src_x = 0;
}
// Normalize negative y-offset of src bitmap
if (src_y < 0) {
dst_y -= src_y;
src_y = 0;
}
}
// Normalize negative top-left of destination rectangle
if (dst_x < 0) {
src_x -= dst_x;
dst_x = 0;
}
if (dst_y < 0) {
src_y -= dst_y;
dst_y = 0;
}
// Calculate dimension of effective rectangle
int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x;
int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y;
if (bitmap != NULL) {
width = MIN(width, bitmap->size.x - src_x);
height = MIN(height, bitmap->size.y - src_y);
}
gl_clip_t clip = {
.dst_x = dst_x,
.dst_y = dst_y,
.src_x = src_x,
.src_y = src_y,
.width = width,
.height = height,
};
return clip;
}
void gl_draw_bar(gl_rect_t rect, gl_color_t color) {
gl_clip_t clip = gl_clip(rect, NULL);
if (clip.width <= 0 || clip.height <= 0) {
return;
}
dma2d_params_t dp = {
// Destination bitmap
.height = clip.height,
.width = clip.width,
.dst_row = NULL,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = 0,
// Source bitmap
.src_fg = color,
.src_alpha = 255,
};
display_fill(&dp);
}
void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap) {
gl_clip_t clip = gl_clip(rect, bitmap);
if (clip.width <= 0 || clip.height <= 0) {
return;
}
dma2d_params_t dp = {
// Destination bitmap
.height = clip.height,
.width = clip.width,
.dst_row = NULL,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = 0,
// Source bitmap
.src_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.src_y,
.src_x = clip.src_x,
.src_y = clip.src_y,
.src_stride = bitmap->stride,
.src_fg = bitmap->fg_color,
.src_bg = bitmap->bg_color,
.src_alpha = 255,
};
#if TREZOR_FONT_BPP == 1
if (bitmap->format == GL_FORMAT_MONO1P) {
display_copy_mono1p(&dp);
}
#endif
#if TREZOR_FONT_BPP == 4
if (bitmap->format == GL_FORMAT_MONO4) {
display_copy_mono4(&dp);
}
#endif
}
#if TREZOR_FONT_BPP == 1
#define GLYPH_FORMAT GL_FORMAT_MONO1P
#define GLYPH_STRIDE(w) (((w) + 7) / 8)
#elif TREZOR_FONT_BPP == 2
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GL_FORMAT_MONO2
#define GLYPH_STRIDE(w) (((w) + 3) / 4)
#elif TREZOR_FONT_BPP == 4
#define GLYPH_FORMAT GL_FORMAT_MONO4
#define GLYPH_STRIDE(w) (((w) + 1) / 2)
#elif TREZOR_FONT_BPP == 8
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GL_FORMAT_MONO8
#define GLYPH_STRIDE(w) (w)
#else
#error Unsupported TREZOR_FONT_BPP value
#endif
#define GLYPH_WIDTH(g) ((g)[0])
#define GLYPH_HEIGHT(g) ((g)[1])
#define GLYPH_ADVANCE(g) ((g)[2])
#define GLYPH_BEARING_X(g) ((g)[3])
#define GLYPH_BEARING_Y(g) ((g)[4])
#define GLYPH_DATA(g) ((void*)&(g)[5])
void gl_draw_text(gl_rect_t rect, const char* text, size_t maxlen,
const gl_text_attr_t* attr) {
if (text == NULL) {
return;
}
gl_bitmap_t bitmap = {
.format = GLYPH_FORMAT,
.fg_color = attr->fg_color,
.bg_color = attr->bg_color,
};
int max_height = font_max_height(attr->font);
int baseline = font_baseline(attr->font);
for (int i = 0; i < maxlen; i++) {
uint8_t ch = (uint8_t)text[i];
if (ch == 0 || rect.x0 >= rect.x1) {
break;
}
const uint8_t* glyph = font_get_glyph(attr->font, ch);
if (glyph == NULL) {
continue;
}
bitmap.ptr = GLYPH_DATA(glyph);
bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph));
bitmap.size.x = GLYPH_WIDTH(glyph);
bitmap.size.y = GLYPH_HEIGHT(glyph);
bitmap.offset.x = -GLYPH_BEARING_X(glyph);
bitmap.offset.y = -(max_height - baseline - GLYPH_BEARING_Y(glyph));
gl_draw_bitmap(rect, &bitmap);
rect.x0 += GLYPH_ADVANCE(glyph);
}
}
// ===============================================================
// emulation of legacy functions
void display_bar(int x, int y, int w, int h, uint16_t c) {}
void display_text(int x, int y, const char* text, int textlen, int font,
uint16_t fgcolor, uint16_t bgcolor) {}

@ -0,0 +1,135 @@
/*
* 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/>.
*/
#ifndef GL_DRAW_H
#define GL_DRAW_H
#include "gl_color.h"
// 2D rectangle coordinates (x1, y1 point is not included)
typedef struct {
int16_t x0;
int16_t y0;
int16_t x1;
int16_t y1;
} gl_rect_t;
// Creates a rectangle from top-left coordinates and dimensions
static inline gl_rect_t gl_rect_wh(int16_t x, int16_t y, int16_t w, int16_t h) {
gl_rect_t rect = {
.x0 = x,
.y0 = y,
.x1 = x + w,
.y1 = y + h,
};
return rect;
}
// Creates a rectangle from top-left and bottom-right coordinates
static inline gl_rect_t gl_rect(int16_t x0, int16_t y0, int16_t x1,
int16_t y1) {
gl_rect_t rect = {
.x0 = x0,
.y0 = y0,
.x1 = x1,
.y1 = y1,
};
return rect;
}
// 2D offset/ coordinates
typedef struct {
int16_t x;
int16_t y;
} gl_offset_t;
static inline gl_offset_t gl_offset(int16_t x, int16_t y) {
gl_offset_t size = {
.x = x,
.y = y,
};
return size;
}
// 2D size in pixels
typedef struct {
int16_t x;
int16_t y;
} gl_size_t;
static inline gl_size_t gl_size(int16_t x, int16_t y) {
gl_size_t size = {
.x = x,
.y = y,
};
return size;
}
// Bitmap pixel format
typedef enum {
GL_FORMAT_UNKNOWN, //
GL_FORMAT_MONO1P, // 1-bpp per pixel (packed)
GL_FORMAT_MONO4, // 4-bpp per pixel
GL_FORMAT_RGB565, // 16-bpp per pixel
GL_FORMAT_RGBA8888, // 32-bpp
} gl_format_t;
// 2D bitmap references
typedef struct {
// pointer to top-left pixel
void* ptr;
// stride in bytes
size_t stride;
// size in pixels
gl_size_t size;
// format of pixels, GL_FORMAT_xxx
uint8_t format;
// offset used when bitmap is drawed using gl_draw_bitmap()
gl_offset_t offset;
// foreground color (used with MONOx formats)
gl_color_t fg_color;
// background color (used with MONOx formats)
gl_color_t bg_color;
} gl_bitmap_t;
// Text attributes
typedef struct {
int font;
gl_color_t fg_color;
gl_color_t bg_color;
} gl_text_attr_t;
// Fills a rectangle with a specified color
void gl_draw_bar(gl_rect_t rect, gl_color_t color);
// Draws a bitmap into the specified rectangle
// The destination rectangle may not be fully filled if the source bitmap
// is smaller then destination rectangle or if the bitmap is translated by
// an offset partially or completely outside the destination rectangle.
void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap);
// !@# TODO
void gl_draw_text(gl_rect_t rect, const char* text, size_t maxlen,
const gl_text_attr_t* attr);
#endif // GL_DRAW_H

@ -77,6 +77,9 @@ void term_print(const char *text, int textlen) {
}
}
#ifdef NEW_RENDERING
// TODO !@#
#else
// render buffer to display
display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
for (int i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
@ -105,6 +108,7 @@ void term_print(const char *text, int textlen) {
}
display_pixeldata_dirty();
display_refresh();
#endif
}
#ifdef TREZOR_EMULATOR

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "alloc-traits"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -279,6 +285,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static-alloc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "570b7e840addf99f80c5b26abba410e21537002316fc82f2747fd87c171e9d7e"
dependencies = [
"alloc-traits",
]
[[package]]
name = "syn"
version = "1.0.80"
@ -309,8 +324,11 @@ dependencies = [
"qrcodegen",
"serde_json",
"spin",
"static-alloc",
"trezor-tjpgdec",
"ufmt",
"unsize",
"without-alloc",
"zeroize",
]
@ -347,6 +365,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unsize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939"
dependencies = [
"autocfg",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -369,6 +396,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "without-alloc"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb"
dependencies = [
"alloc-traits",
"unsize",
]
[[package]]
name = "zeroize"
version = "1.7.0"

@ -14,10 +14,17 @@ micropython = []
protobuf = ["micropython"]
ui = []
dma2d = []
xframebuffer = []
display_mono = []
display_rgb565 = []
display_rgba8888 = []
framebuffer = []
framebuffer32bit = []
ui_debug = []
ui_bounds = []
ui_antialiasing = []
ui_blurring = []
ui_jpeg_decoder = []
bootloader = []
button = []
touch = []
@ -104,6 +111,15 @@ version = "0.2.6"
default-features = false
features = ["nightly"]
[dependencies.static-alloc]
version = "0.2.4"
[dependencies.without-alloc]
version = "0.2.2"
[dependencies.unsize]
version = "1.1.0"
# Build dependencies
[build-dependencies.bindgen]

@ -110,6 +110,16 @@ fn prepare_bindings() -> bindgen::Builder {
format!("-DTREZOR_BOARD=\"{}\"", board()).as_str(),
]);
#[cfg(feature = "xframebuffer")]
{
bindings = bindings.clang_args(&["-DXFRAMEBUFFER"]);
}
#[cfg(feature = "new_rendering")]
{
bindings = bindings.clang_args(["-DNEW_RENDERING"]);
}
// Pass in correct include paths and defines.
if is_firmware() {
let mut clang_args: Vec<&str> = Vec::new();
@ -338,6 +348,25 @@ fn generate_trezorhal_bindings() {
.allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y")
.allowlist_var("DISPLAY_RESX")
.allowlist_var("DISPLAY_RESY")
.allowlist_function("display_get_frame_addr")
.allowlist_function("display_fill")
.allowlist_function("display_copy_rgb565")
// dma2d
.allowlist_type("dma2d_params_t")
.allowlist_function("rgb565_fill")
.allowlist_function("rgb565_copy_mono4")
.allowlist_function("rgb565_copy_rgb565")
.allowlist_function("rgb565_blend_mono4")
.allowlist_function("rgba8888_fill")
.allowlist_function("rgba8888_copy_mono4")
.allowlist_function("rgba8888_copy_rgb565")
.allowlist_function("rgba8888_copy_rgba8888")
.allowlist_function("rgba8888_blend_mono4")
.allowlist_function("mono8_fill")
.allowlist_function("mono8_copy_mono1p")
.allowlist_function("mono8_copy_mono4")
.allowlist_function("mono8_blend_mono1p")
.allowlist_function("mono8_blend_mono4")
// fonts
.allowlist_function("font_height")
.allowlist_function("font_max_height")

@ -13,11 +13,11 @@ pub use ffi::{
#[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))]
#[derive(Copy, Clone)]
pub struct FrameBuffer(*mut u16);
pub struct FrameBuffer(pub *mut u16);
#[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))]
#[derive(Copy, Clone)]
pub struct FrameBuffer(*mut u32);
pub struct FrameBuffer(pub *mut u32);
pub fn backlight(val: i32) -> i32 {
unsafe { ffi::display_backlight(val) }
@ -99,6 +99,7 @@ pub fn get_fb_addr() -> FrameBuffer {
#[inline(always)]
#[cfg(all(not(feature = "framebuffer"), feature = "disp_i8080_8bit_dw"))]
pub fn pixeldata(c: u16) {
#[cfg(not(feature = "new_rendering"))]
unsafe {
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8);
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8);
@ -178,3 +179,8 @@ pub fn clear() {
ffi::display_clear();
}
}
#[cfg(feature = "xframebuffer")]
pub fn get_frame_addr() -> *mut cty::c_void {
unsafe { ffi::display_get_frame_addr() }
}

@ -0,0 +1,216 @@
use super::ffi;
use crate::ui::{
display::Color,
geometry::Rect,
shape::{Bitmap, BitmapFormat, BitmapView},
};
pub type Dma2d = ffi::dma2d_params_t;
impl Default for Dma2d {
fn default() -> Self {
Self {
width: 0,
height: 0,
dst_row: core::ptr::null_mut(),
dst_stride: 0,
dst_x: 0,
dst_y: 0,
src_row: core::ptr::null_mut(),
src_bg: 0,
src_fg: 0,
src_stride: 0,
src_x: 0,
src_y: 0,
src_alpha: 255,
}
}
}
impl Dma2d {
pub fn new_fill(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option<Self> {
let r = r.clamp(clip);
if !r.is_empty() {
Some(
Self::default()
.with_rect(r)
.with_fg(color)
.with_alpha(alpha),
)
} else {
None
}
}
pub fn new_copy(r: Rect, clip: Rect, src: &BitmapView) -> Option<Self> {
let mut offset = src.offset;
let mut r_dst = r;
// Normalize negative x & y-offset of the bitmap
if offset.x < 0 {
r_dst.x0 -= offset.x;
offset.x = 0;
}
if offset.y < 0 {
r_dst.y0 -= offset.y;
offset.y = 0;
}
// Clip with the canvas viewport
let mut r = r_dst.clamp(clip);
// Clip with the bitmap top-left
if r.x0 > r_dst.x0 {
offset.x += r.x0 - r_dst.x0;
}
if r.y0 > r_dst.y0 {
offset.y += r.y0 - r_dst.y0;
}
// Clip with the bitmap size
r.x1 = core::cmp::min(r.x0 + src.size().x - offset.x, r.x1);
r.y1 = core::cmp::min(r.y0 + src.size().y - offset.y, r.y1);
if !r.is_empty() {
Some(
Dma2d::default()
.with_rect(r)
.with_src(src.bitmap, offset.x, offset.y)
.with_bg(src.bg_color)
.with_fg(src.fg_color),
)
} else {
None
}
}
pub fn with_dst(self, dst: &mut Bitmap) -> Self {
Self {
dst_row: unsafe { dst.row_ptr(self.dst_y) },
dst_stride: dst.stride() as u16,
..self
}
}
fn with_rect(self, r: Rect) -> Self {
Self {
width: r.width() as u16,
height: r.height() as u16,
dst_x: r.x0 as u16,
dst_y: r.y0 as u16,
..self
}
}
fn with_src(self, bitmap: &Bitmap, x: i16, y: i16) -> Self {
let bitmap_stride = match bitmap.format() {
BitmapFormat::MONO1P => bitmap.width() as u16, // packed bits
_ => bitmap.stride() as u16,
};
Self {
src_row: unsafe { bitmap.row_ptr(y as u16) },
src_stride: bitmap_stride,
src_x: x as u16,
src_y: y as u16,
..self
}
}
fn with_fg(self, fg_color: Color) -> Self {
Self {
src_fg: fg_color.into(),
..self
}
}
fn with_bg(self, bg_color: Color) -> Self {
Self {
src_bg: bg_color.into(),
..self
}
}
fn with_alpha(self, alpha: u8) -> Self {
Self {
src_alpha: alpha,
..self
}
}
pub fn wait_for_transfer() {
#[cfg(feature = "dma2d")]
unsafe {
ffi::dma2d_wait_for_transfer()
}
}
pub unsafe fn rgb565_fill(&self) {
unsafe { ffi::rgb565_fill(self) };
}
pub unsafe fn rgb565_copy_mono4(&self) {
unsafe { ffi::rgb565_copy_mono4(self) };
}
pub unsafe fn rgb565_copy_rgb565(&self) {
unsafe { ffi::rgb565_copy_rgb565(self) };
}
pub unsafe fn rgb565_blend_mono4(&self) {
unsafe { ffi::rgb565_blend_mono4(self) };
}
pub unsafe fn rgba8888_fill(&self) {
unsafe { ffi::rgba8888_fill(self) };
}
pub unsafe fn rgba8888_copy_mono4(&self) {
unsafe { ffi::rgba8888_copy_mono4(self) };
}
pub unsafe fn rgba8888_copy_rgb565(&self) {
unsafe { ffi::rgba8888_copy_rgb565(self) };
}
pub unsafe fn rgba8888_copy_rgba8888(&self) {
unsafe { ffi::rgba8888_copy_rgba8888(self) };
}
pub unsafe fn rgba8888_blend_mono4(&self) {
unsafe { ffi::rgba8888_blend_mono4(self) };
}
pub unsafe fn mono8_fill(&self) {
unsafe { ffi::mono8_fill(self) };
}
pub unsafe fn mono8_copy_mono1p(&self) {
unsafe { ffi::mono8_copy_mono1p(self) };
}
pub unsafe fn mono8_copy_mono4(&self) {
unsafe { ffi::mono8_copy_mono4(self) };
}
pub unsafe fn mono8_blend_mono1p(&self) {
unsafe { ffi::mono8_blend_mono1p(self) };
}
pub unsafe fn mono8_blend_mono4(&self) {
unsafe { ffi::mono8_blend_mono4(self) };
}
#[cfg(feature = "new_rendering")]
pub unsafe fn display_fill(&self) {
unsafe { ffi::display_fill(self) };
}
#[cfg(feature = "new_rendering")]
pub unsafe fn display_copy_rgb565(&self) {
unsafe { ffi::display_copy_rgb565(self) };
}
}

@ -6,9 +6,11 @@ pub mod fatal_error;
pub mod display;
#[cfg(feature = "dma2d")]
pub mod dma2d;
pub mod dma2d_new;
mod ffi;
#[cfg(feature = "haptic")]
pub mod haptic;
pub mod io;
pub mod model;
pub mod random;

@ -3,6 +3,7 @@ use crate::{
ui::{
constant,
geometry::{Offset, Point, Rect},
shape::{Bitmap, BitmapFormat},
},
};
use core::slice;
@ -41,12 +42,12 @@ impl Glyph {
let width = *data.offset(0) as i16;
let height = *data.offset(1) as i16;
let data_bits = constant::FONT_BPP * width * height;
let data_bytes = if data_bits % 8 == 0 {
data_bits / 8
} else {
(data_bits / 8) + 1
let data_bytes = match constant::FONT_BPP {
1 => (width * height + 7) / 8, // packed bits
2 => (width * height + 3) / 4, // packed bits
4 => (width + 1) / 2 * height, // row aligned to bytes
8 => width * height,
_ => panic!(),
};
Glyph {
@ -119,6 +120,28 @@ impl Glyph {
_ => 0,
}
}
pub fn bitmap(&self) -> Bitmap<'static> {
match constant::FONT_BPP {
1 => unwrap!(Bitmap::new(
BitmapFormat::MONO1P,
None,
Offset::new(self.width, self.height),
None,
self.data,
)),
2 => panic!(),
4 => unwrap!(Bitmap::new(
BitmapFormat::MONO4,
None,
Offset::new(self.width, self.height),
None,
self.data,
)),
8 => panic!(),
_ => panic!(),
}
}
}
/// Font constants. Keep in sync with FONT_ definitions in
@ -240,6 +263,16 @@ impl Font {
constant::LINE_SPACE + self.text_height()
}
// Returns x-coordinate of the text start (including left bearing)
pub fn horz_center(&self, start: i16, end: i16, text: &str) -> i16 {
(start + end - self.visible_text_width(text)) / 2 - self.start_x_bearing(text)
}
// Returns y-coordinate of the text baseline
pub fn vert_center(&self, start: i16, end: i16, text: &str) -> i16 {
(start + end + self.visible_text_height(text)) / 2
}
pub fn get_glyph(self, ch: char) -> Glyph {
let gl_data = display::get_char_glyph(ch as u16, self.into());

@ -545,6 +545,7 @@ pub enum Alignment {
End,
}
#[derive(Copy, Clone)]
pub struct Alignment2D(pub Alignment, pub Alignment);
impl Alignment2D {
@ -552,6 +553,8 @@ impl Alignment2D {
pub const TOP_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Start);
pub const TOP_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Start);
pub const CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Center);
pub const CENTER_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::Center);
pub const CENTER_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Center);
pub const BOTTOM_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::End);
pub const BOTTOM_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::End);
pub const BOTTOM_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::End);

@ -8,6 +8,7 @@ pub mod display;
pub mod event;
pub mod geometry;
pub mod lerp;
pub mod shape;
#[macro_use]
pub mod util;

@ -0,0 +1,357 @@
use crate::ui::geometry::Offset;
/// This is a simple and fast blurring algorithm that uses a box filter -
/// a square kernel with all coefficients set to 1.
///
/// The `BlurFilter` structure holds the context of a simple 2D window averaging
/// filter - a sliding window and the sum of all rows in the sliding window.
///
/// The `BlurFilter` implements only five public functions - `new`, `push`,
/// `push_read`, `pop` and `pop_ready`.
///
/// The `new()` function creates a blur filter context.
/// - The `size` argument specifies the size of the blurred area.
/// - The `radius` argument specifies the length of the kernel side.
///
/// ```rust
/// let blur = BlurFilter::new(size, radius);
/// ```
///
/// The `push_ready()` function returns the row from the source bitmap
/// needed to be pushed
///
/// The `push()` function pushes source row data into the sliding window and
/// performs all necessary calculations.
///
/// ```rust
/// if let Some(y) = blur.push_ready() {
/// blur.push(&src_bitmap.row(y)[x0..x1]);
/// }
/// ```
///
/// The `pop_ready()` function returns the row from the destination bitmap
/// that can be popped out
///
/// The `pop()` function pops the blurred row from the sliding window.
///
/// ```rust
/// if let Some(y) = blur.pop_ready() {
/// blur.pop(&mut dst_bitmap.row(y)[x0..x1]);
/// }
/// ```
use core::mem::size_of;
const MAX_RADIUS: usize = 4;
const MAX_SIDE: usize = 1 + MAX_RADIUS * 2;
const MAX_WIDTH: usize = 240;
pub type BlurBuff = [u8; MAX_WIDTH * (MAX_SIDE * 3 + size_of::<u16>() * 3) + 8];
type PixelColor = u16;
#[derive(Default, Copy, Clone)]
struct Rgb<T> {
pub r: T,
pub g: T,
pub b: T,
}
impl Rgb<u16> {
#[inline(always)]
fn mulshift(&self, multiplier: u32, shift: u8) -> Rgb<u8> {
Rgb::<u8> {
r: ((self.r as u32 * multiplier) >> shift) as u8,
g: ((self.g as u32 * multiplier) >> shift) as u8,
b: ((self.b as u32 * multiplier) >> shift) as u8,
}
}
}
impl From<u16> for Rgb<u16> {
#[inline(always)]
fn from(value: u16) -> Rgb<u16> {
Rgb::<u16> {
r: (value >> 8) & 0xF8,
g: (value >> 3) & 0xFC,
b: (value << 3) & 0xF8,
}
}
}
impl core::ops::AddAssign<u16> for Rgb<u16> {
#[inline(always)]
fn add_assign(&mut self, rhs: u16) {
let rgb: Rgb<u16> = rhs.into();
*self += rgb;
}
}
impl core::ops::SubAssign<u16> for Rgb<u16> {
#[inline(always)]
fn sub_assign(&mut self, rhs: u16) {
let rgb: Rgb<u16> = rhs.into();
*self -= rgb;
}
}
impl core::ops::AddAssign for Rgb<u16> {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.r += rhs.r;
self.g += rhs.g;
self.b += rhs.b;
}
}
impl core::ops::SubAssign for Rgb<u16> {
#[inline(always)]
fn sub_assign(&mut self, rhs: Rgb<u16>) {
self.r -= rhs.r;
self.g -= rhs.g;
self.b -= rhs.b;
}
}
impl From<Rgb<u8>> for u16 {
#[inline(always)]
fn from(value: Rgb<u8>) -> u16 {
let r = (value.r as u16 & 0xF8) << 8;
let g = (value.g as u16 & 0xFC) << 3;
let b = (value.b as u16 & 0xF8) >> 3;
r | g | b
}
}
impl From<Rgb<u16>> for Rgb<u8> {
#[inline(always)]
fn from(value: Rgb<u16>) -> Rgb<u8> {
Rgb::<u8> {
r: value.r as u8,
g: value.g as u8,
b: value.b as u8,
}
}
}
impl core::ops::AddAssign<Rgb<u8>> for Rgb<u16> {
#[inline(always)]
fn add_assign(&mut self, rhs: Rgb<u8>) {
self.r += rhs.r as u16;
self.g += rhs.g as u16;
self.b += rhs.b as u16;
}
}
impl core::ops::SubAssign<Rgb<u8>> for Rgb<u16> {
#[inline(always)]
fn sub_assign(&mut self, rhs: Rgb<u8>) {
self.r -= rhs.r as u16;
self.g -= rhs.g as u16;
self.b -= rhs.b as u16;
}
}
pub struct BlurAlgorithm<'a> {
size: Offset,
radius: usize,
row: usize,
totals: &'a mut [Rgb<u16>],
window: &'a mut [Rgb<u8>],
row_count: usize,
}
impl<'a> BlurAlgorithm<'a> {
/// Constraints:
/// width <= MAX_WIDTH
/// radius <= MAX_RADIUS
/// width >= radius
pub fn new(size: Offset, radius: usize, memory: &'a mut BlurBuff) -> Result<Self, ()> {
assert!(size.x as usize <= MAX_WIDTH);
assert!(radius <= MAX_RADIUS);
assert!(size.x as usize > 2 * radius - 1);
// Split buffer into two parts
let window_size = size.x as usize * (1 + radius * 2);
let (window_buff, total_buff) =
memory.split_at_mut(window_size * core::mem::size_of::<Rgb<u8>>());
// Allocate `window` from the beginning of the buffer
let (_, window_buff, _) = unsafe { window_buff.align_to_mut() };
if window_buff.len() < window_size {
return Err(());
}
let window = &mut window_buff[..window_size];
window.iter_mut().for_each(|it| *it = Rgb::<u8>::default());
// Allocate `totals` from the rest of the buffer
let (_, totals_buff, _) = unsafe { total_buff.align_to_mut() };
if totals_buff.len() < size.x as usize {
return Err(());
}
let totals = &mut totals_buff[..size.x as usize];
totals.iter_mut().for_each(|it| *it = Rgb::<u16>::default());
Ok(Self {
size,
radius,
row: 0,
window,
totals,
row_count: 0,
})
}
/// Returns the length of the box filter side.
fn box_side(&self) -> usize {
1 + self.radius * 2
}
/// Takes an input row and calculates the same-sized vector
/// as the floating average of n subsequent elements where n = 2 * radius +
/// 1. Finally, it stores it into the specifed row in the sliding
/// window.
fn average_to_row(&mut self, inp: &[PixelColor], row: usize) {
let radius = self.radius;
let offset = self.size.x as usize * row;
let row = &mut self.window[offset..offset + self.size.x as usize];
let mut sum = Rgb::<u16>::default();
let divisor = (radius * 2 + 1) as u16;
let shift = 10;
let multiplier = (1 << shift) as u32 / divisor as u32;
// Prepare before averaging
for i in 0..radius {
sum += inp[0]; // Duplicate pixels on the left
sum += inp[i]; // Add first radius pixels
}
// Process the first few pixels of the row
for i in 0..radius {
sum += inp[i + radius];
row[i] = sum.mulshift(multiplier, shift);
sum -= inp[0];
}
// Process the inner part of the row
for i in radius..row.len() - radius {
sum += inp[i + radius];
row[i] = sum.mulshift(multiplier, shift);
sum -= inp[i - radius];
}
// Process the last few pixels of the row
for i in (row.len() - radius)..row.len() {
sum += inp[inp.len() - 1];
row[i] = sum.mulshift(multiplier, shift);
sum -= inp[i - radius]; // Duplicate pixels on the right
}
}
/// Copy one row from the window to the another row.
fn copy_row(&mut self, from_row: usize, to_row: usize) {
let from_offset = self.size.x as usize * from_row;
let to_offset = self.size.x as usize * to_row;
for i in 0..self.size.x as usize {
self.window[to_offset + i] = self.window[from_offset + i];
}
}
/// Subtracts the specified row of sliding window from `totals[]`.
fn subtract_row(&mut self, row: usize) {
let offset = self.size.x as usize * row;
let row = &self.window[offset..offset + self.size.x as usize];
for (i, item) in row.iter().enumerate() {
self.totals[i] -= *item;
}
}
/// Adds the specified row of sliding window to `totals[]`.
fn add_row(&mut self, row: usize) {
let offset = self.size.x as usize * row;
let row = &self.window[offset..offset + self.size.x as usize];
for (i, item) in row.iter().enumerate() {
self.totals[i] += *item;
}
}
/// Pushes the most recently pushed row again.
fn push_last_row(&mut self) {
let to_row = self.row;
let from_row = if to_row > 0 {
to_row - 1
} else {
self.box_side() - 1
};
self.subtract_row(to_row);
self.copy_row(from_row, to_row);
self.add_row(to_row);
self.row = (to_row + 1) % self.box_side();
self.row_count += 1;
}
/// Returns the index of the row needed to be pushed into.
pub fn push_ready(&self) -> Option<i16> {
let y = core::cmp::max(0, self.row_count as i16 - self.radius as i16);
if y < self.size.y {
Some(y)
} else {
None
}
}
/// Takes the source row and pushes it into the sliding window.
pub fn push(&mut self, input: &[PixelColor]) {
let row = self.row;
self.subtract_row(row);
self.average_to_row(input, row);
self.add_row(row);
self.row = (row + 1) % self.box_side();
self.row_count += 1;
while self.row_count <= self.radius {
self.push_last_row();
}
}
/// Returns the index of row ready to be popped out.
pub fn pop_ready(&self) -> Option<i16> {
let y = self.row_count as i16 - self.box_side() as i16;
if y < 0 {
None
} else {
Some(y)
}
}
/// Copies the current content of `totals[]` to the output buffer.
pub fn pop(&mut self, output: &mut [PixelColor], dim: Option<u8>) {
let divisor = match dim {
Some(dim) => {
if dim > 0 {
(self.box_side() as u16 * 255) / dim as u16
} else {
65535u16
}
}
None => self.box_side() as u16,
};
let shift = 10;
let multiplier = (1 << shift) as u32 / divisor as u32;
for (i, item) in output.iter_mut().enumerate() {
*item = self.totals[i].mulshift(multiplier, shift).into();
}
if self.push_ready().is_none() {
self.push_last_row();
}
}
}

@ -0,0 +1,76 @@
/// Iterator providing points for 1/8th of a circle (single octant)
///
/// The iterator supplies coordinates of pixels relative to the
/// circle's center point, along with an alpha value in
/// the range (0..255), indicating the proportion of the pixel
/// that lies inside the circle.
///
/// for p in circle_points(radius) {
/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>..<see_below>
/// println!("{}", p.frac); // distance from the circle <0..255>
/// println!("{}", p.first); // `v` has changed
/// println!("{}", p.last); // next `v` will change
/// }
///
/// `u` axis is the main and increments at each iteration.
///
/// endpoint [t, t] or [t - 1, t] where t = radius * (1 / sqrt(2))
pub fn circle_points(radius: i16) -> CirclePoints {
CirclePoints {
radius,
u: 0,
v: radius,
t1: radius / 16,
first: true,
}
}
pub struct CirclePoints {
radius: i16,
u: i16,
v: i16,
t1: i16,
first: bool,
}
#[derive(Copy, Clone)]
pub struct CirclePointsItem {
pub u: i16,
pub v: i16,
pub frac: u8,
pub first: bool,
pub last: bool,
}
impl Iterator for CirclePoints {
type Item = CirclePointsItem;
fn next(&mut self) -> Option<Self::Item> {
if self.v >= self.u {
let mut item = CirclePointsItem {
u: self.u,
v: self.v,
frac: 255 - ((self.t1 as i32 * 255) / self.radius as i32) as u8,
first: self.first,
last: false,
};
self.first = false;
self.u += 1;
self.t1 += self.u;
let t2 = self.t1 - self.v;
if t2 >= 0 {
self.t1 = t2;
self.v -= 1;
self.first = true;
}
item.last = item.v != self.v;
Some(item)
} else {
None
}
}
}

@ -0,0 +1,94 @@
/// Iterator providing points on a line (using bresenham's algorithm)
///
/// The iterator supplies coordinates of pixels relative to the
/// line's start point.
///
/// constraint: `du` >= `dv`, `start_u` < `du`
///
/// for p in line_points(du, dv, start_u) {
/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>..<du-1, dv-1>
/// println!("{}", p.frac); // distance from the line <0..255>
/// println!("{}", p.first); // `v` has changed
/// println!("{}", p.last); // next `v` will change
/// }
///
/// `u` axis is the main and increments at each iteration.
pub fn line_points(du: i16, dv: i16, start_u: i16) -> LinePoints {
let mut d = 2 * du - 2 * dv;
let mut y = 0;
for _ in 0..start_u {
if d <= 0 {
d += 2 * du - 2 * dv;
y += 1;
} else {
d -= 2 * dv;
}
}
LinePoints {
du,
dv,
d,
u: start_u,
v: y,
first: true,
}
}
pub struct LinePoints {
du: i16,
dv: i16,
d: i16,
u: i16,
v: i16,
first: bool,
}
#[derive(Copy, Clone)]
pub struct LinePointsItem {
pub u: i16,
pub v: i16,
pub frac: u8,
pub first: bool,
pub last: bool,
}
impl Iterator for LinePoints {
type Item = LinePointsItem;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if self.u < self.du {
let frac = if self.dv < self.du {
255 - ((self.d + 2 * self.dv - 1) as i32 * 255 / (2 * self.du - 1) as i32) as u8
} else {
0
};
let next = LinePointsItem {
u: self.u,
v: self.v,
frac,
first: self.first,
last: self.d <= 0,
};
if self.d <= 0 {
self.d += 2 * self.du - 2 * self.dv;
self.v += 1;
self.first = true;
} else {
self.d -= 2 * self.dv;
self.first = false;
}
self.u += 1;
Some(next)
} else {
None
}
}
}

@ -0,0 +1,9 @@
mod blur;
mod circle;
mod line;
mod trigo;
pub use blur::{BlurAlgorithm, BlurBuff};
pub use circle::circle_points;
pub use line::line_points;
pub use trigo::{sin_i16, PI4};

@ -0,0 +1,29 @@
/// Integer representing an angle of 45 degress (PI/4).
//
// Changing this constant requires revisiting isin() algorithm
// (for higher values consider changing T type to i64 or f32)
pub const PI4: i16 = 45;
/// Fast sine approximation.
///
/// Returns mult * sin(angle).
///
/// Angle must be in range <0..PI4>.
/// This function provides an error within +-1 for multiplier up to 500
pub fn sin_i16(angle: i16, mult: i16) -> i16 {
assert!(angle >= 0 && angle <= PI4);
assert!(mult <= 2500);
type T = i32;
// Based on polynomial x - x^3 / 6
let x = angle as T;
// Constants for the approximation
const K: f32 = (PI4 as f32) * 4.0 / core::f32::consts::PI;
const M: T = (6.0 * K * K + 0.5) as T;
const N: T = (6.0 * K * K * K + 0.5) as T;
// Applying the approximation
(((M * x - x * x * x) * mult as T + N / 2) / N) as i16
}

@ -0,0 +1,128 @@
use crate::ui::{display::Color, geometry::Rect};
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
/// A shape for the rendering variuous type of rectangles.
pub struct Bar {
/// Rectangle position and dimenstion
area: Rect,
/// Foreground color (default None)
fg_color: Option<Color>,
/// Background color (default None)
bg_color: Option<Color>,
/// Thickness (default 0)
thickness: i16,
/// Corner radius (default 0)
radius: i16,
/// Alpha (default 255)
alpha: u8,
}
impl Bar {
pub fn new(area: Rect) -> Self {
Self {
area,
fg_color: None,
bg_color: None,
thickness: 1,
radius: 0,
alpha: 255,
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: Some(fg_color),
..self
}
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn with_radius(self, radius: i16) -> Self {
Self { radius, ..self }
}
pub fn with_thickness(self, thickness: i16) -> Self {
Self { thickness, ..self }
}
pub fn with_alpha(self, alpha: u8) -> Self {
Self { alpha, ..self }
}
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
renderer.render_shape(self);
}
}
impl Shape<'_> for Bar {
fn bounds(&self, _cache: &DrawingCache) -> Rect {
self.area
}
fn cleanup(&mut self, _cache: &DrawingCache) {}
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
// NOTE: drawing of rounded bars without a background
// is not supported. If we needed it, we would have to
// introduce a new function in RgbCanvas.
// TODO: panic! in unsupported scenarious
let th = match self.fg_color {
Some(_) => self.thickness,
None => 0,
};
if self.radius == 0 {
if let Some(fg_color) = self.fg_color {
// outline
if th > 0 {
let r = self.area;
canvas.fill_rect(Rect { y1: r.y0 + th, ..r }, fg_color, self.alpha);
canvas.fill_rect(Rect { x1: r.x0 + th, ..r }, fg_color, self.alpha);
canvas.fill_rect(Rect { x0: r.x1 - th, ..r }, fg_color, self.alpha);
canvas.fill_rect(Rect { y0: r.y1 - th, ..r }, fg_color, self.alpha);
}
}
if let Some(bg_color) = self.bg_color {
// background
let bg_r = self.area.shrink(th);
canvas.fill_rect(bg_r, bg_color, self.alpha);
}
} else {
if let Some(fg_color) = self.fg_color {
if th > 0 {
if self.bg_color.is_some() {
canvas.fill_round_rect(self.area, self.radius, fg_color, self.alpha);
} else {
#[cfg(not(feature = "ui_antialiasing"))]
canvas.draw_round_rect(self.area, self.radius, fg_color);
}
}
}
if let Some(bg_color) = self.bg_color {
let bg_r = self.area.shrink(th);
canvas.fill_round_rect(bg_r, self.radius, bg_color, self.alpha);
}
}
}
}
impl<'s> ShapeClone<'s> for Bar {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<Bar>()?;
Some(clone.uninit.init(Bar { ..self }))
}
}

@ -0,0 +1,53 @@
use crate::ui::geometry::Rect;
use super::{Canvas, DrawingCache};
use without_alloc::alloc::LocalAllocLeakExt;
// ==========================================================================
// trait Shape
// ==========================================================================
/// This trait is used internally by so-called Renderers -
/// `DirectRenderer` & `ProgressiveRederer`.
///
/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered
/// must implement `Shape` trait.
///
/// `Shape` objects may use `DrawingCache` as a scratch-pad memory or for
/// caching expensive calculations results.
pub trait Shape<'s> {
/// Returns the smallest bounding rectangle containing whole parts of the
/// shape.
///
/// The function is used by renderer for optimization if the shape
/// must be renderer or not.
fn bounds(&self, cache: &DrawingCache<'s>) -> Rect;
/// Draws shape on the canvas.
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'s>);
/// The function should release all allocated resources needed
/// for shape drawing.
///
/// It's called by renderer if the shape's draw() function won't be called
/// anymore.
fn cleanup(&mut self, cache: &DrawingCache<'s>);
}
// ==========================================================================
// trait ShapeClone
// ==========================================================================
/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered
/// by `ProgressiveRender` must implement `ShapeClone`.
pub trait ShapeClone<'s> {
/// Clones a shape object at the specified memory bump.
///
/// The method is used by `ProgressiveRenderer` to store shape objects for
/// deferred drawing.
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
'alloc: 's;
}

@ -0,0 +1,317 @@
use crate::trezorhal::dma2d_new::Dma2d;
use crate::ui::{display::Color, geometry::Offset};
use core::{cell::Cell, marker::PhantomData};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BitmapFormat {
/// 1-bit mono
MONO1,
/// 1-bit mono packed (bitmap stride is in bits)
MONO1P,
/// 4-bit mono
MONO4,
/// 8-bit mono
MONO8,
/// 16-bit color, RGB565 format
RGB565,
/// 32-bit color, RGBA format
RGBA8888,
}
pub struct Bitmap<'a> {
/// Pointer to top-left pixel
ptr: *mut u8,
/// Stride in bytes
stride: usize,
/// Size in pixels
size: Offset,
/// Format of pixels
format: BitmapFormat,
/// Bitmap data is mutable
mutable: bool,
/// DMA operation is pending
dma_pending: Cell<bool>,
///
_phantom: core::marker::PhantomData<&'a ()>,
}
impl<'a> Bitmap<'a> {
/// Creates a new bitmap referencing a specified buffer.
///
/// Optionally minimal height can be specified and then the height
/// of the new bitmap is adjusted to the buffer size.
///
/// Returns None if the buffer is not big enough.
///
/// The `buff` needs to be properly aligned and big enough
/// to hold a bitmap with the specified format and size
pub fn new(
format: BitmapFormat,
stride: Option<usize>,
mut size: Offset,
min_height: Option<i16>,
buff: &'a [u8],
) -> Option<Self> {
if size.x < 0 && size.y < 0 {
return None;
}
let min_stride = match format {
BitmapFormat::MONO1 => (size.x + 7) / 8,
BitmapFormat::MONO1P => 0,
BitmapFormat::MONO4 => (size.x + 1) / 2,
BitmapFormat::MONO8 => size.x,
BitmapFormat::RGB565 => size.x * 2,
BitmapFormat::RGBA8888 => size.x * 4,
} as usize;
let stride = stride.unwrap_or(min_stride);
let alignment = match format {
BitmapFormat::MONO1 => 1,
BitmapFormat::MONO1P => 1,
BitmapFormat::MONO4 => 1,
BitmapFormat::MONO8 => 1,
BitmapFormat::RGB565 => 2,
BitmapFormat::RGBA8888 => 4,
};
assert!(stride >= min_stride);
assert!(buff.as_ptr() as usize & (alignment - 1) == 0);
assert!(stride & (alignment - 1) == 0);
let max_height = if stride == 0 {
size.y as usize
} else {
buff.len() / stride
};
if size.y as usize > max_height {
if let Some(min_height) = min_height {
if max_height >= min_height as usize {
size.y = max_height as i16;
} else {
return None;
}
} else {
return None;
}
}
Some(Self {
ptr: buff.as_ptr() as *mut u8,
stride,
size,
format,
mutable: false,
dma_pending: Cell::new(false),
_phantom: PhantomData,
})
}
/// Creates a new mutable bitmap referencing a specified buffer.
///
/// Optionally minimal height can be specified and then the height
/// of the new bitmap is adjusted to the buffer size.
///
/// Returns None if the buffer is not big enough.
///
/// The `buff` needs to be properly aligned and big enough
/// to hold a bitmap with the specified format and size
pub fn new_mut(
format: BitmapFormat,
stride: Option<usize>,
size: Offset,
min_height: Option<i16>,
buff: &'a mut [u8],
) -> Option<Self> {
let mut bitmap = Self::new(format, stride, size, min_height, buff)?;
bitmap.mutable = true;
return Some(bitmap);
}
/// Returns bitmap width in pixels.
pub fn width(&self) -> i16 {
self.size.x
}
/// Returns bitmap height in pixels.
pub fn height(&self) -> i16 {
self.size.y
}
/// Returns bitmap width and height in pixels.
pub fn size(&self) -> Offset {
self.size
}
/// Returns bitmap stride in bytes.
pub fn stride(&self) -> usize {
self.stride
}
/// Returns bitmap format.
pub fn format(&self) -> BitmapFormat {
self.format
}
pub fn view(&self) -> BitmapView {
BitmapView::new(&self)
}
/// Returns the specified row as an immutable slice.
///
/// Returns None if row is out of range.
pub fn row<T>(&self, row: i16) -> Option<&[T]> {
if row >= 0 && row < self.size.y {
self.wait_for_dma();
let offset = row as usize * (self.stride / core::mem::size_of::<T>());
Some(unsafe {
core::slice::from_raw_parts(
(self.ptr as *const T).add(offset),
self.stride / core::mem::size_of::<T>(),
)
})
} else {
None
}
}
/// Returns the specified row as a mutable slice.
///
/// Returns None if row is out of range.
pub fn row_mut<T>(&mut self, row: i16) -> Option<&mut [T]> {
if row >= 0 && row < self.size.y {
self.wait_for_dma();
let offset = row as usize * (self.stride / core::mem::size_of::<T>());
Some(unsafe {
core::slice::from_raw_parts_mut(
(self.ptr as *mut T).add(offset),
self.stride / core::mem::size_of::<T>(),
)
})
} else {
None
}
}
/// Returns specified consecutive rows as a mutable slice
///
/// Returns None if any of requested row is out of range.
pub fn rows_mut<T>(&mut self, row: i16, height: i16) -> Option<&mut [T]> {
if row >= 0 && height > 0 && row < self.size.y && row + height <= self.size.y {
self.wait_for_dma();
let offset = self.stride * row as usize;
let len = self.stride * height as usize;
let array = unsafe {
core::slice::from_raw_parts_mut(
self.ptr as *mut T,
self.size.y as usize * self.stride / core::mem::size_of::<T>(),
)
};
Some(&mut array[offset..offset + len])
} else {
None
}
}
/// Return raw mut pointer to the specified bitmap row.
///
/// `y` must be in range <0; self.height() - 1>.
pub unsafe fn row_ptr(&self, y: u16) -> *mut cty::c_void {
unsafe { self.ptr.add(self.stride() * y as usize) as *mut cty::c_void }
}
/// Waits until DMA operation is finished
fn wait_for_dma(&self) {
if self.dma_pending.get() {
Dma2d::wait_for_transfer();
self.dma_pending.set(false);
}
}
// Mark bitmap as DMA operation is pending
pub fn mark_dma_pending(&self) {
self.dma_pending.set(true);
}
}
impl<'a> Drop for Bitmap<'a> {
fn drop(&mut self) {
self.wait_for_dma();
}
}
pub struct BitmapView<'a> {
pub bitmap: &'a Bitmap<'a>,
pub offset: Offset,
pub fg_color: Color,
pub bg_color: Color,
}
impl<'a> BitmapView<'a> {
/// Creates a new reference to the bitmap
pub fn new(bitmap: &'a Bitmap) -> Self {
Self {
bitmap,
offset: Offset::zero(),
fg_color: Color::black(),
bg_color: Color::black(),
}
}
/// Builds a new structure with offset set to the specified value
pub fn with_offset(self, offset: Offset) -> Self {
Self {
offset: (offset + self.offset.into()).into(),
..self
}
}
/// Builds a new structure with foreground color set to the specified value
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: fg_color.into(),
..self
}
}
/// Builds a new structure with background color set to the specified value
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: bg_color.into(),
..self
}
}
/// Returns the bitmap width and height in pixels
pub fn size(&self) -> Offset {
self.bitmap.size
}
/// Returns the bitmap width in pixels
pub fn width(&self) -> i16 {
self.bitmap.width()
}
/// Returns the bitmap height in pixels
pub fn height(&self) -> i16 {
self.bitmap.height()
}
/// Returns the bitmap format
pub fn format(&self) -> BitmapFormat {
self.bitmap.format
}
/// Returns the specified row as an immutable slice.
///
/// Returns None if row is out of range.
pub fn row<T>(&self, row: i16) -> Option<&[T]> {
self.bitmap.row(row)
}
}

@ -0,0 +1,6 @@
pub mod bitmap;
pub mod mono8;
pub mod rgb565;
pub mod rgba8888;
pub use bitmap::{Bitmap, BitmapFormat, BitmapView};

@ -0,0 +1,48 @@
use super::{Bitmap, BitmapFormat, BitmapView};
use crate::{
trezorhal::dma2d_new::Dma2d,
ui::{display::Color, geometry::Rect},
};
impl<'a> Bitmap<'a> {
/// Fills a rectangle with the specified color.
///
/// The function is aplicable only on bitmaps with RGB565 format.
pub fn mono8_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) {
assert!(self.format() == BitmapFormat::MONO8);
if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) {
let dma2d = dma2d.with_dst(self);
unsafe { dma2d.mono8_fill() };
self.mark_dma_pending();
}
}
//
pub fn mono8_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
assert!(self.format() == BitmapFormat::MONO8);
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
let dma2d = dma2d.with_dst(self);
match src.format() {
BitmapFormat::MONO1P => unsafe { dma2d.mono8_copy_mono1p() },
BitmapFormat::MONO4 => unsafe { dma2d.mono8_copy_mono4() },
_ => panic!("Unsupported DMA operation"),
}
self.mark_dma_pending();
src.bitmap.mark_dma_pending();
}
}
pub fn mono8_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
assert!(self.format() == BitmapFormat::MONO8);
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
let dma2d = dma2d.with_dst(self);
match src.format() {
BitmapFormat::MONO1P => unsafe { dma2d.mono8_blend_mono1p() },
BitmapFormat::MONO4 => unsafe { dma2d.mono8_blend_mono4() },
_ => panic!("Unsupported DMA operation"),
}
self.mark_dma_pending();
src.bitmap.mark_dma_pending();
}
}
}

@ -0,0 +1,47 @@
use super::{Bitmap, BitmapFormat, BitmapView};
use crate::{
trezorhal::dma2d_new::Dma2d,
ui::{display::Color, geometry::Rect},
};
impl<'a> Bitmap<'a> {
/// Fills a rectangle with the specified color.
///
/// The function is aplicable only on bitmaps with RGB565 format.
pub fn rgb565_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) {
assert!(self.format() == BitmapFormat::RGB565);
if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) {
let dma2d = dma2d.with_dst(self);
unsafe { dma2d.rgb565_fill() };
self.mark_dma_pending();
}
}
//
pub fn rgb565_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
assert!(self.format() == BitmapFormat::RGB565);
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
let dma2d = dma2d.with_dst(self);
match src.format() {
BitmapFormat::MONO4 => unsafe { dma2d.rgb565_copy_mono4() },
BitmapFormat::RGB565 => unsafe { dma2d.rgb565_copy_rgb565() },
_ => panic!("Unsupported DMA operation"),
}
self.mark_dma_pending();
src.bitmap.mark_dma_pending();
}
}
pub fn rgb565_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
assert!(self.format() == BitmapFormat::RGB565);
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
let dma2d = dma2d.with_dst(self);
match src.format() {
BitmapFormat::MONO4 => unsafe { dma2d.rgb565_blend_mono4() },
_ => panic!("Unsupported DMA operation"),
}
self.mark_dma_pending();
src.bitmap.mark_dma_pending();
}
}
}

@ -0,0 +1,47 @@
use super::{Bitmap, BitmapFormat, BitmapView};
use crate::{
trezorhal::dma2d_new::Dma2d,
ui::{display::Color, geometry::Rect},
};
impl<'a> Bitmap<'a> {
/// Fills a rectangle with the specified color.
///
/// The function is aplicable only on bitmaps with RGBA888 format.
pub fn rgba8888_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) {
assert!(self.format() == BitmapFormat::RGBA8888);
if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) {
let dma2d = dma2d.with_dst(self);
unsafe { dma2d.rgba8888_fill() };
self.mark_dma_pending();
}
}
pub fn rgba8888_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
assert!(self.format() == BitmapFormat::RGBA8888);
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
let dma2d = dma2d.with_dst(self);
match src.format() {
BitmapFormat::MONO4 => unsafe { dma2d.rgba8888_copy_mono4() },
BitmapFormat::RGB565 => unsafe { dma2d.rgba8888_copy_rgb565() },
BitmapFormat::RGBA8888 => unsafe { dma2d.rgba8888_copy_rgba8888() },
_ => panic!("Unsupported DMA operation"),
}
self.mark_dma_pending();
src.bitmap.mark_dma_pending();
}
}
pub fn rgba8888_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
assert!(self.format() == BitmapFormat::RGBA8888);
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
let dma2d = dma2d.with_dst(self);
match src.format() {
BitmapFormat::MONO4 => unsafe { dma2d.rgba8888_blend_mono4() },
_ => panic!("Unsupported DMA operation"),
}
self.mark_dma_pending();
src.bitmap.mark_dma_pending();
}
}
}

@ -0,0 +1,45 @@
use crate::ui::geometry::Rect;
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
pub struct Blurring {
// Blurred area
area: Rect,
/// Blurring kernel radius
radius: usize,
}
/// A shape for the blurring of a specified rectangle area.
impl Blurring {
pub fn new(area: Rect, radius: usize) -> Self {
Self { area, radius }
}
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
renderer.render_shape(self);
}
}
impl Shape<'_> for Blurring {
fn bounds(&self, _cache: &DrawingCache) -> Rect {
self.area
}
fn cleanup(&mut self, _cache: &DrawingCache) {}
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
canvas.blur_rect(self.area, self.radius, cache);
}
}
impl<'s> ShapeClone<'s> for Blurring {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<Blurring>()?;
Some(clone.uninit.init(Blurring { ..self }))
}
}

@ -0,0 +1,55 @@
use super::super::algo::{BlurAlgorithm, BlurBuff};
use crate::ui::geometry::Offset;
use core::cell::UnsafeCell;
use without_alloc::alloc::LocalAllocLeakExt;
pub struct BlurCache<'a> {
algo: Option<BlurAlgorithm<'a>>,
buff: &'a UnsafeCell<BlurBuff>,
tag: u32,
}
impl<'a> BlurCache<'a> {
pub fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option<Self>
where
T: LocalAllocLeakExt<'alloc>,
{
let buff = bump
.alloc_t::<UnsafeCell<BlurBuff>>()?
.uninit
.init(UnsafeCell::new([0; 7928])); // TODO !!! 7928
Some(Self {
algo: None,
buff,
tag: 0,
})
}
pub fn get(
&mut self,
size: Offset,
radius: usize,
tag: Option<u32>,
) -> Result<(&mut BlurAlgorithm<'a>, u32), ()> {
if let Some(tag) = tag {
if self.tag == tag {
return Ok((unwrap!(self.algo.as_mut()), self.tag));
}
}
// Drop the existing blurring inbstance holding
// a mutable reference to its scratchpad buffer
self.algo = None;
self.tag += 1;
// Now there's nobody else holding any reference to our buffer
// so we can get mutable reference and pass it to a new
// instance of the blurring algorithm
let buff = unsafe { &mut *self.buff.get() };
self.algo = Some(BlurAlgorithm::new(size, radius, buff)?);
Ok((unwrap!(self.algo.as_mut()), self.tag))
}
}

@ -0,0 +1,99 @@
use super::zlib_cache::ZlibCache;
#[cfg(feature = "ui_blurring")]
use super::blur_cache::BlurCache;
#[cfg(feature = "ui_jpeg_decoder")]
use super::jpeg_cache::JpegCache;
use core::cell::{RefCell, RefMut};
use without_alloc::alloc::LocalAllocLeakExt;
const ALIGN_PAD: usize = 8;
const ZLIB_CACHE_SLOTS: usize = 4;
const JPEG_CACHE_SLOTS: usize = 1;
const RENDER_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
const IMAGE_BUFF_SIZE: usize = 2048 + ALIGN_PAD;
pub type ImageBuff = [u8; IMAGE_BUFF_SIZE];
pub type RenderBuff = [u8; RENDER_BUFF_SIZE];
pub type ImageBuffRef<'a> = RefMut<'a, ImageBuff>;
pub type RenderBuffRef<'a> = RefMut<'a, RenderBuff>;
pub struct DrawingCache<'a> {
zlib_cache: RefCell<ZlibCache<'a>>,
#[cfg(feature = "ui_jpeg_decoder")]
jpeg_cache: RefCell<JpegCache<'a>>,
#[cfg(feature = "ui_blurring")]
blur_cache: RefCell<BlurCache<'a>>,
render_buff: &'a RefCell<RenderBuff>,
image_buff: &'a RefCell<ImageBuff>,
}
fn alloc_buf<'a, const S: usize, B>(bump: &'a B) -> Option<&'a RefCell<[u8; S]>>
where
B: LocalAllocLeakExt<'a>,
{
Some(
bump.alloc_t::<RefCell<[u8; S]>>()?
.uninit
.init(RefCell::new([0; S])),
)
}
impl<'a> DrawingCache<'a> {
pub fn new<TA, TB>(bump_a: &'a TA, bump_b: &'a TB) -> Self
where
TA: LocalAllocLeakExt<'a>,
TB: LocalAllocLeakExt<'a>,
{
Self {
zlib_cache: RefCell::new(unwrap!(
ZlibCache::new(bump_a, ZLIB_CACHE_SLOTS),
"ZLIB cache alloc"
)),
#[cfg(feature = "ui_jpeg_decoder")]
jpeg_cache: RefCell::new(unwrap!(
JpegCache::new(bump_a, JPEG_CACHE_SLOTS),
"JPEG cache alloc"
)),
#[cfg(feature = "ui_blurring")]
blur_cache: RefCell::new(unwrap!(BlurCache::new(bump_a), "Blur cache alloc")),
render_buff: unwrap!(alloc_buf(bump_b), "Render buff alloc"),
image_buff: unwrap!(alloc_buf(bump_b), "Toif buff alloc"),
}
}
/// Returns an object for decompression of TOIF images
pub fn zlib(&self) -> RefMut<ZlibCache<'a>> {
self.zlib_cache.borrow_mut()
}
/// Returns an object for decompression of JPEG images
#[cfg(feature = "ui_jpeg_decoder")]
pub fn jpeg(&self) -> RefMut<JpegCache<'a>> {
self.jpeg_cache.borrow_mut()
}
/// Returns an object providing blurring algorithm
#[cfg(feature = "ui_blurring")]
pub fn blur(&self) -> RefMut<BlurCache<'a>> {
self.blur_cache.borrow_mut()
}
/// Returns a buffer used for ProgressiveRenderer slice
pub fn render_buff(&self) -> Option<RenderBuffRef<'a>> {
self.render_buff.try_borrow_mut().ok()
}
/// Returns a buffer for intended for drawing of
/// QrCode or ToifImage
pub fn image_buff(&self) -> Option<ImageBuffRef<'a>> {
self.image_buff.try_borrow_mut().ok()
}
}

@ -0,0 +1,364 @@
use crate::ui::{
display::tjpgd,
geometry::{Offset, Point, Rect},
shape::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Rgb565Canvas},
};
use core::cell::UnsafeCell;
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
// JDEC work buffer size
//
// number of quantization tables (n_qtbl) = 2..4 (typical 2)
// number of huffman tables (n_htbl) = 2..4 (typical 2)
// mcu size = 1 * 1 .. 2 * 2 = 1..4 (typical 4)
//
// hufflut_ac & hufflut_dc are required only if JD_FASTDECODE == 2 (default)
//
// ---------------------------------------------------------------------
// table | size calculation | MIN..MAX | TYP
// ---------------------------------------------------------------------
// qttbl | n_qtbl * size_of(i32) * 64 | 512..1024 | 512
// huffbits | n_htbl * size_of(u8) * 16 | 32..64 | 32
// huffcode | n_htbl * size_of(u16) * 256 | 1024..2048 | 1024
// huffdata | n_htbl * size_of(u8) * 256 | 512..1024 | 512
// hufflut_ac | n_htbl * size_of(u16) * 1024 | 4096..8192 | 4096
// hufflut_dc | n_htbl * size_of(u8) * 1024 | 2048..4096 | 2048
// workbuf | mcu_size * 192 + 64 | 256..832 | 832
// mcubuf | (mcu_size + 2) * size_of(u16) * 64 | 384..768 | 768
// inbuff | JD_SZBUF constant | 512..512 | 512
// ---------------------------------------------------------------|------
// SUM | | 9376..18560 | 10336
// ---------------------------------------------------------------|------
const JPEG_SCRATCHPAD_SIZE: usize = 10500; // the same const > 10336 as in original code
// Buffer for a cached row of JPEG MCUs (up to 240x16 RGB565 pixels)
const ALIGN_PAD: usize = 8;
const JPEG_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
pub struct JpegCacheSlot<'a> {
// Reference to compressed data
jpeg: &'a [u8],
// value in range 0..3 leads into scale factor 1 << scale
scale: u8,
// Input buffer referencing compressed data
input: Option<tjpgd::BufferInput<'a>>,
// JPEG decoder instance
decoder: Option<tjpgd::JDEC<'a>>,
// Scratchpad memory used by the JPEG decoder
// (it's used just by our decoder and nobody else)
scratchpad: &'a UnsafeCell<[u8; JPEG_SCRATCHPAD_SIZE]>,
// horizontal coordinate of cached row or None
// (valid if row_canvas is Some)
row_y: i16,
// Canvas for recently decoded row of MCU's
row_canvas: Option<Rgb565Canvas<'a>>,
// Buffer for slice canvas
row_buff: &'a UnsafeCell<[u8; JPEG_BUFF_SIZE]>,
}
impl<'a> JpegCacheSlot<'a> {
fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option<Self>
where
T: LocalAllocLeakExt<'alloc>,
{
let scratchpad = bump
.alloc_t::<UnsafeCell<[u8; JPEG_SCRATCHPAD_SIZE]>>()?
.uninit
.init(UnsafeCell::new([0; JPEG_SCRATCHPAD_SIZE]));
let canvas_buff = bump
.alloc_t::<UnsafeCell<[u8; JPEG_BUFF_SIZE]>>()?
.uninit
.init(UnsafeCell::new([0; JPEG_BUFF_SIZE]));
Some(Self {
jpeg: &[],
scale: 0,
input: None,
decoder: None,
scratchpad,
row_y: 0,
row_canvas: None,
row_buff: canvas_buff,
})
}
fn reset<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<(), tjpgd::Error> {
// Drop the existing decoder holding
// a mutable reference to the scratchpad and canvas buffer & c
self.decoder = None;
self.row_canvas = None;
if !jpeg.is_empty() {
// Now there's nobody else holding any reference to our scratchpad buffer
// so we can get a mutable reference and pass it to a new
// instance of the JPEG decoder
let scratchpad = unsafe { &mut *self.scratchpad.get() };
// Prepare a input buffer
let mut input = tjpgd::BufferInput(jpeg);
// Initialize the decoder by reading headers from input
let mut decoder = tjpgd::JDEC::new(&mut input, scratchpad)?;
// Set decoder scale factor
decoder.set_scale(scale)?;
self.decoder = Some(decoder);
// Save modified input buffer
self.input = Some(input);
} else {
self.input = None;
}
self.jpeg = jpeg;
self.scale = scale;
Ok(())
}
fn is_for<'i: 'a>(&self, jpeg: &'i [u8], scale: u8) -> bool {
jpeg == self.jpeg && scale == self.scale && self.decoder.is_some()
}
pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<Offset, tjpgd::Error> {
if !self.is_for(jpeg, scale) {
self.reset(jpeg, scale)?;
}
let decoder = unwrap!(self.decoder.as_mut()); // should never fail
let divisor = 1 << self.scale;
Ok(Offset::new(
decoder.width() / divisor,
decoder.height() / divisor,
))
}
// left-top origin of output rectangle must be aligned to JPEG MCU size
pub fn decompress_mcu<'i: 'a>(
&mut self,
jpeg: &'i [u8],
scale: u8,
offset: Point,
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
) -> Result<(), tjpgd::Error> {
// Reset the slot if the JPEG image is different
if !self.is_for(jpeg, scale) {
self.reset(jpeg, scale)?;
}
// Get coordinates of the next coming MCU
let decoder = unwrap!(self.decoder.as_ref()); // should never fail
let divisor = 1 << self.scale;
let next_mcu = Offset::new(
decoder.next_mcu().0 as i16 / divisor,
decoder.next_mcu().1 as i16 / divisor,
);
// Get height of the MCUs (8 or 16pixels)
let mcu_height = decoder.mcu_height() / (1 << self.scale);
// Reset the decoder if pixel at the offset was already decoded
if offset.y < next_mcu.y || (offset.x < next_mcu.x && offset.y < next_mcu.y + mcu_height) {
self.reset(self.jpeg, scale)?;
}
let decoder = unwrap!(self.decoder.as_mut()); // should never fail
let input = unwrap!(self.input.as_mut()); // should never fail
let mut output = JpegFnOutput::new(output);
match decoder.decomp2(input, &mut output) {
Ok(_) | Err(tjpgd::Error::Interrupted) => Ok(()),
Err(e) => Err(e),
}
}
pub fn decompress_row<'i: 'a>(
&mut self,
jpeg: &'i [u8],
scale: u8,
mut offset_y: i16,
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
) -> Result<(), tjpgd::Error> {
// Reset the slot if the JPEG image is different
if !self.is_for(jpeg, scale) {
self.reset(jpeg, scale)?;
}
let mut row_canvas = self.row_canvas.take();
let mut row_y = self.row_y;
// Use cached data if possible
if let Some(row_canvas) = row_canvas.as_mut() {
if offset_y >= self.row_y && offset_y < self.row_y + row_canvas.height() {
if !output(
Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)),
row_canvas.view(),
) {
return Ok(());
}
// Align to the next MCU row
offset_y += row_canvas.height() - offset_y % row_canvas.height();
}
} else {
// Create a new row for cahing decoded JPEG data
// Now there's nobody else holding any reference to canvas_buff so
// we can get a mutable reference and pass it to a new instance
// of Rgb565Canvas
let canvas_buff = unsafe { &mut *self.row_buff.get() };
// Prepare canvas as a cache for a row of decoded JPEG MCUs
let decoder = unwrap!(self.decoder.as_ref()); // shoud never fail
let divisor = 1 << self.scale;
row_canvas = Some(unwrap!(
Rgb565Canvas::new(
Offset::new(decoder.width() / divisor, decoder.mcu_height() / divisor),
None,
canvas_buff
),
"Buffer too small"
));
}
self.decompress_mcu(
jpeg,
scale,
Point::new(0, offset_y),
&mut |mcu_r, mcu_bitmap| {
// Get canvas for MCU caching
let row_canvas = unwrap!(row_canvas.as_mut()); // should never fail
// Calculate coordinates in the row canvas
let dst_r = Rect {
y0: 0,
y1: mcu_r.height(),
..mcu_r
};
// Draw a MCU
row_canvas.draw_bitmap(dst_r, mcu_bitmap);
if mcu_r.x1 < row_canvas.size().x {
// We are not done with the row yet
true
} else {
// We have a complete row, let's pass it to the callee
row_y = mcu_r.y0;
output(
Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)),
row_canvas.view(),
)
}
},
)?;
// Store the recently decoded row for future use
self.row_y = row_y;
self.row_canvas = row_canvas;
Ok(())
}
}
struct JpegFnOutput<F>
where
F: FnMut(Rect, BitmapView) -> bool,
{
output: F,
}
impl<F> JpegFnOutput<F>
where
F: FnMut(Rect, BitmapView) -> bool,
{
pub fn new(output: F) -> Self {
Self { output }
}
}
impl<F> trezor_tjpgdec::JpegOutput for JpegFnOutput<F>
where
F: FnMut(Rect, BitmapView) -> bool,
{
fn write(
&mut self,
_jd: &tjpgd::JDEC,
rect_origin: (u32, u32),
rect_size: (u32, u32),
pixels: &[u16],
) -> bool {
// MCU coordinates in source image
let mcu_r = Rect::from_top_left_and_size(
Point::new(rect_origin.0 as i16, rect_origin.1 as i16),
Offset::new(rect_size.0 as i16, rect_size.1 as i16),
);
// SAFETY: aligning from [u16] -> [u8]
let (_, pixels, _) = unsafe { pixels.align_to() };
// Create readonly bitmap
let mcu_bitmap = unwrap!(Bitmap::new(
BitmapFormat::RGB565,
None,
mcu_r.size(),
None,
pixels,
));
// Return true to continue decompression
(self.output)(mcu_r, BitmapView::new(&mcu_bitmap))
}
}
pub struct JpegCache<'a> {
slots: FixedVec<'a, JpegCacheSlot<'a>>,
}
impl<'a> JpegCache<'a> {
pub fn new<'alloc: 'a, T>(bump: &'alloc T, slot_count: usize) -> Option<Self>
where
T: LocalAllocLeakExt<'alloc>,
{
assert!(slot_count <= 1); // we support just 1 decoder
let mut cache = Self {
slots: bump.fixed_vec(slot_count)?,
};
for _ in 0..cache.slots.capacity() {
unwrap!(cache.slots.push(JpegCacheSlot::new(bump)?)); // should never fail
}
Some(cache)
}
pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<Offset, tjpgd::Error> {
if self.slots.capacity() > 0 {
self.slots[0].get_size(jpeg, scale)
} else {
Err(tjpgd::Error::MemoryPool)
}
}
pub fn decompress_mcu<'i: 'a>(
&mut self,
jpeg: &'i [u8],
scale: u8,
offset: Point,
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
) -> Result<(), tjpgd::Error> {
if self.slots.capacity() > 0 {
self.slots[0].decompress_mcu(jpeg, scale, offset, output)
} else {
Err(tjpgd::Error::MemoryPool)
}
}
pub fn decompress_row<'i: 'a>(
&mut self,
jpeg: &'i [u8],
scale: u8,
offset_y: i16,
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
) -> Result<(), tjpgd::Error> {
if self.slots.capacity() > 0 {
self.slots[0].decompress_row(jpeg, scale, offset_y, output)
} else {
Err(tjpgd::Error::MemoryPool)
}
}
}

@ -0,0 +1,7 @@
pub mod blur_cache;
pub mod drawing_cache;
#[cfg(feature = "ui_jpeg_decoder")]
pub mod jpeg_cache;
pub mod zlib_cache;

@ -0,0 +1,166 @@
use crate::{
trezorhal::uzlib::{UzlibContext, UZLIB_WINDOW_SIZE},
ui::display::toif::Toif,
};
use core::cell::UnsafeCell;
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
struct ZlibCacheSlot<'a> {
/// Reference to compressed data
zdata: &'a [u8],
/// Current offset in docempressed data
offset: usize,
/// Decompression context for the current zdata
dc: Option<UzlibContext<'a>>,
/// Window used by current decompression context.
/// (It's used just by own dc and nobody else.)
window: &'a UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>,
}
impl<'a> ZlibCacheSlot<'a> {
fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option<Self>
where
T: LocalAllocLeakExt<'alloc>,
{
let window = bump
.alloc_t::<UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>>()?
.uninit
.init(UnsafeCell::new([0; UZLIB_WINDOW_SIZE]));
Some(Self {
zdata: &[],
offset: 0,
dc: None,
window,
})
}
/// May be called with zdata == &[] to make the slot free
fn reset(&mut self, zdata: &'a [u8]) {
// Drop the existing decompression context holding
// a mutable reference to window buffer
self.dc = None;
if !zdata.is_empty() {
// Now there's nobody else holding any reference to our window
// so we can get mutable reference and pass it to a new
// instance of the decompression context
let window = unsafe { &mut *self.window.get() };
self.dc = Some(UzlibContext::new(zdata, Some(window)));
}
self.offset = 0;
self.zdata = zdata;
}
fn uncompress(&mut self, dest_buf: &mut [u8]) -> Result<bool, ()> {
if let Some(dc) = self.dc.as_mut() {
match dc.uncompress(dest_buf) {
Ok(done) => {
if done {
self.reset(&[]);
} else {
self.offset += dest_buf.len();
}
Ok(done)
}
Err(e) => Err(e),
}
} else {
Err(())
}
}
fn skip(&mut self, nbytes: usize) -> Result<bool, ()> {
if let Some(dc) = self.dc.as_mut() {
match dc.skip(nbytes) {
Ok(done) => {
if done {
self.reset(&[]);
} else {
self.offset += nbytes;
}
Ok(done)
}
Err(e) => Err(e),
}
} else {
Err(())
}
}
fn is_for(&self, zdata: &[u8], offset: usize) -> bool {
self.zdata == zdata && self.offset == offset
}
}
pub struct ZlibCache<'a> {
slots: FixedVec<'a, ZlibCacheSlot<'a>>,
}
impl<'a> ZlibCache<'a> {
pub fn new<'alloc: 'a, T>(bump: &'alloc T, slot_count: usize) -> Option<Self>
where
T: LocalAllocLeakExt<'alloc>,
{
let mut cache = Self {
slots: bump.fixed_vec(slot_count)?,
};
for _ in 0..cache.slots.capacity() {
unwrap!(cache.slots.push(ZlibCacheSlot::new(bump)?)); // should never fail
}
Some(cache)
}
fn select_slot_for_reuse(&self) -> Result<usize, ()> {
if self.slots.capacity() > 0 {
let mut selected = 0;
for (i, slot) in self.slots.iter().enumerate() {
if slot.dc.is_none() {
selected = i;
break;
}
}
Ok(selected)
} else {
Err(())
}
}
pub fn uncompress(
&mut self,
zdata: &'a [u8],
offset: usize,
dest_buf: &mut [u8],
) -> Result<bool, ()> {
let slot = self
.slots
.iter_mut()
.find(|slot| slot.is_for(zdata, offset));
if let Some(slot) = slot {
slot.uncompress(dest_buf)
} else {
let selected = self.select_slot_for_reuse()?;
let slot = &mut self.slots[selected];
slot.reset(zdata);
slot.skip(offset)?;
slot.uncompress(dest_buf)
}
}
pub fn uncompress_toif(
&mut self,
toif: Toif<'a>,
from_row: i16,
dest_buf: &mut [u8],
) -> Result<(), ()> {
let from_offset = toif.stride() * from_row as usize;
self.uncompress(toif.zdata(), from_offset, dest_buf)?;
Ok(())
}
}

@ -0,0 +1,891 @@
use crate::ui::{
display::Color,
geometry::{Offset, Point, Rect},
};
use super::super::{
algo::{circle_points, line_points, sin_i16, PI4},
BitmapView, Viewport,
};
#[cfg(feature = "ui_blurring")]
use crate::ui::shape::DrawingCache;
pub trait BasicCanvas {
/// Returns dimensions of the canvas in pixels.
fn size(&self) -> Offset;
/// Returns the dimensions of the canvas as a rectangle with
/// the top-left at (0,0).
fn bounds(&self) -> Rect {
Rect::from_size(self.size())
}
/// Returns the width of the canvas in pixels.
fn width(&self) -> i16 {
self.size().x
}
/// Returns the height of the canvas in pixels.
fn height(&self) -> i16 {
self.size().y
}
/// Gets the current drawing viewport previously set by `set_viewport()`
/// function.
fn viewport(&self) -> Viewport;
/// Sets the active viewport valid for all subsequent drawing operations.
fn set_viewport(&mut self, vp: Viewport);
/// Sets the new viewport that's intersection of the
/// current viewport and the `window` rectangle relative
/// to the current viewport. The viewport's origin is
/// set to the top-left corener of the `window`.
fn set_window(&mut self, window: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_window(window));
viewport
}
/// Sets the new viewport that's intersection of the
/// current viewport and the `clip` rectangle relative
/// to the current viewport. The viewport's origin is
/// not changed.
fn set_clip(&mut self, clip: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_clip(clip));
viewport
}
/// Draws a filled rectangle with the specified color.
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8);
/// Fills the canvas background with the specified color.
fn fill_background(&mut self, color: Color) {
self.fill_rect(self.viewport().clip, color, 255);
}
/// Draws a bitmap of bitmap into to the rectangle.
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView);
}
pub trait Canvas: BasicCanvas {
/// Returns a non-mutable view of the underlying bitmap.
fn view(&self) -> BitmapView;
/// Draw a pixel at specified coordinates.
fn draw_pixel(&mut self, pt: Point, color: Color);
/// Draws a single pixel and blends its color with the background.
///
/// - If alpha == 255, the (foreground) pixel color is used.
/// - If 0 < alpha << 255, pixel and backround colors are blended.
/// - If alpha == 0, the background color is used.
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8);
/// Blends a bitmap with the canvas background
fn blend_bitmap(&mut self, r: Rect, src: BitmapView);
/// Applies a blur effect to the specified rectangle.
///
/// The blur effect works properly only when the rectangle is not clipped,
/// which is a strong constraint that's hard to be met. The function uses a
/// simple box filter, where the 'radius' argument represents the length
/// of the sides of this filter.
///
/// It's important to be aware that strong artifacts may appear on images
/// with horizontal/vertical lines.
#[cfg(feature = "ui_blurring")]
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache);
/// Draws an outline of a rectangle with rounded corners.
fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
let split = unwrap!(circle_points(radius).last()).v;
let b = Rect {
y1: r.y0 + radius - split + 1,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius) {
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
if p.v == radius && p.last {
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
} else {
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
}
let b = Rect {
y0: r.y0 + radius - split + 1,
y1: r.y0 + radius + 1,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
self.fill_rect(
Rect {
x0: r.x0,
y0: r.y0 + radius + 1,
x1: r.x0 + 1,
y1: r.y1 - radius - 1,
},
color,
255,
);
self.fill_rect(
Rect {
x0: r.x1 - 1,
y0: r.y0 + radius + 1,
x1: r.x1,
y1: r.y1 - radius - 1,
},
color,
255,
);
let b = Rect {
y0: r.y1 - radius - 1,
y1: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let b = Rect {
y0: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius) {
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
if p.v == radius && p.last {
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
} else {
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
}
}
/// Draws filled rectangle with rounded corners.
#[cfg(not(feature = "ui_antialiasing"))]
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
let split = unwrap!(circle_points(radius).last()).v;
let b = Rect {
y1: r.y0 + radius - split + 1,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius) {
if p.last {
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
}
let b = Rect {
y0: r.y0 + radius - split + 1,
y1: r.y0 + radius + 1,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
self.fill_rect(
Rect {
x0: r.x0,
y0: r.y0 + radius + 1,
x1: r.x1,
y1: r.y1 - radius - 1,
},
color,
alpha,
);
let b = Rect {
y0: r.y1 - radius - 1,
y1: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
let b = Rect {
y0: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius) {
if p.last {
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
}
}
/// Draws filled rectangle with antialiased rounded corners.
#[cfg(feature = "ui_antialiasing")]
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
let split = unwrap!(circle_points(radius).last()).v;
let b = Rect {
y1: r.y0 + radius - split + 1,
..r
};
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
if self.viewport().contains(b) {
for p in circle_points(radius) {
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
if p.first {
let inner = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(inner, color, alpha);
}
}
}
let b = Rect {
y0: r.y0 + radius - split + 1,
y1: r.y0 + radius + 1,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
let inner = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(inner, color, alpha);
}
}
self.fill_rect(
Rect {
x0: r.x0,
y0: r.y0 + radius + 1,
x1: r.x1,
y1: r.y1 - radius - 1,
},
color,
alpha,
);
let b = Rect {
y0: r.y1 - radius - 1,
y1: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
let b = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(b, color, alpha);
}
}
let b = Rect {
y0: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for p in circle_points(radius) {
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
if p.first {
let b = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(b, color, alpha);
}
}
}
}
// Draws circle with the specified center and the radius.
#[cfg(not(feature = "ui_antialiasing"))]
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
let split = unwrap!(circle_points(radius).last()).v;
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
let pt_l = Point::new(center.x - p.u, center.y - p.v);
let pt_r = Point::new(center.x + p.u, center.y - p.v);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y - p.u);
let pt_r = Point::new(center.x + p.v, center.y - p.u);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + 1),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y + p.u);
let pt_r = Point::new(center.x + p.v, center.y + p.u);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
let pt_l = Point::new(center.x - p.u, center.y + p.v);
let pt_r = Point::new(center.x + p.u, center.y + p.v);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
}
/// Draws antialiased circle with the specified center and the radius.
/*#[cfg(feature = "ui_antialiasing")]
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
let split = unwrap!(circle_points(radius).last()).v;
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
let pt_l = Point::new(center.x - p.u, center.y - p.v);
self.blend_pixel(pt_l, color, p.frac);
self.blend_pixel(pt_l.under(), color, 255 - p.frac);
let pt_r = Point::new(center.x + p.u, center.y - p.v);
self.blend_pixel(pt_r, color, p.frac);
self.blend_pixel(pt_r.under(), color, 255 - p.frac);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y - p.u);
self.blend_pixel(pt_l, color, p.frac);
self.blend_pixel(pt_l.onright(), color, 255 - p.frac);
let pt_r = Point::new(center.x + p.v, center.y - p.u);
self.blend_pixel(pt_r, color, p.frac);
self.blend_pixel(pt_r.onleft(), color, 255 - p.frac);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + 1),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y + p.u);
self.blend_pixel(pt_l, color, p.frac);
self.blend_pixel(pt_l.onright(), color, 255 - p.frac);
let pt_r = Point::new(center.x + p.v, center.y + p.u);
self.blend_pixel(pt_r, color, p.frac);
self.blend_pixel(pt_r.onleft(), color, 255 - p.frac);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
let pt_l = Point::new(center.x - p.u, center.y + p.v);
self.blend_pixel(pt_l, color, p.frac);
self.blend_pixel(pt_l.above(), color, 255 - p.frac);
let pt_r = Point::new(center.x + p.u, center.y + p.v);
self.blend_pixel(pt_r, color, p.frac);
self.blend_pixel(pt_r.above(), color, 255 - p.frac);
}
}
}*/
/// Draws filled circle with the specified center and the radius.
#[cfg(not(feature = "ui_antialiasing"))]
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
let split = unwrap!(circle_points(radius).last()).v;
let alpha = 255;
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
if p.last {
let pt_l = Point::new(center.x - p.u, center.y - p.v);
let pt_r = Point::new(center.x + p.u, center.y - p.v);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y - p.u);
let pt_r = Point::new(center.x + p.v, center.y - p.u);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + 1),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y + p.u);
let pt_r = Point::new(center.x + p.v, center.y + p.u);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
if p.last {
let pt_l = Point::new(center.x - p.u, center.y + p.v);
let pt_r = Point::new(center.x + p.u, center.y + p.v);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
}
}
}
}
/// Draws antialiased filled circle with the specified center and the
/// radius.
#[cfg(feature = "ui_antialiasing")]
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
let split = unwrap!(circle_points(radius).last()).v;
let alpha = 255;
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
let pt_l = Point::new(center.x - p.u, center.y - p.v);
let pt_r = Point::new(center.x + p.u, center.y - p.v);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
if pt_l != pt_r {
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
}
if p.first {
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color, alpha);
}
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y - p.u);
let pt_r = Point::new(center.x + p.v, center.y - p.u);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color, alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + 1),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
let pt_l = Point::new(center.x - p.v, center.y + p.u);
let pt_r = Point::new(center.x + p.v, center.y + p.u);
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color, alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for p in circle_points(radius) {
let pt_l = Point::new(center.x - p.u, center.y + p.v);
let pt_r = Point::new(center.x + p.u, center.y + p.v);
if pt_l != pt_r {
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
}
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
if p.first {
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color, alpha);
}
}
}
}
/// Fills circle sector with a specified color.
fn fill_sector(
&mut self,
center: Point,
radius: i16,
mut start: i16,
mut end: i16,
color: Color,
) {
start = (PI4 * 8 + start % (PI4 * 8)) % (PI4 * 8);
end = (PI4 * 8 + end % (PI4 * 8)) % (PI4 * 8);
let alpha = 255;
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
if start != end {
// The algorithm fills everything except the middle point ;-)
self.draw_pixel(center, color);
}
for octant in 0..8 {
let angle = octant * PI4;
// Function for calculation of 'u' coordinate inside the circle octant
// radius * sin(angle)
let sin = |angle: i16| -> i16 { sin_i16(angle, radius) };
// Calculate the octant's bounding rectangle
let p = Point::new(sin(PI4) + 1, -radius - 1).rot(octant);
let r = Rect::new(center, p + center.into()).normalize();
// Skip octant if not visible
if !self.viewport().contains(r) {
continue;
}
// Function for filling a line between two endpoints with antialiasing.
// The function is special for each octant using 4 different axes of symmetry
let filler = &mut |p1: Option<Point>, p1_frac, p2: Point, p2_frac| {
let p2: Point = center + p2.rot(octant).into();
self.blend_pixel(p2, color, alpha_mul(p2_frac));
if let Some(p1) = p1 {
let p1: Point = center + p1.rot(octant).into();
let ofs = Point::new(-1, 0).rot(octant);
self.blend_pixel(p1 + ofs.into(), color, alpha_mul(p1_frac));
if ofs.x + ofs.y < 0 {
if ofs.x != 0 {
self.fill_rect(Rect::new(p1, p2.under()), color, alpha);
} else {
self.fill_rect(Rect::new(p1, p2.onright()), color, alpha);
}
} else {
let p1 = p1 + ofs.into();
let p2 = p2 + ofs.into();
if ofs.x != 0 {
self.fill_rect(Rect::new(p2, p1.under()), color, alpha);
} else {
self.fill_rect(Rect::new(p2, p1.onright()), color, alpha);
}
}
}
};
let corr = if octant & 1 == 0 {
// The clockwise octant
|angle| angle
} else {
// The anticlockwise octant
|angle| PI4 - angle
};
if start <= end {
// Octant may contain 0 or 1 sector
if start < angle + PI4 && end > angle {
if start <= angle && end >= angle + PI4 {
// Fill all pixels in the octant
fill_octant(radius, 0, sin(PI4), filler);
} else {
// Partial fill
let u1 = if start <= angle {
sin(corr(0))
} else {
sin(corr(start - angle))
};
let u2 = if end <= angle + PI4 {
sin(corr(end - angle))
} else {
sin(corr(PI4))
};
fill_octant(radius, u1, u2, filler);
}
}
} else {
// Octant may contain 0, 1 or 2 sectors
if end >= angle + PI4 || start <= angle {
// Fill all pixels in the octant
fill_octant(radius, 0, sin(PI4), filler);
} else {
// Partial fill
if (end > angle) && (end < angle + PI4) {
// Fill up to `end`
fill_octant(radius, sin(corr(0)), sin(corr(end - angle)), filler);
}
if start < angle + PI4 {
// Fill all from `start`
fill_octant(radius, sin(corr(start - angle)), sin(corr(PI4)), filler);
}
}
}
}
}
}
/// Calculates endpoints of a single octant of a circle
///
/// Used internally by `Canvas::fill_sector()`.
fn fill_octant(
radius: i16,
mut u1: i16,
mut u2: i16,
fill: &mut impl FnMut(Option<Point>, u8, Point, u8),
) {
// Starting end ending points on
if u1 > u2 {
(u1, u2) = (u2, u1);
}
let mut iter = circle_points(radius).skip(u1 as usize);
// Intersection of the p1 line and the circle
let p1_start = unwrap!(iter.next());
// Intersection of the p1 line and the circle
let mut p2_start = p1_start;
loop {
if let Some(p) = iter.next() {
if p.u > u2 {
break;
}
p2_start = p;
} else {
break;
}
}
// Flag if we draw section up to 45degs
let join_flag = iter.next().is_none();
// Process area between a p1 line and the circle
let mut p1_iter = line_points(p1_start.v, p1_start.u, 0);
let mut first = true;
let mut skip = 0;
for c in circle_points(radius)
.skip(p1_start.u as usize)
.take((p2_start.u - p1_start.u) as usize)
{
let p2_coord = Point::new(c.u, -c.v);
if c.first || first {
let p1 = unwrap!(p1_iter.next());
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
first = false;
fill(Some(p1_coord), p1.frac, p2_coord, c.frac);
} else {
fill(None, 0, p2_coord, c.frac);
}
skip = if c.last { 0 } else { 1 };
}
// Process area between a p1 and p2 lines
let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip);
for (p1, p2) in p1_iter.zip(p2_iter) {
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u);
let p2_frac = if join_flag { 255 } else { 255 - p2.frac };
fill(Some(p1_coord), p1.frac, p2_coord, p2_frac);
}
}
impl Rect {
/// Normalizes the rectangle coordinates.
///
/// Returns a new `Rect` with potentially swapped left/right,
/// top/bottom coordinates, ensuring that `x0`, `y0` represents
/// the top-left corner and `x1`, `y1` represents the bottom-right corner.
pub fn normalize(&self) -> Self {
Rect {
x0: core::cmp::min(self.x0, self.x1),
y0: core::cmp::min(self.y0, self.y1),
x1: core::cmp::max(self.x0, self.x1),
y1: core::cmp::max(self.y0, self.y1),
}
}
}
impl Point {
fn onleft(self) -> Self {
Self {
x: self.x - 1,
..self
}
}
fn onright(self) -> Self {
Self {
x: self.x + 1,
..self
}
}
fn above(self) -> Self {
Self {
y: self.y - 1,
..self
}
}
fn under(self) -> Self {
Self {
y: self.y + 1,
..self
}
}
fn rot(self, octant: i16) -> Self {
let mut result = self;
if (octant + 1) & 2 != 0 {
result = Point::new(-result.y, -result.x);
}
if octant & 4 != 0 {
result = Point::new(-result.x, result.y);
}
if (octant + 2) & 4 != 0 {
result = Point::new(result.x, -result.y);
}
result
}
}

@ -0,0 +1,11 @@
mod common;
mod mono8;
mod rgb565;
mod rgba8888;
mod viewport;
pub use common::{BasicCanvas, Canvas};
pub use mono8::Mono8Canvas;
pub use rgb565::Rgb565Canvas;
pub use rgba8888::Rgba8888Canvas;
pub use viewport::Viewport;

@ -0,0 +1,100 @@
use crate::ui::{
display::Color,
geometry::{Offset, Point, Rect},
};
use super::{
super::{Bitmap, BitmapFormat, BitmapView},
BasicCanvas, Canvas, Viewport,
};
#[cfg(feature = "ui_blurring")]
use super::super::DrawingCache;
/// A struct representing 8-bit monochromatic canvas
pub struct Mono8Canvas<'a> {
bitmap: Bitmap<'a>,
viewport: Viewport,
}
impl<'a> Mono8Canvas<'a> {
/// Creates a new canvas with the specified size and buffer.
///
/// Optionally minimal height can be specified and then the height
/// of the new bitmap is adjusted to the buffer size.
///
/// Returns None if the buffer is not big enough.
pub fn new(size: Offset, min_height: Option<i16>, buff: &'a mut [u8]) -> Option<Self> {
let bitmap = Bitmap::new_mut(BitmapFormat::MONO8, None, size, min_height, buff)?;
let viewport = Viewport::from_size(bitmap.size());
Some(Self { bitmap, viewport })
}
/// Returns the specified row as a mutable slice.
///
/// Returns None if row is out of range.
pub fn row_mut(&mut self, row: i16) -> Option<&mut [u8]> {
self.bitmap.row_mut(row)
}
}
impl<'a> BasicCanvas for Mono8Canvas<'a> {
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn size(&self) -> Offset {
self.bitmap.size()
}
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
let r = r.translate(self.viewport.origin);
self.bitmap.mono8_fill(r, self.viewport.clip, color, alpha);
}
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
let r = r.translate(self.viewport.origin);
self.bitmap.mono8_copy(r, self.viewport.clip, &bitmap);
}
}
impl<'a> Canvas for Mono8Canvas<'a> {
fn view(&self) -> BitmapView {
BitmapView::new(&self.bitmap)
}
fn draw_pixel(&mut self, pt: Point, color: Color) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
if let Some(row) = self.row_mut(pt.y) {
row[pt.x as usize] = color.luminance() as u8;
}
}
}
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
if let Some(row) = self.row_mut(pt.y) {
let pixel = &mut row[pt.x as usize];
let fg_color = color.luminance() as u16;
let bg_color = *pixel as u16;
*pixel = ((fg_color * alpha as u16 + bg_color * (255 - alpha) as u16) / 255) as u8;
}
}
}
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
let r = r.translate(self.viewport.origin);
self.bitmap.mono8_blend(r, self.viewport.clip, &src);
}
#[cfg(feature = "ui_blurring")]
fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) {
// Not implemented
}
}

@ -0,0 +1,123 @@
use crate::ui::{
display::Color,
geometry::{Offset, Point, Rect},
};
use super::{
super::{Bitmap, BitmapFormat, BitmapView},
BasicCanvas, Canvas, Viewport,
};
#[cfg(feature = "ui_blurring")]
use super::super::DrawingCache;
/// A struct representing 16-bit (RGB565) color canvas
pub struct Rgb565Canvas<'a> {
bitmap: Bitmap<'a>,
viewport: Viewport,
}
impl<'a> Rgb565Canvas<'a> {
/// Creates a new canvas with the specified size and buffer.
///
/// Optionally minimal height can be specified and then the height
/// of the new bitmap is adjusted to the buffer size.
///
/// Returns None if the buffer is not big enough.
pub fn new(size: Offset, min_height: Option<i16>, buff: &'a mut [u8]) -> Option<Self> {
let bitmap = Bitmap::new_mut(BitmapFormat::RGB565, None, size, min_height, buff)?;
let viewport = Viewport::from_size(bitmap.size());
Some(Self { bitmap, viewport })
}
/// Returns the specified row as a mutable slice.
///
/// Returns None if row is out of range.
pub fn row_mut(&mut self, row: i16) -> Option<&mut [u16]> {
self.bitmap.row_mut(row)
}
}
impl<'a> BasicCanvas for Rgb565Canvas<'a> {
fn size(&self) -> Offset {
self.bitmap.size()
}
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
let r = r.translate(self.viewport.origin);
self.bitmap.rgb565_fill(r, self.viewport.clip, color, alpha);
}
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
let r = r.translate(self.viewport.origin);
self.bitmap.rgb565_copy(r, self.viewport.clip, &bitmap);
}
}
impl<'a> Canvas for Rgb565Canvas<'a> {
fn view(&self) -> BitmapView {
BitmapView::new(&self.bitmap)
}
fn draw_pixel(&mut self, pt: Point, color: Color) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
if let Some(row) = self.row_mut(pt.y) {
row[pt.x as usize] = color.into();
}
}
}
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
if let Some(row) = self.row_mut(pt.y) {
let pixel = &mut row[pt.x as usize];
let bg_color: Color = (*pixel).into();
*pixel = bg_color.blend(color, alpha).into();
}
}
}
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
let r = r.translate(self.viewport.origin);
self.bitmap.rgb565_blend(r, self.viewport.clip, &src);
}
#[cfg(feature = "ui_blurring")]
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache) {
let clip = r.translate(self.viewport.origin).clamp(self.viewport.clip);
let ofs = radius as i16;
if clip.width() > 2 * ofs - 1 && clip.height() > 2 * ofs - 1 {
let mut blur_cache = cache.blur();
let (blur, _) = unwrap!(
blur_cache.get(clip.size(), radius, None),
"Too small blur buffer"
);
loop {
if let Some(y) = blur.push_ready() {
let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic
blur.push(&row[clip.x0 as usize..clip.x1 as usize]);
}
if let Some(y) = blur.pop_ready() {
let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic
blur.pop(&mut row[clip.x0 as usize..clip.x1 as usize], None);
if y + 1 >= clip.height() {
break;
}
}
}
}
}
}

@ -0,0 +1,93 @@
use crate::ui::{
display::Color,
geometry::{Offset, Point, Rect},
};
use super::{
super::{Bitmap, BitmapFormat, BitmapView},
BasicCanvas, Canvas, Viewport,
};
#[cfg(feature = "ui_blurring")]
use super::super::DrawingCache;
/// A struct representing 32-bit (RGBA8888) color canvas
pub struct Rgba8888Canvas<'a> {
bitmap: Bitmap<'a>,
viewport: Viewport,
}
impl<'a> Rgba8888Canvas<'a> {
/// Creates a new canvas with the specified size and buffer.
///
/// Optionally minimal height can be specified and then the height
/// of the new bitmap is adjusted to the buffer size.
///
/// Returns None if the buffer is not big enough.
pub fn new(size: Offset, min_height: Option<i16>, buff: &'a mut [u8]) -> Option<Self> {
let bitmap = Bitmap::new_mut(BitmapFormat::RGBA8888, None, size, min_height, buff)?;
let viewport = Viewport::from_size(bitmap.size());
Some(Self { bitmap, viewport })
}
/// Returns the specified row as a mutable slice.
///
/// Returns None if row is out of range.
pub fn row_mut(&mut self, row: i16) -> Option<&mut [u32]> {
self.bitmap.row_mut(row)
}
}
impl<'a> BasicCanvas for Rgba8888Canvas<'a> {
fn size(&self) -> Offset {
self.bitmap.size()
}
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
let r = r.translate(self.viewport.origin);
self.bitmap
.rgba8888_fill(r, self.viewport.clip, color, alpha);
}
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
let r = r.translate(self.viewport.origin);
self.bitmap.rgba8888_copy(r, self.viewport.clip, &bitmap);
}
}
impl<'a> Canvas for Rgba8888Canvas<'a> {
fn view(&self) -> BitmapView {
BitmapView::new(&self.bitmap)
}
fn draw_pixel(&mut self, pt: Point, color: Color) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
if let Some(row) = self.row_mut(pt.y) {
row[pt.x as usize] = color.into();
}
}
}
fn blend_pixel(&mut self, _pt: Point, _color: Color, _alpha: u8) {
// TODO: not implemented yet, requires 32-bit color blending routines
}
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
let r = r.translate(self.viewport.origin);
self.bitmap.rgba8888_blend(r, self.viewport.clip, &src);
}
#[cfg(feature = "ui_blurring")]
fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) {
// TODO
}
}

@ -0,0 +1,103 @@
use crate::ui::geometry::{Offset, Rect};
/// The Viewport concept is foundation for clipping and translating
/// during drawing on the general canvas.
///
/// The Viewport structure comprises a rectangle representing the
/// clipping area and a drawing origin (or offset), which is applied
/// to all coordinates passed to the drawing functions.
///
/// Two coordination systems exist - "absolute" and "relative."
///
/// In the "absolute" coordinate system, (0, 0) is at the left-top of
/// a referenced canvas (device or bitmap).
///
/// Relative coordinates are with respect to the viewport origin.
/// The relative coordinate (0, 0) is located at (viewport.origin.x,
/// viewport.origin.y).
///
/// Conversion between "absolute" and "relative" coordinates is straightforward:
///
/// pt_absolute = pt_relative.translate(viewport.origin)
///
/// pt_relative = pt_absolute.translate(-viewport.origin)
///
/// The Viewport's clipping area and origin are always in "absolute"
/// coordinates. Canvas objects utilize the viewport to translate "relative"
/// coordinates passed to drawing functions into "absolute" coordinates that
/// correspond to the target device or bitmap.
#[derive(Copy, Clone)]
pub struct Viewport {
/// Clipping rectangle relative to the canvas top-left corner
pub clip: Rect,
/// Offset applied to all coordinates before clipping
pub origin: Offset,
}
impl Viewport {
/// Creates a new viewport with specified clip rectangle and origin at
/// (0,0).
pub fn new(clip: Rect) -> Self {
Self {
clip,
origin: Offset::zero(),
}
}
/// Creates a new viewport with specified size and origin at (0,0).
pub fn from_size(size: Offset) -> Self {
Self {
clip: Rect::from_size(size),
origin: Offset::zero(),
}
}
/// Checks if the viewport intersects with the specified rectangle
/// given in relative coordinates.
pub fn contains(&self, r: Rect) -> bool {
!r.translate(self.origin).clamp(self.clip).is_empty()
}
pub fn translate(self, offset: Offset) -> Self {
Self {
clip: self.clip.translate(offset),
origin: self.origin + offset,
}
}
/// Creates a new viewport with the new origin given in
/// absolute coordinates.
pub fn with_origin(self, origin: Offset) -> Self {
Self { origin, ..self }
}
/// Creates a clip of the viewport containing only the specified rectangle
/// given in absolute coordinates. The origin of the new viewport
/// remains unchanged.
pub fn absolute_clip(self, r: Rect) -> Self {
Self {
clip: r.clamp(self.clip),
..self
}
}
/// Creates a clip of the viewport containing only the specified rectangle
/// given in relative coordinates. The origin of the new viewport
/// remains unchanged.
pub fn relative_clip(self, r: Rect) -> Self {
Self {
clip: r.translate(self.origin).clamp(self.clip),
..self
}
}
/// Creates a clip of the viewport containing only the specified rectangle
/// given in relative coordinates. The origin of the new viewport
/// is set to the top-left corner of the rectangle.
pub fn relative_window(&self, r: Rect) -> Self {
let clip = r.translate(self.origin).clamp(self.clip);
let origin = self.origin + (clip.top_left() - self.clip.top_left());
Self { clip, origin }
}
}

@ -0,0 +1,139 @@
use crate::ui::{
display::Color,
geometry::{Point, Rect},
};
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
/// A shape for rendering various types of circles or circle sectors.
pub struct Circle {
center: Point,
radius: i16,
fg_color: Option<Color>,
bg_color: Option<Color>,
thickness: i16,
start_angle: Option<i16>,
end_angle: Option<i16>,
}
impl Circle {
pub fn new(center: Point, radius: i16) -> Self {
Self {
center,
radius,
fg_color: None,
bg_color: None,
thickness: 1,
start_angle: None,
end_angle: None,
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: Some(fg_color),
..self
}
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn with_thickness(self, thickness: i16) -> Self {
Self { thickness, ..self }
}
pub fn with_start_angle(self, from_angle: i16) -> Self {
Self {
start_angle: Some(from_angle),
..self
}
}
pub fn with_end_angle(self, to_angle: i16) -> Self {
Self {
end_angle: Some(to_angle),
..self
}
}
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
renderer.render_shape(self);
}
}
impl Shape<'_> for Circle {
fn bounds(&self, _cache: &DrawingCache) -> Rect {
let c = self.center;
let r = self.radius;
Rect::new(
Point::new(c.x - r, c.y - r),
Point::new(c.x + r + 1, c.y + r + 1),
)
}
fn cleanup(&mut self, _cache: &DrawingCache) {}
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
// NOTE: drawing of circles without a background and with a thickness > 1
// is not supported. If we needed it, we would have to
// introduce RgbCanvas::draw_ring() function.
// TODO: panic! in unsupported scenarious
let th = match self.fg_color {
Some(_) => self.thickness,
None => 0,
};
if self.start_angle.is_none() && self.end_angle.is_none() {
if th == 1 {
if let Some(color) = self.bg_color {
canvas.fill_circle(self.center, self.radius, color);
}
if let Some(color) = self.fg_color {
#[cfg(not(feature = "ui_antialiasing"))]
canvas.draw_circle(self.center, self.radius, color);
#[cfg(feature = "ui_antialiasing")]
canvas.fill_circle(self.center, self.radius, color);
}
} else {
if let Some(color) = self.fg_color {
if th > 0 {
canvas.fill_circle(self.center, self.radius, color);
}
}
if let Some(color) = self.bg_color {
canvas.fill_circle(self.center, self.radius - th, color);
}
}
} else {
let start = self.start_angle.unwrap_or(0);
let end = self.end_angle.unwrap_or(360);
if let Some(color) = self.fg_color {
if th > 0 {
canvas.fill_sector(self.center, self.radius, start, end, color);
}
}
if let Some(color) = self.bg_color {
canvas.fill_sector(self.center, self.radius - th, start, end, color);
}
}
}
}
impl<'s> ShapeClone<'s> for Circle {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<Circle>()?;
Some(clone.uninit.init(Circle { ..self }))
}
}

@ -0,0 +1,12 @@
use crate::ui::{
display::Color,
geometry::Rect,
shape::{DirectRenderer, Mono8Canvas},
};
pub fn render_on_display<'a, F>(_clip: Option<Rect>, _bg_color: Option<Color>, _func: F)
where
F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>),
{
panic!("Not implemented")
}

@ -0,0 +1,45 @@
use crate::ui::{
display::Color,
geometry::{Offset, Rect},
shape::{BasicCanvas, DirectRenderer, DrawingCache, Mono8Canvas, Viewport},
};
use crate::trezorhal::display;
use static_alloc::Bump;
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
where
F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>),
{
static mut BUMP: Bump<[u8; 40 * 1024]> = Bump::uninit();
let bump = unsafe { &mut *core::ptr::addr_of_mut!(BUMP) };
{
let width = display::DISPLAY_RESX as i16;
let height = display::DISPLAY_RESY as i16;
bump.reset();
let cache = DrawingCache::new(bump, bump);
let fb = unsafe {
core::slice::from_raw_parts_mut(
display::get_frame_addr() as *mut u8,
width as usize * height as usize * core::mem::size_of::<u8>(),
)
};
let mut canvas = unwrap!(Mono8Canvas::new(Offset::new(width, height), None, fb));
if let Some(clip) = clip {
canvas.set_viewport(Viewport::new(clip));
}
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
func(&mut target);
display::refresh();
}
}

@ -0,0 +1,51 @@
use crate::ui::{
display::Color,
geometry::{Offset, Rect},
shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport},
};
use crate::trezorhal::display;
use static_alloc::Bump;
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
where
F: FnOnce(&mut DirectRenderer<'_, 'a, Rgb565Canvas<'a>>),
{
#[link_section = ".no_dma_buffers"]
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
#[link_section = ".buf"]
static mut BUMP_B: Bump<[u8; 16 * 1024]> = Bump::uninit();
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
{
let width = display::DISPLAY_RESX as i16;
let height = display::DISPLAY_RESY as i16;
bump_a.reset();
bump_b.reset();
let cache = DrawingCache::new(bump_a, bump_b);
let fb = unsafe {
core::slice::from_raw_parts_mut(
display::get_frame_addr() as *mut u8,
width as usize * height as usize * core::mem::size_of::<u16>(),
)
};
let mut canvas = unwrap!(Rgb565Canvas::new(Offset::new(width, height), None, fb));
if let Some(clip) = clip {
canvas.set_viewport(Viewport::new(clip));
}
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
func(&mut target);
display::refresh();
}
}

@ -0,0 +1,51 @@
use crate::ui::{
display::Color,
geometry::{Offset, Rect},
shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport},
};
use static_alloc::Bump;
use crate::trezorhal::display;
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
where
F: FnOnce(&mut DirectRenderer<'_, 'a, Rgba8888Canvas<'a>>),
{
#[link_section = ".no_dma_buffers"]
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
#[link_section = ".buf"]
static mut BUMP_B: Bump<[u8; 16 * 1024]> = Bump::uninit();
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
{
let width = display::DISPLAY_RESX as i16;
let height = display::DISPLAY_RESY as i16;
bump_a.reset();
bump_b.reset();
let cache = DrawingCache::new(bump_a, bump_b);
let fb = unsafe {
core::slice::from_raw_parts_mut(
display::get_frame_addr() as *mut u8,
width as usize * height as usize * core::mem::size_of::<u32>(),
)
};
let mut canvas = unwrap!(Rgba8888Canvas::new(Offset::new(width, height), None, fb));
if let Some(clip) = clip {
canvas.set_viewport(Viewport::new(clip));
}
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
func(&mut target);
display::refresh();
}
}

@ -0,0 +1,58 @@
## Memory usage comparison
## Legacy solution
**Memory with DMA access**
```
buffer_line_16bpp @.buf 1440
buffer_line_4bpp @.buf 360
buffer_text @.buf 4320
-------------------------------------------------
6120
```
**Memory without DMA access**
```
buffer_jpeg @.no_dma 7680
buffer_jpeg_work @.no_dma 10500
buffer_blurring @.no_dma 14400
buffer_blurring_totals @.no_dma 1440
zlib context+window @.stack 2308
-------------------------------------------------
36328
```
## New drawing library
The memory usage is configurable, so the two options are considered.\
MIN variant is slower, but consumes less memory. OPT variant should
be sufficient for all purposes.
**Memory with DMA access**
```
MIN OPT
ProgressiveRenderer.slice @.buf 480 7680
ProgressiveRenderer.scratch @.buf 480 2048
---------------------------------------------------------
960 9728
```
**Memory without DMA access**
```
ProgressiveRenderer.list @.stack 512 2048
zlib decompression context @.no_dma 2308 6924
jpeg decompressor @.no_dma 10500 10500
partial jpeg image @.no_dma 7680 7680
blurring window/totals @.no_dma 7920 7920
------------------------------------------------------------------
28920 35072
```

@ -0,0 +1,24 @@
#[cfg(all(feature = "xframebuffer", feature = "display_mono"))]
pub mod fb_mono8;
#[cfg(all(feature = "xframebuffer", feature = "display_mono"))]
pub use fb_mono8::render_on_display;
#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))]
pub mod nofb_rgb565;
#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))]
pub use nofb_rgb565::render_on_display;
#[cfg(all(feature = "xframebuffer", feature = "display_rgb565"))]
pub mod fb_rgb565;
#[cfg(all(feature = "xframebuffer", feature = "display_rgb565"))]
pub use fb_rgb565::render_on_display;
#[cfg(all(feature = "xframebuffer", feature = "display_rgba8888"))]
pub mod fb_rgba8888;
#[cfg(all(feature = "xframebuffer", feature = "display_rgba8888"))]
pub use fb_rgba8888::render_on_display;
#[cfg(not(feature = "new_rendering"))]
pub mod fake_display;
#[cfg(not(feature = "new_rendering"))]
pub use fake_display::render_on_display;

@ -0,0 +1,88 @@
use crate::trezorhal::dma2d_new::Dma2d;
use crate::ui::{
display::Color,
geometry::{Offset, Rect},
};
use super::super::{
BasicCanvas, BitmapFormat, BitmapView, DrawingCache, ProgressiveRenderer, Viewport,
};
use static_alloc::Bump;
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
where
F: FnOnce(&mut ProgressiveRenderer<'_, 'a, Bump<[u8; 40 * 1024]>, DisplayCanvas>),
{
#[link_section = ".no_dma_buffers"]
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
#[link_section = ".buf"]
static mut BUMP_B: Bump<[u8; 16 * 1024]> = Bump::uninit();
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
{
bump_a.reset();
bump_b.reset();
let cache = DrawingCache::new(bump_a, bump_b);
let mut canvas = DisplayCanvas::acquire().unwrap();
if let Some(clip) = clip {
canvas.set_viewport(Viewport::new(clip));
}
let mut target = ProgressiveRenderer::new(&mut canvas, bg_color, &cache, bump_a, 45);
func(&mut target);
target.render(16);
}
}
pub struct DisplayCanvas {
size: Offset,
viewport: Viewport,
}
impl DisplayCanvas {
pub fn acquire() -> Option<Self> {
let size = Offset::new(240, 240); // TODO
let viewport = Viewport::from_size(size);
Some(Self { size, viewport })
}
}
impl BasicCanvas for DisplayCanvas {
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn size(&self) -> Offset {
self.size
}
fn fill_rect(&mut self, r: Rect, color: Color, _alpha: u8) {
let r = r.translate(self.viewport.origin);
if let Some(dma2d) = Dma2d::new_fill(r, self.viewport.clip, color, 255) {
unsafe { dma2d.display_fill() };
}
}
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
let r = r.translate(self.viewport.origin);
if let Some(dma2d) = Dma2d::new_copy(r, self.viewport.clip, &bitmap) {
match bitmap.format() {
BitmapFormat::RGB565 => unsafe { dma2d.display_copy_rgb565() },
_ => panic!("Unsupported DMA operation"),
}
bitmap.bitmap.mark_dma_pending();
}
}
}

@ -0,0 +1,196 @@
use crate::ui::geometry::{Alignment2D, Offset, Point, Rect};
use super::{Bitmap, BitmapFormat, BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
/// A shape for rendering compressed JPEG images.
pub struct JpegImage<'a> {
/// Image position
pos: Point,
// Image position alignment
align: Alignment2D,
/// JPEG data
jpeg: &'a [u8],
/// Scale factor (default 0)
scale: u8,
/// Blurring radius or 0 if no blurring required (default 0)
blur_radius: usize,
/// Dimming of blurred image in range of 0..255 (default 255)
dim: u8,
/// Set if blurring is pending
/// (used only during image drawing).
blur_tag: Option<u32>,
}
impl<'a> JpegImage<'a> {
pub fn new(pos: Point, jpeg: &'a [u8]) -> Self {
JpegImage {
pos,
align: Alignment2D::TOP_LEFT,
scale: 0,
dim: 255,
blur_radius: 0,
jpeg,
blur_tag: None,
}
}
pub fn with_align(self, align: Alignment2D) -> Self {
Self { align, ..self }
}
pub fn with_scale(self, scale: u8) -> Self {
assert!(scale <= 3);
Self { scale, ..self }
}
pub fn with_blur(self, blur_radius: usize) -> Self {
Self {
blur_radius,
..self
}
}
pub fn with_dim(self, dim: u8) -> Self {
Self { dim, ..self }
}
pub fn render(self, renderer: &mut impl Renderer<'a>) {
renderer.render_shape(self);
}
}
impl<'a> Shape<'a> for JpegImage<'a> {
fn bounds(&self, cache: &DrawingCache<'a>) -> Rect {
let size = unwrap!(cache.jpeg().get_size(self.jpeg, self.scale), "Invalid JPEG");
Rect::from_top_left_and_size(size.snap(self.pos, self.align), size)
}
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {
self.blur_tag = None;
}
/*
// Faster implementation suitable for DirectRenderer without blurring support
// (but is terribly slow on ProgressiveRenderer if slices are not aligned
// to JPEG MCUs )
fn draw(&mut self, canvas: &mut dyn RgbCanvasEx, cache: &DrawingCache<'a>) {
let bounds = self.bounds(cache);
let clip = canvas.viewport().relative_clip(bounds).clip;
// translate clip to JPEG relative coordinates
let clip = clip.translate(-canvas.viewport().origin);
let clip = clip.translate((-bounds.top_left()).into());
unwrap!(
cache.jpeg().decompress_mcu(
self.jpeg,
self.scale,
clip.top_left(),
&mut |mcu_r, mcu_bitmap| {
// Draw single MCU
canvas.draw_bitmap(mcu_r.translate(bounds.top_left().into()), mcu_bitmap);
// Return true if we are not done yet
mcu_r.x1 < clip.x1 || mcu_r.y1 < clip.y1
}
),
"Invalid JPEG"
);
}*/
// This is a little bit slower implementation suitable for ProgressiveRenderer
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
let bounds = self.bounds(cache);
let clip = canvas.viewport().relative_clip(bounds).clip;
// Translate clip to JPEG relative coordinates
let clip = clip.translate(-canvas.viewport().origin);
let clip = clip.translate((-bounds.top_left()).into());
if self.blur_radius == 0 {
// Draw JPEG without blurring
// Routine for drawing single JPEG MCU
let draw_mcu = &mut |row_r: Rect, row_bitmap: BitmapView| {
// Draw a row of decoded MCUs
canvas.draw_bitmap(row_r.translate(bounds.top_left().into()), row_bitmap);
// Return true if we are not done yet
row_r.y1 < clip.y1
};
unwrap!(
cache
.jpeg()
.decompress_row(self.jpeg, self.scale, clip.y0, draw_mcu),
"Invalid JPEG"
);
} else {
// Draw JPEG with blurring effect
let jpeg_size = self.bounds(cache).size();
// Get a single line working bitmap
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
let mut slice = unwrap!(
Bitmap::new(
BitmapFormat::RGB565,
None,
Offset::new(jpeg_size.x, 1),
None,
&mut buff[..]
),
"Too small buffer"
);
// Get the blurring algorithm instance
let mut blur_cache = cache.blur();
let (blur, blur_tag) =
unwrap!(blur_cache.get(jpeg_size, self.blur_radius, self.blur_tag));
self.blur_tag = Some(blur_tag);
if let Some(y) = blur.push_ready() {
// A function for drawing a row of JPEG MCUs
let draw_row = &mut |row_r: Rect, jpeg_slice: BitmapView| {
loop {
if let Some(y) = blur.push_ready() {
if y < row_r.y1 {
// should never fail
blur.push(unwrap!(jpeg_slice.row(y - row_r.y0)));
} else {
return true; // need more data
}
}
if let Some(y) = blur.pop_ready() {
blur.pop(unwrap!(slice.row_mut(0)), Some(self.dim)); // should never fail
let dst_r = Rect::from_top_left_and_size(bounds.top_left(), jpeg_size)
.translate(Offset::new(0, y));
canvas.draw_bitmap(dst_r, slice.view());
if y + 1 >= clip.y1 {
return false; // we are done
}
}
}
};
unwrap!(
cache
.jpeg()
.decompress_row(self.jpeg, self.scale, y, draw_row),
"Invalid JPEG"
);
}
}
}
}
impl<'a> ShapeClone<'a> for JpegImage<'a> {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'a>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<JpegImage>()?;
Some(clone.uninit.init(JpegImage { ..self }))
}
}

@ -0,0 +1,33 @@
mod algo;
mod bar;
mod base;
mod bitmap;
#[cfg(feature = "ui_blurring")]
mod blur;
mod cache;
mod canvas;
mod circle;
mod display;
#[cfg(feature = "ui_jpeg_decoder")]
mod jpeg;
mod qrcode;
mod render;
mod text;
mod toif;
pub use algo::PI4;
pub use bar::Bar;
pub use base::{Shape, ShapeClone};
pub use bitmap::{Bitmap, BitmapFormat, BitmapView};
#[cfg(feature = "ui_blurring")]
pub use blur::Blurring;
pub use cache::drawing_cache::DrawingCache;
pub use canvas::{BasicCanvas, Canvas, Mono8Canvas, Rgb565Canvas, Rgba8888Canvas, Viewport};
pub use circle::Circle;
pub use display::render_on_display;
#[cfg(feature = "ui_jpeg_decoder")]
pub use jpeg::JpegImage;
pub use qrcode::QrImage;
pub use render::{DirectRenderer, ProgressiveRenderer, Renderer};
pub use text::Text;
pub use toif::ToifImage;

@ -0,0 +1,169 @@
use crate::ui::{
display::Color,
geometry::{Offset, Rect},
};
use qrcodegen::QrCode;
use super::{
algo::line_points, Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone,
};
use without_alloc::alloc::LocalAllocLeakExt;
const MAX_QRCODE_BYTES: usize = 400;
/// A shape for `QrCode` rendering.
pub struct QrImage {
/// Destination rectangle
area: Rect,
/// QR code bitmap
qr_modules: [u8; MAX_QRCODE_BYTES],
/// Size of QR code bitmap in bytes
qr_size: i16,
/// Foreground color
fg_color: Color,
/// Optional background color
bg_color: Option<Color>,
}
impl QrImage {
pub fn new(area: Rect, qrcode: &QrCode) -> Self {
if area.width() < qrcode.size() as i16 || area.height() < qrcode.size() as i16 {
panic!("Too small area");
}
let mut result = QrImage {
area,
qr_size: qrcode.size() as i16,
qr_modules: [0u8; MAX_QRCODE_BYTES],
fg_color: Color::white(),
bg_color: None,
};
// Copy content of QR code to the qrmodules buffer
for y in 0..result.qr_size {
for x in 0..result.qr_size {
result.set_module(x, y, qrcode.get_module(x as i32, y as i32));
}
}
result
}
fn set_module(&mut self, x: i16, y: i16, value: bool) {
// Every row starts at byte aligned address
let row_offset = (y * (self.qr_size + 7) / 8) as usize;
let row = &mut self.qr_modules[row_offset..];
let col_offset = (x / 8) as usize;
let col_bit = 1 << (x & 0x7);
if value {
row[col_offset] |= col_bit;
} else {
row[col_offset] &= col_bit ^ 0xFF;
}
}
fn get_module(&self, x: i16, y: i16) -> bool {
// Every row starts at byte aligned address
let row_offset = (y * (self.qr_size + 7) / 8) as usize;
let row = &self.qr_modules[row_offset..];
let col_offset = (x / 8) as usize;
let col_bit = 1 << (x & 0x7);
(row[col_offset] & col_bit) != 0
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self { fg_color, ..self }
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
renderer.render_shape(self);
}
fn draw_row(&self, slice_row: &mut [u8], qr_y: i16) {
slice_row.iter_mut().for_each(|b| *b = 0);
let mut qr_module = false;
for p in line_points(self.area.x1 - self.area.x0, self.qr_size, 0) {
if p.first {
qr_module = self.get_module(p.v, qr_y);
}
if !qr_module {
if p.u & 0x01 == 0 {
slice_row[(p.u / 2) as usize] |= 0x0F;
} else {
slice_row[(p.u / 2) as usize] |= 0xF0;
}
}
}
}
}
impl Shape<'_> for QrImage {
fn bounds(&self, _cache: &DrawingCache) -> Rect {
self.area
}
fn cleanup(&mut self, _cache: &DrawingCache) {}
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
let buff = &mut unwrap!(cache.image_buff(), "No TOIF buffer");
let mut slice = unwrap!(
Bitmap::new_mut(
BitmapFormat::MONO4,
None,
Offset::new(self.area.width(), 1),
Some(1),
&mut buff[..]
),
"Too small buffer"
);
let clip = canvas.viewport().relative_clip(self.bounds(cache)).clip;
// translate clip to the relative coordinates
let clip = clip.translate(-canvas.viewport().origin);
let clip = clip.translate((-self.area.top_left()).into());
for p in line_points(self.area.y1 - self.area.y0, self.qr_size, clip.y0)
.take(clip.height() as usize)
{
if p.first {
self.draw_row(slice.row_mut(0).unwrap(), p.v);
}
let r = Rect {
y0: self.area.y0 + p.u,
y1: self.area.y0 + p.u + 1,
..self.area
};
let slice_view = slice.view().with_fg(self.fg_color);
match self.bg_color {
Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)),
None => canvas.blend_bitmap(r, slice_view),
}
}
}
}
impl<'s> ShapeClone<'s> for QrImage {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<QrImage>()?;
Some(clone.uninit.init(QrImage { ..self }))
}
}

@ -0,0 +1,249 @@
use crate::ui::{
display::Color,
geometry::{Offset, Point, Rect},
};
use super::{BasicCanvas, Canvas, DrawingCache, Rgb565Canvas, Shape, ShapeClone, Viewport};
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
// ==========================================================================
// trait Renderer
// ==========================================================================
/// All renders must implement Renderer trait
/// Renderers can immediately use the draw() method of the passed shape or
/// may store it (using the boxed() method) and draw it later
pub trait Renderer<'a> {
fn viewport(&self) -> Viewport;
fn set_viewport(&mut self, viewport: Viewport);
fn set_window(&mut self, window: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_window(window));
viewport
}
fn set_clip(&mut self, clip: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_clip(clip));
viewport
}
fn render_shape<S>(&mut self, shape: S)
where
S: Shape<'a> + ShapeClone<'a>;
fn in_window(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) {
let original = self.set_window(r);
inner(self);
self.set_viewport(original);
}
fn in_clip(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) {
let original = self.set_clip(r);
inner(self);
self.set_viewport(original);
}
fn with_origin(&mut self, origin: Offset, inner: &dyn Fn(&mut Self)) {
let original = self.viewport();
self.set_viewport(self.viewport().with_origin(origin));
inner(self);
self.set_viewport(original);
}
}
// ==========================================================================
// struct DirectRenderer
// ==========================================================================
/// A simple implementation of a Renderer that draws directly onto the CanvasEx
pub struct DirectRenderer<'a, 'alloc, C>
where
C: Canvas,
{
/// Target canvas
canvas: &'a mut C,
/// Drawing cache (decompression context, scratch-pad memory)
cache: &'a DrawingCache<'alloc>,
}
impl<'a, 'alloc, C> DirectRenderer<'a, 'alloc, C>
where
C: Canvas,
{
/// Creates a new DirectRenderer instance with the given canvas
pub fn new(
canvas: &'a mut C,
bg_color: Option<Color>,
cache: &'a DrawingCache<'alloc>,
) -> Self {
if let Some(color) = bg_color {
canvas.fill_background(color);
}
// TODO: consider storing original canvas.viewport
// and restoring it by drop() function
Self { canvas, cache }
}
}
impl<'a, 'alloc, C> Renderer<'alloc> for DirectRenderer<'a, 'alloc, C>
where
C: Canvas,
{
fn viewport(&self) -> Viewport {
self.canvas.viewport()
}
fn set_viewport(&mut self, viewport: Viewport) {
self.canvas.set_viewport(viewport);
}
fn render_shape<S>(&mut self, mut shape: S)
where
S: Shape<'alloc> + ShapeClone<'alloc>,
{
if self.canvas.viewport().contains(shape.bounds(self.cache)) {
shape.draw(self.canvas, self.cache);
shape.cleanup(self.cache);
}
}
}
// ==========================================================================
// struct ProgressiveRenderer
// ==========================================================================
struct ShapeHolder<'a> {
shape: &'a mut dyn Shape<'a>,
viewport: Viewport,
}
/// A more advanced Renderer implementation that supports deferred rendering.
pub struct ProgressiveRenderer<'a, 'alloc, T, C>
where
T: LocalAllocLeakExt<'alloc>,
C: BasicCanvas,
{
/// Target canvas
canvas: &'a mut C,
/// Bump for cloning shapes
bump: &'alloc T,
/// List of rendered shapes
shapes: FixedVec<'alloc, ShapeHolder<'alloc>>,
/// Current viewport
viewport: Viewport,
// Default background color
bg_color: Option<Color>,
/// Drawing cache (decompression context, scratch-pad memory)
cache: &'a DrawingCache<'alloc>,
}
impl<'a, 'alloc, T, C> ProgressiveRenderer<'a, 'alloc, T, C>
where
T: LocalAllocLeakExt<'alloc>,
C: BasicCanvas,
{
/// Creates a new ProgressiveRenderer instance
pub fn new(
canvas: &'a mut C,
bg_color: Option<Color>,
cache: &'a DrawingCache<'alloc>,
bump: &'alloc T,
max_shapes: usize,
) -> Self {
let viewport = canvas.viewport();
Self {
canvas,
bump,
shapes: unwrap!(bump.fixed_vec(max_shapes), "No shape memory"),
viewport,
bg_color,
cache,
}
}
/// Renders stored shapes onto the specified canvas
pub fn render(&mut self, lines: usize) {
let canvas_clip = self.canvas.viewport().clip;
let canvas_origin = self.canvas.viewport().origin;
let buff = &mut unwrap!(self.cache.render_buff(), "No render buffer");
let mut slice = unwrap!(
Rgb565Canvas::new(
Offset::new(canvas_clip.width(), lines as i16),
Some(1),
&mut buff[..],
),
"No render memory"
);
for y in (canvas_clip.y0..canvas_clip.y1).step_by(lines) {
// Calculate the coordinates of the slice we will draw into
let slice_r = Rect::new(
// slice_r is in absolute coordinates
Point::new(canvas_clip.x0, y),
Point::new(canvas_clip.x1, y + lines as i16),
)
.translate(-canvas_origin);
// Clear the slice background
if let Some(color) = self.bg_color {
slice.set_viewport(Viewport::from_size(slice_r.size()));
slice.fill_background(color);
}
// Draw all shapes that overlaps the slice
for holder in self.shapes.iter_mut() {
let shape_viewport = holder.viewport.absolute_clip(slice_r);
let shape_bounds = holder.shape.bounds(self.cache);
// Is the shape overlapping the current slice?
if shape_viewport.contains(shape_bounds) {
slice.set_viewport(shape_viewport.translate((-slice_r.top_left()).into()));
holder.shape.draw(&mut slice, self.cache);
if shape_bounds.y1 + shape_viewport.origin.y <= shape_viewport.clip.y1 {
// The shape will never be drawn again
holder.shape.cleanup(self.cache);
}
}
}
self.canvas.draw_bitmap(slice_r, slice.view());
}
}
}
impl<'a, 'alloc, T, C> Renderer<'alloc> for ProgressiveRenderer<'a, 'alloc, T, C>
where
T: LocalAllocLeakExt<'alloc>,
C: BasicCanvas,
{
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.canvas.bounds());
}
fn render_shape<S>(&mut self, shape: S)
where
S: Shape<'alloc> + ShapeClone<'alloc>,
{
// Is the shape visible?
if self.viewport.contains(shape.bounds(self.cache)) {
// Clone the shape & push it to the list
let holder = ShapeHolder {
shape: unwrap!(shape.clone_at_bump(self.bump), "No shape memory"),
viewport: self.viewport,
};
unwrap!(self.shapes.push(holder), "Shape list full");
}
}
}

@ -0,0 +1,134 @@
use crate::ui::{
display::{Color, Font},
geometry::{Alignment, Offset, Point, Rect},
};
use super::{BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
/// A shape for text strings rendering.
pub struct Text<'a> {
// Text position
pos: Point,
// Text string
text: &'a str,
// Text color
color: Color,
// Text font
font: Font,
// Horizontal alignment
align: Alignment,
// Final bounds calculated when rendered
bounds: Rect,
}
impl<'a> Text<'a> {
/// Creates a `shape::Text` structure with a specified
/// text (`str`) and the top-left corner (`pos`).
pub fn new(pos: Point, text: &'a str) -> Self {
Self {
pos,
text,
color: Color::white(),
font: Font::NORMAL,
align: Alignment::Start,
bounds: Rect::zero(),
}
}
pub fn with_fg(self, color: Color) -> Self {
Self { color, ..self }
}
pub fn with_font(self, font: Font) -> Self {
Self { font, ..self }
}
pub fn with_align(self, align: Alignment) -> Self {
Self { align, ..self }
}
pub fn render<'r>(mut self, renderer: &mut impl Renderer<'r>) {
self.bounds = self.calc_bounds();
renderer.render_shape(self);
}
fn aligned_pos(&self) -> Point {
match self.align {
Alignment::Start => self.pos,
Alignment::Center => Point::new(
self.font.horz_center(self.pos.x, self.pos.x, self.text),
self.pos.y,
),
Alignment::End => Point::new(self.pos.x - self.font.text_width(self.text), self.pos.y),
}
}
fn calc_bounds(&self) -> Rect {
let pos = self.aligned_pos();
let (ascent, descent) = self.font.visible_text_height_ex(self.text);
Rect {
x0: pos.x,
y0: pos.y - ascent,
x1: pos.x + self.font.text_width(self.text),
y1: pos.y + descent,
}
}
}
impl<'a> Shape<'_> for Text<'a> {
fn bounds(&self, _cache: &DrawingCache) -> Rect {
self.bounds
}
fn cleanup(&mut self, _cache: &DrawingCache) {}
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
let mut r = self.bounds(cache);
let max_ascent = self.pos.y - r.y0;
// TODO: optimize text clipping, use canvas.viewport()
for ch in self.text.chars() {
if r.x0 >= r.x1 {
break;
}
let glyph = self.font.get_glyph(ch);
let glyph_bitmap = glyph.bitmap();
let glyph_view = BitmapView::new(&glyph_bitmap)
.with_fg(self.color)
.with_offset(Offset::new(
-glyph.bearing_x,
-(max_ascent - glyph.bearing_y),
));
canvas.blend_bitmap(r, glyph_view);
r.x0 += glyph.adv;
}
}
}
impl<'a, 's> ShapeClone<'s> for Text<'a> {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<Text>()?;
let text = bump.copy_str(self.text)?;
Some(clone.uninit.init(Text { text, ..self }))
}
}
impl Font {
fn visible_text_height_ex(&self, text: &str) -> (i16, i16) {
let (mut ascent, mut descent) = (0, 0);
for c in text.chars() {
let glyph = self.get_glyph(c);
ascent = ascent.max(glyph.bearing_y);
descent = descent.max(glyph.height - glyph.bearing_y);
}
(ascent, descent)
}
}

@ -0,0 +1,174 @@
use crate::ui::{
display::{toif::Toif, Color},
geometry::{Alignment2D, Offset, Point, Rect},
};
use super::{Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
/// A shape for rendering compressed TOIF images.
pub struct ToifImage<'a> {
/// Image position
pos: Point,
// Image position alignment
align: Alignment2D,
// Image data
toif: Toif<'a>,
// Foreground color
fg_color: Color,
// Optional background color
bg_color: Option<Color>,
}
impl<'a> ToifImage<'a> {
pub fn new(pos: Point, toif: Toif<'a>) -> Self {
Self {
pos,
align: Alignment2D::TOP_LEFT,
toif,
fg_color: Color::white(),
bg_color: None,
}
}
pub fn with_align(self, align: Alignment2D) -> Self {
Self { align, ..self }
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self { fg_color, ..self }
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn render(self, renderer: &mut impl Renderer<'a>) {
renderer.render_shape(self);
}
fn draw_grayscale(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
// TODO: introduce new viewport/shape function for this calculation
let bounds = self.bounds(cache);
let viewport = canvas.viewport();
let mut clip = self
.bounds(cache)
.clamp(viewport.clip.translate(-viewport.origin))
.translate((-bounds.top_left()).into());
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
let mut slice = unwrap!(
Bitmap::new_mut(
BitmapFormat::MONO4,
None,
self.toif.size(),
Some(1),
&mut buff[..]
),
"Too small buffer"
);
while !clip.is_empty() {
let height = core::cmp::min(slice.height(), clip.height());
unwrap!(
cache.zlib().uncompress_toif(
self.toif,
clip.y0,
unwrap!(slice.rows_mut(0, height)), // should never fail
),
"Invalid TOIF"
);
let r = clip.translate(bounds.top_left().into());
let slice_view = slice
.view()
.with_fg(self.fg_color)
.with_offset(Offset::new(r.x0 - bounds.top_left().x, 0));
match self.bg_color {
Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)),
None => canvas.blend_bitmap(r, slice_view),
}
clip.y0 += height;
}
}
fn draw_rgb(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
// TODO: introduce new viewport/shape function for this calculation
let bounds = self.bounds(cache);
let viewport = canvas.viewport();
let mut clip = self
.bounds(cache)
.clamp(viewport.clip.translate(-viewport.origin))
.translate((-bounds.top_left()).into());
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
let mut slice = unwrap!(
Bitmap::new_mut(
BitmapFormat::RGB565,
None,
self.toif.size(),
Some(1),
&mut buff[..]
),
"Too small buffer"
);
while !clip.is_empty() {
let height = core::cmp::min(slice.height(), clip.height());
if let Some(row_bytes) = slice.rows_mut(0, height) {
// always true
unwrap!(
cache.zlib().uncompress_toif(self.toif, clip.y0, row_bytes,),
"Invalid TOIF"
);
}
let r = clip.translate(bounds.top_left().into());
let slice_view = slice
.view()
.with_offset(Offset::new(r.x0 - bounds.top_left().x, 0));
canvas.draw_bitmap(r, slice_view);
clip.y0 += height;
}
}
}
impl<'a> Shape<'a> for ToifImage<'a> {
fn bounds(&self, _cache: &DrawingCache<'a>) -> Rect {
let size = Offset::new(self.toif.width(), self.toif.height());
Rect::from_top_left_and_size(size.snap(self.pos, self.align), size)
}
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {
// TODO: inform the cache that we won't use the zlib slot anymore
}
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
if self.toif.is_grayscale() {
self.draw_grayscale(canvas, cache);
} else {
self.draw_rgb(canvas, cache);
}
}
}
impl<'a> ShapeClone<'a> for ToifImage<'a> {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'a>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<ToifImage>()?;
Some(clone.uninit.init(ToifImage { ..self }))
}
}

@ -1,11 +1,14 @@
#include TREZOR_BOARD
#include "buffers.h"
#include "button.h"
#include "common.h"
#include "display.h"
#include "display_draw.h"
#include "dma2d.h"
#include "flash.h"
#include "fonts/fonts.h"
#include "gl_dma2d.h"
#include "haptic.h"
#include "model.h"
#include "rgb_led.h"

@ -3,18 +3,16 @@
#define HSE_8MHZ
#define MAX_DISPLAY_RESX 240
#define MAX_DISPLAY_RESY 320
#define DISPLAY_RESX 240
#define DISPLAY_RESY 320
#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565
#define DISPLAY_LEGACY_HEADER "displays/ltdc.h"
#define USE_I2C 1
#define USE_TOUCH 1
#define USE_SDRAM 1
#define USE_RGB_COLORS 1
#include "displays/ltdc.h"
#define I2C_COUNT 1
#define I2C_INSTANCE_0 I2C3
#define I2C_INSTANCE_0_CLK_EN __HAL_RCC_I2C3_CLK_ENABLE

@ -11,7 +11,10 @@
//#define USE_DISP_I8080_8BIT_DW 1
#define USE_HASH_PROCESSOR 1
#include "displays/dsi.h"
#define DISPLAY_RESX 480
#define DISPLAY_RESY 480
#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_ARGB8888
#define DISPLAY_LEGACY_HEADER "displays/dsi.h"
#define I2C_COUNT 1
#define I2C_INSTANCE_0 I2C5

@ -5,7 +5,8 @@
#define USE_BUTTON 1
#include "displays/vg-2864ksweg01.h"
#define DISPLAY_RESX 128
#define DISPLAY_RESY 64
#define BTN_LEFT_PIN GPIO_PIN_5
#define BTN_LEFT_PORT GPIOC

@ -8,7 +8,9 @@
#define USE_I2C 1
#define USE_CONSUMPTION_MASK 1
#include "displays/vg-2864ksweg01.h"
#define DISPLAY_RESX 128
#define DISPLAY_RESY 64
#define DISPLAY_LEGACY_HEADER "displays/vg-2864ksweg01.h"
#define BTN_LEFT_PIN GPIO_PIN_10
#define BTN_LEFT_PORT GPIOC

@ -6,7 +6,9 @@
#define USE_BUTTON 1
#define USE_SBU 1
#include "displays/ug-2828tswig01.h"
#define DISPLAY_RESX 128
#define DISPLAY_RESY 128
#define DISPLAY_LEGACY_HEADER "displays/ug-2828tswig01.h"
#define BTN_LEFT_PIN GPIO_PIN_0
#define BTN_LEFT_PORT GPIOA

@ -6,7 +6,9 @@
#define USE_BUTTON 1
#define USE_SBU 1
#include "displays/vg-2864ksweg01.h"
#define DISPLAY_RESX 128
#define DISPLAY_RESY 64
#define DISPLAY_LEGACY_HEADER "displays/vg-2864ksweg01.h"
#define BTN_LEFT_PIN GPIO_PIN_5
#define BTN_LEFT_PORT GPIOC

@ -6,7 +6,9 @@
#define USE_BUTTON 1
#define USE_SBU 1
#include "displays/vg-2864ksweg01.h"
#define DISPLAY_RESX 128
#define DISPLAY_RESY 64
#define DISPLAY_LEGACY_HEADER "displays/vg-2864ksweg01.h"
#define BTN_LEFT_PIN GPIO_PIN_10
#define BTN_LEFT_PORT GPIOC

@ -3,9 +3,6 @@
#define HSE_8MHZ
#define DISPLAY_RESX 240
#define DISPLAY_RESY 240
#define USE_SD_CARD 1
#define USE_I2C 1
#define USE_TOUCH 1
@ -14,12 +11,14 @@
#define USE_BACKLIGHT 1
#define USE_DISP_I8080_8BIT_DW 1
#include "displays/panels/lx154a2422.h"
#include "displays/st7789v.h"
#define DISPLAY_RESX 240
#define DISPLAY_RESY 240
#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565
#define DISPLAY_LEGACY_HEADER "displays/st7789v.h"
#define DISPLAY_IDENTIFY 1
#define DISPLAY_TE_PORT GPIOD
#define DISPLAY_TE_PIN GPIO_PIN_12
#define TRANSFORM_TOUCH_COORDS lx154a2422_transform_touch_coords
#define BACKLIGHT_PWM_FREQ 10000
#define BACKLIGHT_PWM_TIM TIM1

@ -1,9 +1,6 @@
#ifndef _TREZOR_T3T1_H
#define _TREZOR_T3T1_H
#define DISPLAY_RESX 240
#define DISPLAY_RESY 240
#define VDD_1V8 1
#define USE_SD_CARD 1
@ -16,8 +13,11 @@
#define USE_BACKLIGHT 1
#define USE_HASH_PROCESSOR 1
#include "displays/panels/lx154a2422.h"
#include "displays/st7789v.h"
#define DISPLAY_RESX 240
#define DISPLAY_RESY 240
#define DISPLAY_LEGACY_HEADER "displays/st7789v.h"
#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565
#define DISPLAY_IDENTIFY 1
#define DISPLAY_TE_PORT GPIOD
#define DISPLAY_TE_PIN GPIO_PIN_12
@ -26,10 +26,6 @@
#define DISPLAY_TE_INTERRUPT_GPIOSEL EXTI_GPIOD
#define DISPLAY_TE_INTERRUPT_EXTI_LINE EXTI_LINE_12
#define DISPLAY_PANEL_INIT_SEQ lx154a2422_init_seq
#define DISPLAY_PANEL_ROTATE lx154a2422_rotate
#define TRANSFORM_TOUCH_COORDS lx154a2422_transform_touch_coords
#define BACKLIGHT_PWM_FREQ 12500
#define BACKLIGHT_PWM_TIM TIM17
#define BACKLIGHT_PWM_TIM_CLK_EN __HAL_RCC_TIM17_CLK_ENABLE

@ -1,9 +1,6 @@
#ifndef _TREZOR_T3T1_H
#define _TREZOR_T3T1_H
#define DISPLAY_RESX 240
#define DISPLAY_RESY 240
#define VDD_1V8 1
#define HSE_16MHZ 1
@ -17,8 +14,11 @@
#define USE_BACKLIGHT 1
#define USE_HASH_PROCESSOR 1
#include "displays/panels/lx154a2422.h"
#include "displays/st7789v.h"
#define DISPLAY_RESX 240
#define DISPLAY_RESY 240
#define DISPLAY_LEGACY_HEADER "displays/st7789v.h"
#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565
#define DISPLAY_IDENTIFY 1
#define DISPLAY_TE_PORT GPIOD
#define DISPLAY_TE_PIN GPIO_PIN_12
@ -27,10 +27,6 @@
#define DISPLAY_TE_INTERRUPT_GPIOSEL EXTI_GPIOD
#define DISPLAY_TE_INTERRUPT_EXTI_LINE EXTI_LINE_12
#define DISPLAY_PANEL_INIT_SEQ lx154a2422_init_seq
#define DISPLAY_PANEL_ROTATE lx154a2422_rotate
#define TRANSFORM_TOUCH_COORDS lx154a2422_transform_touch_coords
#define BACKLIGHT_PWM_FREQ 12500
#define BACKLIGHT_PWM_TIM TIM8
#define BACKLIGHT_PWM_TIM_CLK_EN __HAL_RCC_TIM8_CLK_ENABLE

@ -20,11 +20,19 @@
#ifndef TREZORHAL_DISPLAY_H
#define TREZORHAL_DISPLAY_H
#if NEW_RENDERING
#include <xdisplay.h>
#else
#include <stdint.h>
#include "common.h"
#include "display_draw.h"
#include TREZOR_BOARD
#ifdef DISPLAY_LEGACY_HEADER
#include DISPLAY_LEGACY_HEADER
#endif
#ifndef DISPLAY_FRAMEBUFFER_OFFSET_Y
#define DISPLAY_FRAMEBUFFER_OFFSET_Y 0
#endif
@ -69,4 +77,5 @@ uint8_t *display_get_wr_addr(void);
void display_shift_window(uint16_t pixels);
uint16_t display_get_window_offset(void);
#endif // NEW_RENDERING
#endif // TREZORHAL_DISPLAY_H

@ -20,8 +20,12 @@
#ifndef TREZORHAL_DMA2D_H
#define TREZORHAL_DMA2D_H
#include <stdbool.h>
#include <stdint.h>
#include "common.h"
#include "gl_dma2d.h"
void dma2d_init(void);
void dma2d_setup_const(void);
@ -40,4 +44,12 @@ void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
void dma2d_wait_for_transfer(void);
void dma2d_wait(void);
bool dma2d_rgb565_fill(const dma2d_params_t* dp);
bool dma2d_rgb565_copy_mono4(const dma2d_params_t* dp);
bool dma2d_rgb565_copy_rgb565(const dma2d_params_t* dp);
bool dma2d_rgb565_blend_mono4(const dma2d_params_t* dp);
bool dma2d_accessible(const void* ptr);
#endif // TREZORHAL_DMA2D_H

@ -224,7 +224,11 @@ void collect_hw_entropy(void) {
void ensure_compatible_settings(void) {
display_finish_actions();
#ifdef TREZOR_MODEL_T
#ifdef NEW_RENDERING
display_set_compatible_settings();
#else
display_set_big_endian();
#endif
display_orientation(0);
set_core_clock(CLOCK_168_MHZ);
backlight_pwm_set_slow();

@ -0,0 +1,194 @@
/*
* 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 <string.h>
#include <xdisplay.h>
#include "display_fb.h"
#include "display_io.h"
#include "display_panel.h"
#include "backlight_pwm.h"
#include "supervise.h"
#ifndef BOARDLOADER
#include "bg_copy.h"
#endif
#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 240)
#error "Incompatible display resolution"
#endif
// Display driver context.
typedef struct {
// Current display orientation (0, 90, 180, 270)
int orientation_angle;
} display_driver_t;
// Display driver instance
static display_driver_t g_display_driver;
void display_init(void) {
display_driver_t* drv = &g_display_driver;
memset(drv, 0, sizeof(display_driver_t));
display_io_init_gpio();
display_io_init_fmc();
display_panel_init();
display_panel_set_little_endian();
backlight_pwm_init();
#ifdef XFRAMEBUFFER
display_io_init_te_interrupt();
#endif
}
void display_reinit(void) {
display_driver_t* drv = &g_display_driver;
memset(drv, 0, sizeof(display_driver_t));
// Reinitialize FMC to set correct timing
// We have to do this in reinit because boardloader is fixed.
display_io_init_fmc();
// Important for model T as this is not set in boardloader
display_panel_set_little_endian();
display_panel_init_gamma();
backlight_pwm_reinit();
#ifdef XFRAMEBUFFER
display_io_init_te_interrupt();
#endif
}
void display_finish_actions(void) {
// !@# disable interrupt
// !@# wait for dma ops
}
int display_set_backlight(int level) {
#ifdef XFRAMEBUFFER
#ifndef BOARDLOADER
// wait for DMA transfer to finish before changing backlight
// so that we know that panel has current data
if (backlight_pwm_get() != level && !is_mode_handler()) {
bg_copy_wait();
}
#endif
#endif
return backlight_pwm_set(level);
}
int display_get_backlight(void) { return backlight_pwm_get(); }
int display_set_orientation(int angle) {
display_driver_t* drv = &g_display_driver;
if (angle != drv->orientation_angle) {
if (angle == 0 || angle == 90 || angle == 180 || angle == 270) {
drv->orientation_angle = angle;
#ifdef XFRAMEBUFFER
memset(physical_frame_buffer_0, 0, sizeof(physical_frame_buffer_0));
memset(physical_frame_buffer_1, 0, sizeof(physical_frame_buffer_1));
#endif
display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
for (uint32_t i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
// 2 bytes per pixel because we're using RGB 5-6-5 format
ISSUE_PIXEL_DATA(0x0000);
}
display_panel_rotate(angle);
}
}
return drv->orientation_angle;
}
int display_get_orientation(void) {
display_driver_t* drv = &g_display_driver;
return drv->orientation_angle;
}
#ifndef XFRAMEBUFFER
void display_refresh(void) {
// if the framebuffer is not used the implementation is empty
}
#endif
void display_wait_for_sync(void) {
#ifdef DISPLAY_TE_PIN
uint32_t id = display_panel_identify();
if (id && (id != DISPLAY_ID_GC9307)) {
// synchronize with the panel synchronization signal
// in order to avoid visual tearing effects
while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN))
;
while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN))
;
}
#endif
}
const char* display_save(const char* prefix) { return NULL; }
void display_clear_save(void) {}
void display_set_compatible_settings(void) { display_panel_set_big_endian(); }
static inline void set_window(const dma2d_params_t* dp) {
display_panel_set_window(dp->dst_x, dp->dst_y, dp->dst_x + dp->width - 1,
dp->dst_y + dp->height + 1);
}
// Fills a rectangle with a specified color
void display_fill(const dma2d_params_t* dp) {
set_window(dp);
uint16_t height = dp->height;
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
ISSUE_PIXEL_DATA(dp->src_fg);
}
}
}
// Copies an RGB565 bitmap to specified rectangle
void display_copy_rgb565(const dma2d_params_t* dp) {
set_window(dp);
uint16_t* src_ptr = (uint16_t*)dp->src_row + dp->src_x;
uint16_t height = dp->height;
while (height-- > 0) {
for (int x = 0; x < dp->width; x++) {
ISSUE_PIXEL_DATA(src_ptr[x]);
}
src_ptr += dp->src_stride / sizeof(*src_ptr);
}
}
// Copies a MONO4 bitmap to specified rectangle
// void display_copy_mono4(gdc_dma2d_t *dp);

@ -0,0 +1,160 @@
/*
* 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 <stdint.h>
#include <string.h>
#include TREZOR_BOARD
#include STM32_HAL_H
#include "display_fb.h"
#include "display_io.h"
#include "display_panel.h"
#include "irq.h"
#include "supervise.h"
#ifndef BOARDLOADER
#include "bg_copy.h"
#endif
#ifdef XFRAMEBUFFER
#ifndef STM32U5
#error Framebuffer only supported on STM32U5 for now
#endif
// Physical frame buffers in internal SRAM memory
__attribute__((section(".fb1")))
ALIGN_32BYTES(uint8_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE]);
__attribute__((section(".fb2")))
ALIGN_32BYTES(uint8_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE]);
// The current frame buffer selector at fixed memory address
// It's shared between bootloaders and the firmware
__attribute__((section(".framebuffer_select"))) uint32_t current_frame_buffer =
0;
static bool pending_fb_switch = false;
#ifndef BOARDLOADER
void DISPLAY_TE_INTERRUPT_HANDLER(void) {
HAL_NVIC_DisableIRQ(DISPLAY_TE_INTERRUPT_NUM);
if (current_frame_buffer == 1) {
bg_copy_start_const_out_8((uint8_t *)physical_frame_buffer_1,
(uint8_t *)DISPLAY_DATA_ADDRESS,
DISPLAY_RESX * DISPLAY_RESY * 2);
} else {
bg_copy_start_const_out_8((uint8_t *)physical_frame_buffer_0,
(uint8_t *)DISPLAY_DATA_ADDRESS,
DISPLAY_RESX * DISPLAY_RESY * 2);
}
pending_fb_switch = false;
__HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN);
}
static void wait_for_fb_switch(void) {
while (pending_fb_switch) {
__WFI();
}
bg_copy_wait();
}
#endif
static void copy_fb_to_display(uint16_t *fb) {
for (int i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
// 2 bytes per pixel because we're using RGB 5-6-5 format
ISSUE_PIXEL_DATA(fb[i]);
}
}
static void switch_fb_manually(void) {
// sync with the panel refresh
while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) {
}
while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) {
}
if (current_frame_buffer == 0) {
current_frame_buffer = 1;
copy_fb_to_display((uint16_t *)physical_frame_buffer_1);
memcpy(physical_frame_buffer_0, physical_frame_buffer_1,
sizeof(physical_frame_buffer_0));
} else {
current_frame_buffer = 0;
copy_fb_to_display((uint16_t *)physical_frame_buffer_0);
memcpy(physical_frame_buffer_1, physical_frame_buffer_0,
sizeof(physical_frame_buffer_1));
}
}
#ifndef BOARDLOADER
static void switch_fb_in_backround(void) {
if (current_frame_buffer == 0) {
current_frame_buffer = 1;
memcpy(physical_frame_buffer_0, physical_frame_buffer_1,
sizeof(physical_frame_buffer_0));
pending_fb_switch = true;
__HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN);
svc_enableIRQ(DISPLAY_TE_INTERRUPT_NUM);
} else {
current_frame_buffer = 0;
memcpy(physical_frame_buffer_1, physical_frame_buffer_0,
sizeof(physical_frame_buffer_1));
pending_fb_switch = true;
__HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN);
svc_enableIRQ(DISPLAY_TE_INTERRUPT_NUM);
}
}
#endif
void *display_get_frame_addr(void) {
if (current_frame_buffer == 0) {
return (void *)physical_frame_buffer_1;
} else {
return (void *)physical_frame_buffer_0;
}
}
void display_refresh(void) {
#ifndef BOARDLOADER
wait_for_fb_switch();
display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
if (is_mode_handler()) {
switch_fb_manually();
} else {
switch_fb_in_backround();
}
#else
display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
switch_fb_manually();
#endif
}
#endif // XFRAMEBUFFER

@ -0,0 +1,46 @@
/*
* 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/>.
*/
#ifndef TREZOR_HAL_DISPLAY_INTERNAL_H
#define TREZOR_HAL_DISPLAY_INTERNAL_H
#include TREZOR_BOARD
#include <stdint.h>
#ifdef XFRAMEBUFFER
// Size of the physical frame buffer in bytes
#define PHYSICAL_FRAME_BUFFER_SIZE (DISPLAY_RESX * DISPLAY_RESY * 2)
// Physical frame buffers in internal SRAM memory
//
// Both frame buffers layes in the fixed addresses that
// are shared between bootloaders and the firmware.
extern uint8_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE];
extern uint8_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE];
// The current frame buffer selector at fixed memory address
//
// The variable address is shared between bootloaders and the firmware
extern uint32_t current_frame_buffer;
#endif // XFRAMEBUFFER
#endif // TREZOR_HAL_DISPLAY_INTERNAL_H

@ -0,0 +1,145 @@
/*
* 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 TREZOR_BOARD
#include STM32_HAL_H
#include "display_io.h"
#include "irq.h"
__IO DISP_MEM_TYPE *const DISPLAY_CMD_ADDRESS =
(__IO DISP_MEM_TYPE *const)((uint32_t)DISPLAY_MEMORY_BASE);
__IO DISP_MEM_TYPE *const DISPLAY_DATA_ADDRESS =
(__IO DISP_MEM_TYPE *const)((uint32_t)DISPLAY_MEMORY_BASE |
(DISPLAY_ADDR_SHIFT << DISPLAY_MEMORY_PIN));
void display_io_init_gpio(void) {
// init peripherals
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_FMC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
// 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);
#ifdef DISPLAY_TE_PIN
// LCD_FMARK (tearing effect)
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = 0;
GPIO_InitStructure.Pin = DISPLAY_TE_PIN;
HAL_GPIO_Init(DISPLAY_TE_PORT, &GPIO_InitStructure);
#endif
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);
#ifdef USE_DISP_I8080_16BIT_DW
// LCD_D8/PE11 LCD_D9/PE12 LCD_D10/PE13 LCD_D11/PE14
GPIO_InitStructure.Pin =
GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14;
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
// LCD_D12/PE15
GPIO_InitStructure.Pin = GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
// LCD_D13/PD8 LCD_D14/PD9 LCD_D15/PD10
GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
#endif
}
void display_io_init_fmc(void) {
// Reference UM1725 "Description of STM32F4 HAL and LL drivers",
// section 64.2.1 "How to use this driver"
SRAM_HandleTypeDef external_display_data_sram = {0};
external_display_data_sram.Instance = FMC_NORSRAM_DEVICE;
external_display_data_sram.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
external_display_data_sram.Init.NSBank = FMC_NORSRAM_BANK1;
external_display_data_sram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
external_display_data_sram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
#ifdef USE_DISP_I8080_16BIT_DW
external_display_data_sram.Init.MemoryDataWidth =
FMC_NORSRAM_MEM_BUS_WIDTH_16;
#elif USE_DISP_I8080_8BIT_DW
external_display_data_sram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8;
#endif
external_display_data_sram.Init.BurstAccessMode =
FMC_BURST_ACCESS_MODE_DISABLE;
external_display_data_sram.Init.WaitSignalPolarity =
FMC_WAIT_SIGNAL_POLARITY_LOW;
external_display_data_sram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
external_display_data_sram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
external_display_data_sram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
external_display_data_sram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
external_display_data_sram.Init.AsynchronousWait =
FMC_ASYNCHRONOUS_WAIT_DISABLE;
external_display_data_sram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
external_display_data_sram.Init.ContinuousClock =
FMC_CONTINUOUS_CLOCK_SYNC_ONLY;
external_display_data_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 = 5;
normal_mode_timing.AddressHoldTime = 1; // don't care
normal_mode_timing.DataSetupTime = 6;
normal_mode_timing.BusTurnAroundDuration = 0; // don't care
normal_mode_timing.CLKDivision = 2; // don't care
normal_mode_timing.DataLatency = 2; // don't care
normal_mode_timing.AccessMode = FMC_ACCESS_MODE_A;
HAL_SRAM_Init(&external_display_data_sram, &normal_mode_timing, NULL);
}
#ifdef DISPLAY_TE_INTERRUPT_HANDLER
void display_io_init_te_interrupt(void) {
EXTI_HandleTypeDef EXTI_Handle = {0};
EXTI_ConfigTypeDef EXTI_Config = {0};
EXTI_Config.GPIOSel = DISPLAY_TE_INTERRUPT_GPIOSEL;
EXTI_Config.Line = DISPLAY_TE_INTERRUPT_EXTI_LINE;
EXTI_Config.Mode = EXTI_MODE_INTERRUPT;
EXTI_Config.Trigger = EXTI_TRIGGER_RISING;
HAL_EXTI_SetConfigLine(&EXTI_Handle, &EXTI_Config);
// setup interrupt for tearing effect pin
HAL_NVIC_SetPriority(DISPLAY_TE_INTERRUPT_NUM, IRQ_PRI_DMA, 0);
}
#endif

@ -0,0 +1,67 @@
/*
* 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/>.
*/
#ifndef TREZORHAL_DISPLAY_IO_H
#define TREZORHAL_DISPLAY_IO_H
#include STM32_HAL_H
#include TREZOR_BOARD
void display_io_init_gpio(void);
void display_io_init_fmc(void);
void display_io_init_te_interrupt(void);
#ifndef FMC_BANK1
#define FMC_BANK1 0x60000000U
#endif
#define DISPLAY_MEMORY_BASE FMC_BANK1
#define DISPLAY_MEMORY_PIN 16
#ifdef USE_DISP_I8080_16BIT_DW
#define DISPLAY_ADDR_SHIFT 2
#define DISP_MEM_TYPE uint16_t
#elif USE_DISP_I8080_8BIT_DW
#define DISPLAY_ADDR_SHIFT 1
#define DISP_MEM_TYPE uint8_t
#else
#error "Unsupported display interface"
#endif
/*#define DISPLAY_CMD_ADDRESS ((__IO DISP_MEM_TYPE *)(DISPLAY_MEMORY_BASE))
#define DISPLAY_DATA_ADDRESS \
((__IO DISP_MEM_TYPE *)(DISPLAY_MEMORY_BASE | \
(DISPLAY_ADDR_SHIFT << DISPLAY_MEMORY_PIN)))
*/
extern __IO DISP_MEM_TYPE *const DISPLAY_CMD_ADDRESS;
extern __IO DISP_MEM_TYPE *const DISPLAY_DATA_ADDRESS;
#define ISSUE_CMD_BYTE(X) (*(DISPLAY_CMD_ADDRESS) = (X))
#define ISSUE_DATA_BYTE(X) (*(DISPLAY_DATA_ADDRESS) = (X))
#ifdef USE_DISP_I8080_16BIT_DW
#define ISSUE_PIXEL_DATA(X) DATA(X)
#elif USE_DISP_I8080_8BIT_DW
#define ISSUE_PIXEL_DATA(X) \
ISSUE_DATA_BYTE((X)&0xFF); \
ISSUE_DATA_BYTE((X) >> 8)
#endif
#endif // TREZORHAL_DISPLAY_IO_H

@ -0,0 +1,235 @@
/*
* 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/>.
*/
// using const volatile instead of #define results in binaries that change
// only in 1-byte when the flag changes.
// using #define leads compiler to over-optimize the code leading to bigger
// differencies in the resulting binaries.
#include "display_panel.h"
#include "display_io.h"
#ifdef TREZOR_MODEL_T
#include "panels/154a.h"
#include "panels/lx154a2411.h"
#include "panels/lx154a2422.h"
#include "panels/tf15411a.h"
#else
#include "panels/lx154a2422.h"
#endif
// using const volatile instead of #define results in binaries that change
// only in 1-byte when the flag changes.
// using #define leads compiler to over-optimize the code leading to bigger
// differencies in the resulting binaries.
const volatile uint8_t DISPLAY_ST7789V_INVERT_COLORS2 = 1;
#ifdef DISPLAY_IDENTIFY
static uint32_t read_display_id(uint8_t command) {
volatile uint8_t c = 0;
uint32_t id = 0;
ISSUE_CMD_BYTE(command);
c = *DISPLAY_DATA_ADDRESS; // first returned value is a dummy value and
// should be discarded
c = *DISPLAY_DATA_ADDRESS;
id |= (c << 16);
c = *DISPLAY_DATA_ADDRESS;
id |= (c << 8);
c = *DISPLAY_DATA_ADDRESS;
id |= c;
return id;
}
uint32_t display_panel_identify(void) {
static uint32_t id = 0x000000U;
static bool id_initialized = false;
// Return immediately if id has been already initialized
if (id_initialized) return id;
// RDDID: Read Display ID
id = read_display_id(0x04);
// the default RDDID for ILI9341 should be 0x8000.
// some display modules return 0x0.
// the ILI9341 has an extra id, let's check it here.
if ((id != DISPLAY_ID_ST7789V) && (id != DISPLAY_ID_GC9307)) {
// Read ID4
uint32_t id4 = read_display_id(0xD3);
if (id4 == DISPLAY_ID_ILI9341V) { // definitely found a ILI9341
id = id4;
}
}
id_initialized = true;
return id;
}
#else
uint32_t display_panbel_identify(void) { return DISPLAY_ID_ST7789V; }
#endif
bool display_panel_is_inverted() {
bool inv_on = false;
uint32_t id = display_panel_identify();
if (id == DISPLAY_ID_ST7789V) {
volatile uint8_t c = 0;
ISSUE_CMD_BYTE(0x09); // read display status
c = *DISPLAY_DATA_ADDRESS; // don't care
c = *DISPLAY_DATA_ADDRESS; // don't care
c = *DISPLAY_DATA_ADDRESS; // don't care
c = *DISPLAY_DATA_ADDRESS;
if (c & 0x20) {
inv_on = true;
}
c = *DISPLAY_DATA_ADDRESS; // don't care
}
return inv_on;
}
void display_panel_sleep(void) {
uint32_t id = display_panel_identify();
if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
(id == DISPLAY_ID_ST7789V)) {
ISSUE_CMD_BYTE(0x28); // DISPOFF: Display Off
ISSUE_CMD_BYTE(0x10); // SLPIN: Sleep in
HAL_Delay(5); // need to wait 5 milliseconds after "sleep in" before
// sending any new commands
}
}
void display_panel_unsleep(void) {
uint32_t id = display_panel_identify();
if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
(id == DISPLAY_ID_ST7789V)) {
ISSUE_CMD_BYTE(0x11); // SLPOUT: Sleep Out
HAL_Delay(5); // need to wait 5 milliseconds after "sleep out" before
// sending any new commands
ISSUE_CMD_BYTE(0x29); // DISPON: Display On
}
}
void display_panel_set_window(uint16_t x0, uint16_t y0, uint16_t x1,
uint16_t y1) {
uint32_t id = display_panel_identify();
if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
(id == DISPLAY_ID_ST7789V)) {
ISSUE_CMD_BYTE(0x2A);
ISSUE_DATA_BYTE(x0 >> 8);
ISSUE_DATA_BYTE(x0 & 0xFF);
ISSUE_DATA_BYTE(x1 >> 8);
ISSUE_DATA_BYTE(x1 & 0xFF); // column addr set
ISSUE_CMD_BYTE(0x2B);
ISSUE_DATA_BYTE(y0 >> 8);
ISSUE_DATA_BYTE(y0 & 0xFF);
ISSUE_DATA_BYTE(y1 >> 8);
ISSUE_DATA_BYTE(y1 & 0xFF); // row addr set
ISSUE_CMD_BYTE(0x2C);
}
}
void display_panel_set_little_endian(void) {
uint32_t id = display_panel_identify();
if (id == DISPLAY_ID_GC9307) {
// CANNOT SET ENDIAN FOR GC9307
} else if (id == DISPLAY_ID_ST7789V) {
ISSUE_CMD_BYTE(0xB0);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0xF8);
} else if (id == DISPLAY_ID_ILI9341V) {
// Interface Control: XOR BGR as ST7789V does
ISSUE_CMD_BYTE(0xF6);
ISSUE_DATA_BYTE(0x09);
ISSUE_DATA_BYTE(0x30);
ISSUE_DATA_BYTE(0x20);
}
}
void display_panel_set_big_endian(void) {
uint32_t id = display_panel_identify();
if (id == DISPLAY_ID_GC9307) {
// CANNOT SET ENDIAN FOR GC9307
} else if (id == DISPLAY_ID_ST7789V) {
ISSUE_CMD_BYTE(0xB0);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0xF0);
} else if (id == DISPLAY_ID_ILI9341V) {
// Interface Control: XOR BGR as ST7789V does
ISSUE_CMD_BYTE(0xF6);
ISSUE_DATA_BYTE(0x09);
ISSUE_DATA_BYTE(0x30);
ISSUE_DATA_BYTE(0x00);
}
}
void display_panal_init(void) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); // LCD_RST/PC14
// 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);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); // LCD_RST/PC14
// max wait time for hardware reset is 120 milliseconds
// (experienced display flakiness using only 5ms wait before sending commands)
HAL_Delay(120);
// identify the controller we will communicate with
#ifdef TREZOR_MODEL_T
uint32_t id = display_panel_identify();
if (id == DISPLAY_ID_GC9307) {
tf15411a_init_seq();
} else if (id == DISPLAY_ID_ST7789V) {
if (DISPLAY_ST7789V_INVERT_COLORS2) {
lx154a2422_init_seq();
} else {
lx154a2411_init_seq();
}
} else if (id == DISPLAY_ID_ILI9341V) {
_154a_init_seq();
}
#else
lx154a2422_init_seq();
#endif
display_panel_unsleep();
}
void display_panel_init_gamma(void) {
#ifdef TREZOR_MODEL_T
uint32_t id = display_panel_identify();
if (id == DISPLAY_ID_ST7789V && display_panel_is_inverted()) {
// newest TT display - set proper gamma
lx154a2422_gamma();
} else if (id == DISPLAY_ID_ST7789V) {
lx154a2411_gamma();
}
#endif
}
void display_panel_rotate(int angle) {
#ifdef TREZOR_MODEL_T
uint32_t id = display_panel_identify();
if (id == DISPLAY_ID_GC9307) {
tf15411a_rotate(angle);
} else {
lx154a2422_rotate(angle);
}
#else
lx154a2422_rotate(angle);
#endif
}

@ -0,0 +1,52 @@
/*
* 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/>.
*/
#ifndef TREZORHAL_ST7789_PANEL_H
#define TREZORHAL_ST7789_PANEL_H
#include <stdbool.h>
#include <stdint.h>
// section "9.1.3 RDDID (04h): Read Display ID"
// of ST7789V datasheet
#define DISPLAY_ID_ST7789V 0x858552U
// section "6.2.1. Read display identification information (04h)"
// of GC9307 datasheet
#define DISPLAY_ID_GC9307 0x009307U
// section "8.3.23 Read ID4 (D3h)"
// of ILI9341V datasheet
#define DISPLAY_ID_ILI9341V 0x009341U
// Identifies the connected display panel and
// returns one of DISPLAY_ID_xxx constant
uint32_t display_panel_identify(void);
bool display_panel_is_inverted();
void display_panel_init(void);
void display_panel_init_gamma(void);
void display_panel_set_little_endian(void);
void display_panel_set_big_endian(void);
void display_panel_sleep(void);
void display_panel_unsleep(void);
void display_panel_set_window(uint16_t x0, uint16_t y0, uint16_t x1,
uint16_t y1);
void display_panel_rotate(int angle);
#endif // TREZORHAL_ST7789_PANEL_H

@ -0,0 +1,132 @@
/*
* 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 "../display_io.h"
void _154a_init_seq(void) {
// most recent manual: https://www.newhavendisplay.com/app_notes/ILI9341.pdf
// TEON: Tearing Effect Line On; V-blanking only
ISSUE_CMD_BYTE(0x35);
ISSUE_DATA_BYTE(0x00);
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
// input)
ISSUE_CMD_BYTE(0x3A);
ISSUE_DATA_BYTE(0x55);
// Display Function Control: gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xB6);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0xC2);
ISSUE_DATA_BYTE(0x27);
ISSUE_DATA_BYTE(0x00);
// Interface Control: XOR BGR as ST7789V does
ISSUE_CMD_BYTE(0xF6);
ISSUE_DATA_BYTE(0x09);
ISSUE_DATA_BYTE(0x30);
ISSUE_DATA_BYTE(0x00);
// the above config is the most important and definitely necessary
ISSUE_CMD_BYTE(0xCF);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0xC1);
ISSUE_DATA_BYTE(0x30);
ISSUE_CMD_BYTE(0xED);
ISSUE_DATA_BYTE(0x64);
ISSUE_DATA_BYTE(0x03);
ISSUE_DATA_BYTE(0x12);
ISSUE_DATA_BYTE(0x81);
ISSUE_CMD_BYTE(0xE8);
ISSUE_DATA_BYTE(0x85);
ISSUE_DATA_BYTE(0x10);
ISSUE_DATA_BYTE(0x7A);
ISSUE_CMD_BYTE(0xF7);
ISSUE_DATA_BYTE(0x20);
ISSUE_CMD_BYTE(0xEA);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0x00);
// power control VRH[5:0]
ISSUE_CMD_BYTE(0xC0);
ISSUE_DATA_BYTE(0x23);
// power control SAP[2:0] BT[3:0]
ISSUE_CMD_BYTE(0xC1);
ISSUE_DATA_BYTE(0x12);
// vcm control 1
ISSUE_CMD_BYTE(0xC5);
ISSUE_DATA_BYTE(0x60);
ISSUE_DATA_BYTE(0x44);
// vcm control 2
ISSUE_CMD_BYTE(0xC7);
ISSUE_DATA_BYTE(0x8A);
// framerate
ISSUE_CMD_BYTE(0xB1);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0x18);
// 3 gamma func disable
ISSUE_CMD_BYTE(0xF2);
ISSUE_DATA_BYTE(0x00);
// gamma curve 1
ISSUE_CMD_BYTE(0xE0);
ISSUE_DATA_BYTE(0x0F);
ISSUE_DATA_BYTE(0x2F);
ISSUE_DATA_BYTE(0x2C);
ISSUE_DATA_BYTE(0x0B);
ISSUE_DATA_BYTE(0x0F);
ISSUE_DATA_BYTE(0x09);
ISSUE_DATA_BYTE(0x56);
ISSUE_DATA_BYTE(0xD9);
ISSUE_DATA_BYTE(0x4A);
ISSUE_DATA_BYTE(0x0B);
ISSUE_DATA_BYTE(0x14);
ISSUE_DATA_BYTE(0x05);
ISSUE_DATA_BYTE(0x0C);
ISSUE_DATA_BYTE(0x06);
ISSUE_DATA_BYTE(0x00);
// gamma curve 2
ISSUE_CMD_BYTE(0xE1);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0x10);
ISSUE_DATA_BYTE(0x13);
ISSUE_DATA_BYTE(0x04);
ISSUE_DATA_BYTE(0x10);
ISSUE_DATA_BYTE(0x06);
ISSUE_DATA_BYTE(0x25);
ISSUE_DATA_BYTE(0x26);
ISSUE_DATA_BYTE(0x3B);
ISSUE_DATA_BYTE(0x04);
ISSUE_DATA_BYTE(0x0B);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x33);
ISSUE_DATA_BYTE(0x39);
ISSUE_DATA_BYTE(0x0F);
}

@ -0,0 +1,27 @@
/*
* 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/>.
*/
#ifndef _154A_H_
#define _154A_H_
// ILI9341 IC controller
void _154a_init_seq(void);
#endif

@ -0,0 +1,82 @@
#include "../display_io.h"
void lx154a2411_gamma(void) {
// positive voltage correction
ISSUE_CMD_BYTE(0xE0);
ISSUE_DATA_BYTE(0xD0);
ISSUE_DATA_BYTE(0x03);
ISSUE_DATA_BYTE(0x08);
ISSUE_DATA_BYTE(0x0E);
ISSUE_DATA_BYTE(0x11);
ISSUE_DATA_BYTE(0x2B);
ISSUE_DATA_BYTE(0x3B);
ISSUE_DATA_BYTE(0x44);
ISSUE_DATA_BYTE(0x4C);
ISSUE_DATA_BYTE(0x2B);
ISSUE_DATA_BYTE(0x16);
ISSUE_DATA_BYTE(0x15);
ISSUE_DATA_BYTE(0x1E);
ISSUE_DATA_BYTE(0x21);
// negative voltage correction
ISSUE_CMD_BYTE(0xE1);
ISSUE_DATA_BYTE(0xD0);
ISSUE_DATA_BYTE(0x03);
ISSUE_DATA_BYTE(0x08);
ISSUE_DATA_BYTE(0x0E);
ISSUE_DATA_BYTE(0x11);
ISSUE_DATA_BYTE(0x2B);
ISSUE_DATA_BYTE(0x3B);
ISSUE_DATA_BYTE(0x54);
ISSUE_DATA_BYTE(0x4C);
ISSUE_DATA_BYTE(0x2B);
ISSUE_DATA_BYTE(0x16);
ISSUE_DATA_BYTE(0x15);
ISSUE_DATA_BYTE(0x1E);
ISSUE_DATA_BYTE(0x21);
}
void lx154a2411_init_seq(void) {
// most recent manual:
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
// TEON: Tearing Effect Line On; V-blanking only
ISSUE_CMD_BYTE(0x35);
ISSUE_DATA_BYTE(0x00);
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
// input)
ISSUE_CMD_BYTE(0x3A);
ISSUE_DATA_BYTE(0x55);
// CMD2EN: Commands in command table 2 can be executed when EXTC level is Low
ISSUE_CMD_BYTE(0xDF);
ISSUE_DATA_BYTE(0x5A);
ISSUE_DATA_BYTE(0x69);
ISSUE_DATA_BYTE(0x02);
ISSUE_DATA_BYTE(0x01);
// LCMCTRL: LCM Control: XOR RGB setting
ISSUE_CMD_BYTE(0xC0);
ISSUE_DATA_BYTE(0x20);
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is gate 80.;
// gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xE4);
ISSUE_DATA_BYTE(0x1D);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x11);
// INVOFF (20h): Display Inversion Off
// INVON (21h): Display Inversion On
ISSUE_CMD_BYTE(0x20);
// the above config is the most important and definitely necessary
// PWCTRL1: Power Control 1
ISSUE_CMD_BYTE(0xD0);
ISSUE_DATA_BYTE(0xA4);
ISSUE_DATA_BYTE(0xA1);
lx154a2411_gamma();
}

@ -0,0 +1,27 @@
/*
* 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/>.
*/
#ifndef LX154A2411_H_
#define LX154A2411_H_
// ST7789_V IC controller
void lx154a2411_gamma(void);
void lx154a2411_init_seq(void);
#endif

@ -0,0 +1,150 @@
/*
* 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 "../display_io.h"
#include "touch.h"
void lx154a2422_gamma(void) {
// positive voltage correction
ISSUE_CMD_BYTE(0xE0);
ISSUE_DATA_BYTE(0xD0);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x10);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x26);
ISSUE_DATA_BYTE(0x36);
ISSUE_DATA_BYTE(0x34);
ISSUE_DATA_BYTE(0x4D);
ISSUE_DATA_BYTE(0x18);
ISSUE_DATA_BYTE(0x13);
ISSUE_DATA_BYTE(0x14);
ISSUE_DATA_BYTE(0x2F);
ISSUE_DATA_BYTE(0x34);
// negative voltage correction
ISSUE_CMD_BYTE(0xE1);
ISSUE_DATA_BYTE(0xD0);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x10);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x09);
ISSUE_DATA_BYTE(0x26);
ISSUE_DATA_BYTE(0x36);
ISSUE_DATA_BYTE(0x53);
ISSUE_DATA_BYTE(0x4C);
ISSUE_DATA_BYTE(0x18);
ISSUE_DATA_BYTE(0x14);
ISSUE_DATA_BYTE(0x14);
ISSUE_DATA_BYTE(0x2F);
ISSUE_DATA_BYTE(0x34);
}
void lx154a2422_init_seq(void) {
// most recent manual:
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
// TEON: Tearing Effect Line On; V-blanking only
ISSUE_CMD_BYTE(0x35);
ISSUE_DATA_BYTE(0x00);
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
// input)
ISSUE_CMD_BYTE(0x3A);
ISSUE_DATA_BYTE(0x55);
// CMD2EN: Commands in command table 2 can be executed when EXTC level is Low
ISSUE_CMD_BYTE(0xDF);
ISSUE_DATA_BYTE(0x5A);
ISSUE_DATA_BYTE(0x69);
ISSUE_DATA_BYTE(0x02);
ISSUE_DATA_BYTE(0x01);
// LCMCTRL: LCM Control: XOR RGB setting
ISSUE_CMD_BYTE(0xC0);
ISSUE_DATA_BYTE(0x20);
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is gate 80.;
// gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xE4);
ISSUE_DATA_BYTE(0x1D);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x11);
// INVOFF (20h): Display Inversion Off
// INVON (21h): Display Inversion On
ISSUE_CMD_BYTE(0x21);
// the above config is the most important and definitely necessary
// PWCTRL1: Power Control 1
ISSUE_CMD_BYTE(0xD0);
ISSUE_DATA_BYTE(0xA4);
ISSUE_DATA_BYTE(0xA1);
lx154a2422_gamma();
}
void lx154a2422_rotate(int degrees) {
uint16_t shift = 0;
#define RGB (1 << 3)
#define ML (1 << 4) // vertical refresh order
#define MH (1 << 2) // horizontal refresh order
#define MV (1 << 5)
#define MX (1 << 6)
#define MY (1 << 7)
// MADCTL: Memory Data Access Control - reference:
// section 8.12 in the ST7789V manual
uint8_t display_command_parameter = 0;
switch (degrees) {
case 0:
display_command_parameter = 0;
break;
case 90:
display_command_parameter = MV | MX | MH | ML;
shift = 1;
break;
case 180:
display_command_parameter = MX | MY | MH | ML;
shift = 1;
break;
case 270:
display_command_parameter = MV | MY;
break;
}
ISSUE_CMD_BYTE(0x36);
ISSUE_DATA_BYTE(display_command_parameter);
if (shift) {
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
// gate 80.; gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xE4);
ISSUE_DATA_BYTE(0x1D);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0x11);
} else {
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
// gate 80.; gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xE4);
ISSUE_DATA_BYTE(0x1D);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x11);
}
}

@ -0,0 +1,29 @@
/*
* 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/>.
*/
#ifndef LX154A2422_H_
#define LX154A2422_H_
#include "displays/st7789v.h"
void lx154a2422_init_seq(void);
void lx154a2422_gamma(void);
void lx154a2422_rotate(int degrees);
#endif

@ -0,0 +1,165 @@
/*
* 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 "../display_io.h"
void tf15411a_init_seq(void) {
// Inter Register Enable1
ISSUE_CMD_BYTE(0xFE);
// Inter Register Enable2
ISSUE_CMD_BYTE(0xEF);
// TEON: Tearing Effect Line On; V-blanking only
ISSUE_CMD_BYTE(0x35);
ISSUE_DATA_BYTE(0x00);
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
// input)
ISSUE_CMD_BYTE(0x3A);
ISSUE_DATA_BYTE(0x55);
// Frame Rate
// ISSUE_CMD_BYTE(0xE8); ISSUE_DATA_BYTE(0x12); ISSUE_DATA_BYTE(0x00);
// Power Control 2
ISSUE_CMD_BYTE(0xC3);
ISSUE_DATA_BYTE(0x27);
// Power Control 3
ISSUE_CMD_BYTE(0xC4);
ISSUE_DATA_BYTE(0x18);
// Power Control 4
ISSUE_CMD_BYTE(0xC9);
ISSUE_DATA_BYTE(0x1F);
ISSUE_CMD_BYTE(0xC5);
ISSUE_DATA_BYTE(0x0F);
ISSUE_CMD_BYTE(0xC6);
ISSUE_DATA_BYTE(0x00);
ISSUE_CMD_BYTE(0xC7);
ISSUE_DATA_BYTE(0x10);
ISSUE_CMD_BYTE(0xC8);
ISSUE_DATA_BYTE(0x01);
ISSUE_CMD_BYTE(0xFF);
ISSUE_DATA_BYTE(0x62);
ISSUE_CMD_BYTE(0x99);
ISSUE_DATA_BYTE(0x3E);
ISSUE_CMD_BYTE(0x9D);
ISSUE_DATA_BYTE(0x4B);
ISSUE_CMD_BYTE(0x8E);
ISSUE_DATA_BYTE(0x0F);
// SET_GAMMA1
ISSUE_CMD_BYTE(0xF0);
ISSUE_DATA_BYTE(0x8F);
ISSUE_DATA_BYTE(0x1B);
ISSUE_DATA_BYTE(0x05);
ISSUE_DATA_BYTE(0x06);
ISSUE_DATA_BYTE(0x07);
ISSUE_DATA_BYTE(0x42);
// SET_GAMMA3
ISSUE_CMD_BYTE(0xF2);
ISSUE_DATA_BYTE(0x5C);
ISSUE_DATA_BYTE(0x1F);
ISSUE_DATA_BYTE(0x12);
ISSUE_DATA_BYTE(0x10);
ISSUE_DATA_BYTE(0x07);
ISSUE_DATA_BYTE(0x43);
// SET_GAMMA2
ISSUE_CMD_BYTE(0xF1);
ISSUE_DATA_BYTE(0x59);
ISSUE_DATA_BYTE(0xCF);
ISSUE_DATA_BYTE(0xCF);
ISSUE_DATA_BYTE(0x35);
ISSUE_DATA_BYTE(0x37);
ISSUE_DATA_BYTE(0x8F);
// SET_GAMMA4
ISSUE_CMD_BYTE(0xF3);
ISSUE_DATA_BYTE(0x58);
ISSUE_DATA_BYTE(0xCF);
ISSUE_DATA_BYTE(0xCF);
ISSUE_DATA_BYTE(0x35);
ISSUE_DATA_BYTE(0x37);
ISSUE_DATA_BYTE(0x8F);
}
void tf15411a_rotate(int degrees) {
uint16_t shift = 0;
#define RGB (1 << 3)
#define ML (1 << 4) // vertical refresh order
#define MH (1 << 2) // horizontal refresh order
#define MV (1 << 5)
#define MX (1 << 6)
#define MY (1 << 7)
// MADCTL: Memory Data Access Control - reference:
// section 9.3 in the ILI9341 manual
// section 6.2.18 in the GC9307 manual
// section 8.12 in the ST7789V manual
uint8_t display_command_parameter = 0;
switch (degrees) {
case 0:
display_command_parameter = 0;
break;
case 90:
display_command_parameter = MV | MX | MH | ML;
shift = 1;
break;
case 180:
display_command_parameter = MX | MY | MH | ML;
shift = 1;
break;
case 270:
display_command_parameter = MV | MY;
break;
}
display_command_parameter ^= RGB | MY; // XOR RGB and MY settings
ISSUE_CMD_BYTE(0x36);
ISSUE_DATA_BYTE(display_command_parameter);
if (shift) {
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
// gate 80.; gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xE4);
ISSUE_DATA_BYTE(0x1D);
ISSUE_DATA_BYTE(0x00);
ISSUE_DATA_BYTE(0x11);
} else {
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
// gate 80.; gate scan direction 319 -> 0
ISSUE_CMD_BYTE(0xE4);
ISSUE_DATA_BYTE(0x1D);
ISSUE_DATA_BYTE(0x0A);
ISSUE_DATA_BYTE(0x11);
}
}

@ -0,0 +1,28 @@
/*
* 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/>.
*/
#ifndef TF15411A_H_
#define TF15411A_H_
// GC9307 IC controller
void tf15411a_init_seq(void);
void tf15411a_rotate(int degrees);
#endif

@ -0,0 +1,121 @@
/*
* 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 "display_internal.h"
#include "ili9341_spi.h"
#include "xdisplay.h"
#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 320)
#error "Incompatible display resolution"
#endif
// Display driver context.
typedef struct {
// Current display orientation (0, 90, 180, 270)
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;
void display_init(void) {
display_driver_t *drv = &g_display_driver;
memset(drv, 0, sizeof(display_driver_t));
// Initialize LTDC controller
BSP_LCD_Init();
// Initialize external display controller
ili9341_init();
}
void display_reinit(void) {
display_driver_t *drv = &g_display_driver;
memset(drv, 0, sizeof(display_driver_t));
}
void display_finish_actions(void) {
// Not used and intentionally left empty
}
int display_set_backlight(int level) {
display_driver_t *drv = &g_display_driver;
// Just emulation, not doing anything
drv->backlight_level = level;
return 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 == 0 || angle == 90 || angle == 180 || angle == 270) {
// Just emulation, not doing anything
drv->orientation_angle = angle;
}
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) { return (void *)FRAME_BUFFER_ADDR; }
void display_refresh(void) {
// Do nothing as using just a single frame buffer
}
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);
*/

@ -0,0 +1,36 @@
/*
* 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/>.
*/
#ifndef TREZORHAL_DISPLAY_INTERNAL_H
#define TREZORHAL_DISPLAY_INTERNAL_H
#include TREZOR_BOARD
#include STM32_HAL_H
#include "sdram.h"
// Frame buffer address in external SDRAM
#define FRAME_BUFFER_ADDR ((uint32_t)SDRAM_DEVICE_ADDR)
// Frame buffer size (16-bit per pixel RGB565)
#define FRAME_BUFFER_SIZE (DISPLAY_RESX * DISPLAY_RESY * 2)
// Initializes LTDC controller and I/O pins
void BSP_LCD_Init(void);
#endif // TREZORHAL_DISPLAY_INTERNAL_H

@ -0,0 +1,291 @@
/*
* 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 "display_internal.h"
#include "ili9341_spi.h"
#include "xdisplay.h"
#define MAX_LAYER_NUMBER 2
LTDC_HandleTypeDef LtdcHandler;
static RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
/* Default LCD configuration with LCD Layer 1 */
uint32_t ActiveLayer = 0;
/**
* @brief Initializes the LCD layers.
* @param LayerIndex: the layer foreground or background.
* @param FB_Address: the layer frame buffer.
*/
void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) {
LTDC_LayerCfgTypeDef Layercfg;
/* Layer Init */
Layercfg.WindowX0 = 0;
Layercfg.WindowX1 = DISPLAY_RESX;
Layercfg.WindowY0 = 0;
Layercfg.WindowY1 = DISPLAY_RESY;
Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
Layercfg.FBStartAdress = FB_Address;
Layercfg.Alpha = 255;
Layercfg.Alpha0 = 0;
Layercfg.Backcolor.Blue = 0;
Layercfg.Backcolor.Green = 0;
Layercfg.Backcolor.Red = 0;
Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
Layercfg.ImageWidth = DISPLAY_RESX;
Layercfg.ImageHeight = DISPLAY_RESY;
HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex);
// DrawProp[LayerIndex].BackColor = LCD_COLOR_WHITE;
// DrawProp[LayerIndex].pFont = &Font24;
// DrawProp[LayerIndex].TextColor = LCD_COLOR_BLACK;
/* Dithering activation */
HAL_LTDC_EnableDither(&LtdcHandler);
}
/**
* @brief Selects the LCD Layer.
* @param LayerIndex: the Layer foreground or background.
*/
void BSP_LCD_SelectLayer(uint32_t LayerIndex) { ActiveLayer = LayerIndex; }
/**
* @brief Sets a LCD Layer visible.
* @param LayerIndex: the visible Layer.
* @param state: new state of the specified layer.
* This parameter can be: ENABLE or DISABLE.
*/
void BSP_LCD_SetLayerVisible(uint32_t LayerIndex, FunctionalState state) {
if (state == ENABLE) {
__HAL_LTDC_LAYER_ENABLE(&LtdcHandler, LayerIndex);
} else {
__HAL_LTDC_LAYER_DISABLE(&LtdcHandler, LayerIndex);
}
__HAL_LTDC_RELOAD_CONFIG(&LtdcHandler);
}
/**
* @brief Sets an LCD Layer visible without reloading.
* @param LayerIndex: Visible Layer
* @param State: New state of the specified layer
* This parameter can be one of the following values:
* @arg ENABLE
* @arg DISABLE
* @retval None
*/
void BSP_LCD_SetLayerVisible_NoReload(uint32_t LayerIndex,
FunctionalState State) {
if (State == ENABLE) {
__HAL_LTDC_LAYER_ENABLE(&LtdcHandler, LayerIndex);
} else {
__HAL_LTDC_LAYER_DISABLE(&LtdcHandler, LayerIndex);
}
/* Do not Sets the Reload */
}
/**
* @brief Configures the Transparency.
* @param LayerIndex: the Layer foreground or background.
* @param Transparency: the Transparency,
* This parameter must range from 0x00 to 0xFF.
*/
void BSP_LCD_SetTransparency(uint32_t LayerIndex, uint8_t Transparency) {
HAL_LTDC_SetAlpha(&LtdcHandler, Transparency, LayerIndex);
}
/**
* @brief Configures the transparency without reloading.
* @param LayerIndex: Layer foreground or background.
* @param Transparency: Transparency
* This parameter must be a number between Min_Data = 0x00 and
* Max_Data = 0xFF
* @retval None
*/
void BSP_LCD_SetTransparency_NoReload(uint32_t LayerIndex,
uint8_t Transparency) {
HAL_LTDC_SetAlpha_NoReload(&LtdcHandler, Transparency, LayerIndex);
}
/**
* @brief Sets a LCD layer frame buffer address.
* @param LayerIndex: specifies the Layer foreground or background
* @param Address: new LCD frame buffer value
*/
void BSP_LCD_SetLayerAddress(uint32_t LayerIndex, uint32_t Address) {
HAL_LTDC_SetAddress(&LtdcHandler, Address, LayerIndex);
}
/**
* @brief Sets an LCD layer frame buffer address without reloading.
* @param LayerIndex: Layer foreground or background
* @param Address: New LCD frame buffer value
* @retval None
*/
void BSP_LCD_SetLayerAddress_NoReload(uint32_t LayerIndex, uint32_t Address) {
HAL_LTDC_SetAddress_NoReload(&LtdcHandler, Address, LayerIndex);
}
void BSP_LCD_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure = {0};
/* Enable the LTDC and DMA2D Clock */
__HAL_RCC_LTDC_CLK_ENABLE();
__HAL_RCC_DMA2D_CLK_ENABLE();
/* Enable GPIOs clock */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/* GPIOs Configuration */
/*
+------------------------+-----------------------+----------------------------+
+ LCD pins assignment +
+------------------------+-----------------------+----------------------------+
| LCD_TFT R2 <-> PC.10 | LCD_TFT G2 <-> PA.06 | LCD_TFT B2 <-> PD.06 | |
LCD_TFT R3 <-> PB.00 | LCD_TFT G3 <-> PG.10 | LCD_TFT B3 <-> PG.11 |
| LCD_TFT R4 <-> PA.11 | LCD_TFT G4 <-> PB.10 | LCD_TFT B4 <-> PG.12 | |
LCD_TFT R5 <-> PA.12 | LCD_TFT G5 <-> PB.11 | LCD_TFT B5 <-> PA.03 |
| LCD_TFT R6 <-> PB.01 | LCD_TFT G6 <-> PC.07 | LCD_TFT B6 <-> PB.08 | |
LCD_TFT R7 <-> PG.06 | LCD_TFT G7 <-> PD.03 | LCD_TFT B7 <-> PB.09 |
-------------------------------------------------------------------------------
| LCD_TFT HSYNC <-> PC.06 | LCDTFT VSYNC <-> PA.04 |
| LCD_TFT CLK <-> PG.07 | LCD_TFT DE <-> PF.10 |
-----------------------------------------------------
*/
/* GPIOA configuration */
GPIO_InitStructure.Pin =
GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_11 | GPIO_PIN_12;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
GPIO_InitStructure.Alternate = GPIO_AF14_LTDC;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOB configuration */
GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
/* GPIOC configuration */
GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10;
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);
/* GPIOD configuration */
GPIO_InitStructure.Pin = GPIO_PIN_3 | GPIO_PIN_6;
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
/* GPIOF configuration */
GPIO_InitStructure.Pin = GPIO_PIN_10;
HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);
/* GPIOG configuration */
GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_11;
HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);
/* GPIOB configuration */
GPIO_InitStructure.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStructure.Alternate = GPIO_AF9_LTDC;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
/* GPIOG configuration */
GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_12;
HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);
/* On STM32F429I-DISCO, it is not possible to read ILI9341 ID because */
/* PIN EXTC is not connected to VDD and then LCD_READ_ID4 is not accessible.
*/
/* In this case, ReadID function is bypassed.*/
/*if(ili9341_drv.ReadID() == ILI9341_ID)*/
/* LTDC Configuration ----------------------------------------------------*/
LtdcHandler.Instance = LTDC;
/* Timing configuration (Typical configuration from ILI9341 datasheet)
HSYNC=10 (9+1)
HBP=20 (29-10+1)
ActiveW=240 (269-20-10+1)
HFP=10 (279-240-20-10+1)
VSYNC=2 (1+1)
VBP=2 (3-2+1)
ActiveH=320 (323-2-2+1)
VFP=4 (327-320-2-2+1)
*/
/* Configure horizontal synchronization width */
LtdcHandler.Init.HorizontalSync = ILI9341_HSYNC;
/* Configure vertical synchronization height */
LtdcHandler.Init.VerticalSync = ILI9341_VSYNC;
/* Configure accumulated horizontal back porch */
LtdcHandler.Init.AccumulatedHBP = ILI9341_HBP;
/* Configure accumulated vertical back porch */
LtdcHandler.Init.AccumulatedVBP = ILI9341_VBP;
/* Configure accumulated active width */
LtdcHandler.Init.AccumulatedActiveW = 269;
/* Configure accumulated active height */
LtdcHandler.Init.AccumulatedActiveH = 323;
/* Configure total width */
LtdcHandler.Init.TotalWidth = 279;
/* Configure total height */
LtdcHandler.Init.TotalHeigh = 327;
/* Configure R,G,B component values for LCD background color */
LtdcHandler.Init.Backcolor.Red = 0;
LtdcHandler.Init.Backcolor.Blue = 0;
LtdcHandler.Init.Backcolor.Green = 0;
/* LCD clock configuration */
/* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
/* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
/* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/4 = 48 Mhz */
/* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_8 = 48/4 = 6Mhz */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
PeriphClkInitStruct.PLLSAI.PLLSAIN = 192;
PeriphClkInitStruct.PLLSAI.PLLSAIR = 4;
PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_8;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
/* Polarity */
LtdcHandler.Init.HSPolarity = LTDC_HSPOLARITY_AL;
LtdcHandler.Init.VSPolarity = LTDC_VSPOLARITY_AL;
LtdcHandler.Init.DEPolarity = LTDC_DEPOLARITY_AL;
LtdcHandler.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
HAL_LTDC_Init(&LtdcHandler);
/* Initialize the LCD Layers */
BSP_LCD_LayerDefaultInit(1, FRAME_BUFFER_ADDR);
memset((void *)FRAME_BUFFER_ADDR, 0, FRAME_BUFFER_SIZE);
}

@ -0,0 +1,512 @@
#include <stdint.h>
#include TREZOR_BOARD
#include "ili9341_spi.h"
#include STM32_HAL_H
/**
* @brief ILI9341 chip IDs
*/
#define ILI9341_ID 0x9341
/**
* @brief ILI9341 Size
*/
#define ILI9341_LCD_PIXEL_WIDTH ((uint16_t)240)
#define ILI9341_LCD_PIXEL_HEIGHT ((uint16_t)320)
/**
* @brief ILI9341 Timing
*/
/* Timing configuration (Typical configuration from ILI9341 datasheet)
HSYNC=10 (9+1)
HBP=20 (29-10+1)
ActiveW=240 (269-20-10+1)
HFP=10 (279-240-20-10+1)
VSYNC=2 (1+1)
VBP=2 (3-2+1)
ActiveH=320 (323-2-2+1)
VFP=4 (327-320-2-2+1)
*/
/**
* @brief ILI9341 Registers
*/
/* Level 1 Commands */
#define LCD_SWRESET 0x01 /* Software Reset */
#define LCD_READ_DISPLAY_ID 0x04 /* Read display identification information */
#define LCD_RDDST 0x09 /* Read Display Status */
#define LCD_RDDPM 0x0A /* Read Display Power Mode */
#define LCD_RDDMADCTL 0x0B /* Read Display MADCTL */
#define LCD_RDDCOLMOD 0x0C /* Read Display Pixel Format */
#define LCD_RDDIM 0x0D /* Read Display Image Format */
#define LCD_RDDSM 0x0E /* Read Display Signal Mode */
#define LCD_RDDSDR 0x0F /* Read Display Self-Diagnostic Result */
#define LCD_SPLIN 0x10 /* Enter Sleep Mode */
#define LCD_SLEEP_OUT 0x11 /* Sleep out register */
#define LCD_PTLON 0x12 /* Partial Mode ON */
#define LCD_NORMAL_MODE_ON 0x13 /* Normal Display Mode ON */
#define LCD_DINVOFF 0x20 /* Display Inversion OFF */
#define LCD_DINVON 0x21 /* Display Inversion ON */
#define LCD_GAMMA 0x26 /* Gamma register */
#define LCD_DISPLAY_OFF 0x28 /* Display off register */
#define LCD_DISPLAY_ON 0x29 /* Display on register */
#define LCD_COLUMN_ADDR 0x2A /* Colomn address register */
#define LCD_PAGE_ADDR 0x2B /* Page address register */
#define LCD_GRAM 0x2C /* GRAM register */
#define LCD_RGBSET 0x2D /* Color SET */
#define LCD_RAMRD 0x2E /* Memory Read */
#define LCD_PLTAR 0x30 /* Partial Area */
#define LCD_VSCRDEF 0x33 /* Vertical Scrolling Definition */
#define LCD_TEOFF 0x34 /* Tearing Effect Line OFF */
#define LCD_TEON 0x35 /* Tearing Effect Line ON */
#define LCD_MAC 0x36 /* Memory Access Control register*/
#define LCD_VSCRSADD 0x37 /* Vertical Scrolling Start Address */
#define LCD_IDMOFF 0x38 /* Idle Mode OFF */
#define LCD_IDMON 0x39 /* Idle Mode ON */
#define LCD_PIXEL_FORMAT 0x3A /* Pixel Format register */
#define LCD_WRITE_MEM_CONTINUE 0x3C /* Write Memory Continue */
#define LCD_READ_MEM_CONTINUE 0x3E /* Read Memory Continue */
#define LCD_SET_TEAR_SCANLINE 0x44 /* Set Tear Scanline */
#define LCD_GET_SCANLINE 0x45 /* Get Scanline */
#define LCD_WDB 0x51 /* Write Brightness Display register */
#define LCD_RDDISBV 0x52 /* Read Display Brightness */
#define LCD_WCD 0x53 /* Write Control Display register*/
#define LCD_RDCTRLD 0x54 /* Read CTRL Display */
#define LCD_WRCABC 0x55 /* Write Content Adaptive Brightness Control */
#define LCD_RDCABC 0x56 /* Read Content Adaptive Brightness Control */
#define LCD_WRITE_CABC 0x5E /* Write CABC Minimum Brightness */
#define LCD_READ_CABC 0x5F /* Read CABC Minimum Brightness */
#define LCD_READ_ID1 0xDA /* Read ID1 */
#define LCD_READ_ID2 0xDB /* Read ID2 */
#define LCD_READ_ID3 0xDC /* Read ID3 */
/* Level 2 Commands */
#define LCD_RGB_INTERFACE 0xB0 /* RGB Interface Signal Control */
#define LCD_FRMCTR1 0xB1 /* Frame Rate Control (In Normal Mode) */
#define LCD_FRMCTR2 0xB2 /* Frame Rate Control (In Idle Mode) */
#define LCD_FRMCTR3 0xB3 /* Frame Rate Control (In Partial Mode) */
#define LCD_INVTR 0xB4 /* Display Inversion Control */
#define LCD_BPC 0xB5 /* Blanking Porch Control register */
#define LCD_DFC 0xB6 /* Display Function Control register */
#define LCD_ETMOD 0xB7 /* Entry Mode Set */
#define LCD_BACKLIGHT1 0xB8 /* Backlight Control 1 */
#define LCD_BACKLIGHT2 0xB9 /* Backlight Control 2 */
#define LCD_BACKLIGHT3 0xBA /* Backlight Control 3 */
#define LCD_BACKLIGHT4 0xBB /* Backlight Control 4 */
#define LCD_BACKLIGHT5 0xBC /* Backlight Control 5 */
#define LCD_BACKLIGHT7 0xBE /* Backlight Control 7 */
#define LCD_BACKLIGHT8 0xBF /* Backlight Control 8 */
#define LCD_POWER1 0xC0 /* Power Control 1 register */
#define LCD_POWER2 0xC1 /* Power Control 2 register */
#define LCD_VCOM1 0xC5 /* VCOM Control 1 register */
#define LCD_VCOM2 0xC7 /* VCOM Control 2 register */
#define LCD_NVMWR 0xD0 /* NV Memory Write */
#define LCD_NVMPKEY 0xD1 /* NV Memory Protection Key */
#define LCD_RDNVM 0xD2 /* NV Memory Status Read */
#define LCD_READ_ID4 0xD3 /* Read ID4 */
#define LCD_PGAMMA 0xE0 /* Positive Gamma Correction register */
#define LCD_NGAMMA 0xE1 /* Negative Gamma Correction register */
#define LCD_DGAMCTRL1 0xE2 /* Digital Gamma Control 1 */
#define LCD_DGAMCTRL2 0xE3 /* Digital Gamma Control 2 */
#define LCD_INTERFACE 0xF6 /* Interface control register */
/* Extend register commands */
#define LCD_POWERA 0xCB /* Power control A register */
#define LCD_POWERB 0xCF /* Power control B register */
#define LCD_DTCA 0xE8 /* Driver timing control A */
#define LCD_DTCB 0xEA /* Driver timing control B */
#define LCD_POWER_SEQ 0xED /* Power on sequence register */
#define LCD_3GAMMA_EN 0xF2 /* 3 Gamma enable register */
#define LCD_PRC 0xF7 /* Pump ratio control register */
/* Size of read registers */
#define LCD_READ_ID4_SIZE 3 /* Size of Read ID4 */
/*############################### SPIx #######################################*/
#define DISCOVERY_SPIx SPI5
#define DISCOVERY_SPIx_CLK_ENABLE() __HAL_RCC_SPI5_CLK_ENABLE()
#define DISCOVERY_SPIx_GPIO_PORT GPIOF /* GPIOF */
#define DISCOVERY_SPIx_AF GPIO_AF5_SPI5
#define DISCOVERY_SPIx_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define DISCOVERY_SPIx_GPIO_CLK_DISABLE() __HAL_RCC_GPIOF_CLK_DISABLE()
#define DISCOVERY_SPIx_SCK_PIN GPIO_PIN_7 /* PF.07 */
#define DISCOVERY_SPIx_MISO_PIN GPIO_PIN_8 /* PF.08 */
#define DISCOVERY_SPIx_MOSI_PIN GPIO_PIN_9 /* PF.09 */
/* Maximum Timeout values for flags waiting loops. These timeouts are not based
on accurate values, they just guarantee that the application will not remain
stuck if the SPI communication is corrupted.
You may modify these timeout values depending on CPU frequency and
application conditions (interrupts routines ...). */
#define SPIx_TIMEOUT_MAX ((uint32_t)0x1000)
/*################################ LCD #######################################*/
/* Chip Select macro definition */
#define LCD_CS_LOW() \
HAL_GPIO_WritePin(LCD_NCS_GPIO_PORT, LCD_NCS_PIN, GPIO_PIN_RESET)
#define LCD_CS_HIGH() \
HAL_GPIO_WritePin(LCD_NCS_GPIO_PORT, LCD_NCS_PIN, GPIO_PIN_SET)
/* Set WRX High to send data */
#define LCD_WRX_LOW() \
HAL_GPIO_WritePin(LCD_WRX_GPIO_PORT, LCD_WRX_PIN, GPIO_PIN_RESET)
#define LCD_WRX_HIGH() \
HAL_GPIO_WritePin(LCD_WRX_GPIO_PORT, LCD_WRX_PIN, GPIO_PIN_SET)
/* Set WRX High to send data */
#define LCD_RDX_LOW() \
HAL_GPIO_WritePin(LCD_RDX_GPIO_PORT, LCD_RDX_PIN, GPIO_PIN_RESET)
#define LCD_RDX_HIGH() \
HAL_GPIO_WritePin(LCD_RDX_GPIO_PORT, LCD_RDX_PIN, GPIO_PIN_SET)
/**
* @brief LCD Control pin
*/
#define LCD_NCS_PIN GPIO_PIN_2
#define LCD_NCS_GPIO_PORT GPIOC
#define LCD_NCS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define LCD_NCS_GPIO_CLK_DISABLE() __HAL_RCC_GPIOC_CLK_DISABLE()
/**
* @}
*/
/**
* @brief LCD Command/data pin
*/
#define LCD_WRX_PIN GPIO_PIN_13
#define LCD_WRX_GPIO_PORT GPIOD
#define LCD_WRX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
#define LCD_WRX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOD_CLK_DISABLE()
#define LCD_RDX_PIN GPIO_PIN_12
#define LCD_RDX_GPIO_PORT GPIOD
#define LCD_RDX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
#define LCD_RDX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOD_CLK_DISABLE()
static SPI_HandleTypeDef SpiHandle;
uint32_t SpixTimeout =
SPIx_TIMEOUT_MAX; /*<! Value of Timeout when SPI communication fails */
/* SPIx bus function */
static void SPIx_Init(void);
static void ili9341_Write(uint16_t Value);
static uint32_t ili9341_Read(uint8_t ReadSize);
static void ili9341_Error(void);
/**
* @brief SPIx Bus initialization
*/
static void SPIx_Init(void) {
if (HAL_SPI_GetState(&SpiHandle) == HAL_SPI_STATE_RESET) {
/* SPI configuration -----------------------------------------------------*/
SpiHandle.Instance = DISCOVERY_SPIx;
/* SPI baudrate is set to 5.6 MHz (PCLK2/SPI_BaudRatePrescaler = 90/16
= 5.625 MHz) to verify these constraints:
- ILI9341 LCD SPI interface max baudrate is 10MHz for write and 6.66MHz
for read
- l3gd20 SPI interface max baudrate is 10MHz for write/read
- PCLK2 frequency is set to 90 MHz
*/
SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
/* On STM32F429I-Discovery, LCD ID cannot be read then keep a common
* configuration */
/* for LCD and GYRO (SPI_DIRECTION_2LINES) */
/* Note: To read a register a LCD, SPI_DIRECTION_1LINE should be set */
SpiHandle.Init.Direction = SPI_DIRECTION_2LINES;
SpiHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
SpiHandle.Init.CLKPolarity = SPI_POLARITY_LOW;
SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
SpiHandle.Init.CRCPolynomial = 7;
SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT;
SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
SpiHandle.Init.NSS = SPI_NSS_SOFT;
SpiHandle.Init.TIMode = SPI_TIMODE_DISABLED;
SpiHandle.Init.Mode = SPI_MODE_MASTER;
HAL_SPI_Init(&SpiHandle);
}
}
/**
* @brief SPIx error treatment function.
*/
static void ili9341_Error(void) {
/* De-initialize the SPI communication BUS */
HAL_SPI_DeInit(&SpiHandle);
/* Re- Initialize the SPI communication BUS */
SPIx_Init();
}
/**
* @brief Reads 4 bytes from device.
* @param ReadSize: Number of bytes to read (max 4 bytes)
* @retval Value read on the SPI
*/
static uint32_t ili9341_Read(uint8_t ReadSize) {
HAL_StatusTypeDef status = HAL_OK;
uint32_t readvalue;
status =
HAL_SPI_Receive(&SpiHandle, (uint8_t*)&readvalue, ReadSize, SpixTimeout);
/* Check the communication status */
if (status != HAL_OK) {
/* Re-Initialize the BUS */
ili9341_Error();
}
return readvalue;
}
/**
* @brief Writes a byte to device.
* @param Value: value to be written
*/
static void ili9341_Write(uint16_t Value) {
HAL_StatusTypeDef status = HAL_OK;
status = HAL_SPI_Transmit(&SpiHandle, (uint8_t*)&Value, 1, SpixTimeout);
/* Check the communication status */
if (status != HAL_OK) {
/* Re-Initialize the BUS */
ili9341_Error();
}
}
void ili9341_spi_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure NCS in Output Push-Pull mode */
LCD_WRX_GPIO_CLK_ENABLE();
GPIO_InitStructure.Pin = LCD_WRX_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(LCD_WRX_GPIO_PORT, &GPIO_InitStructure);
LCD_RDX_GPIO_CLK_ENABLE();
GPIO_InitStructure.Pin = LCD_RDX_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(LCD_RDX_GPIO_PORT, &GPIO_InitStructure);
/* Configure the LCD Control pins ----------------------------------------*/
LCD_NCS_GPIO_CLK_ENABLE();
/* Configure NCS in Output Push-Pull mode */
GPIO_InitStructure.Pin = LCD_NCS_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(LCD_NCS_GPIO_PORT, &GPIO_InitStructure);
/* Set or Reset the control line */
LCD_CS_LOW();
LCD_CS_HIGH();
/* Enable SPIx clock */
DISCOVERY_SPIx_CLK_ENABLE();
/* Enable DISCOVERY_SPI GPIO clock */
DISCOVERY_SPIx_GPIO_CLK_ENABLE();
/* configure SPI SCK, MOSI and MISO */
GPIO_InitStructure.Pin = (DISCOVERY_SPIx_SCK_PIN | DISCOVERY_SPIx_MOSI_PIN |
DISCOVERY_SPIx_MISO_PIN);
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
GPIO_InitStructure.Speed = GPIO_SPEED_MEDIUM;
GPIO_InitStructure.Alternate = DISCOVERY_SPIx_AF;
HAL_GPIO_Init(DISCOVERY_SPIx_GPIO_PORT, &GPIO_InitStructure);
SPIx_Init();
}
/**
* @brief Writes register value.
*/
void ili9341_WriteData(uint16_t RegValue) {
/* Set WRX to send data */
LCD_WRX_HIGH();
/* Reset LCD control line(/CS) and Send data */
LCD_CS_LOW();
ili9341_Write(RegValue);
/* Deselect: Chip Select high */
LCD_CS_HIGH();
}
/**
* @brief Writes register address.
*/
void ili9341_WriteReg(uint8_t Reg) {
/* Reset WRX to send command */
LCD_WRX_LOW();
/* Reset LCD control line(/CS) and Send command */
LCD_CS_LOW();
ili9341_Write(Reg);
/* Deselect: Chip Select high */
LCD_CS_HIGH();
}
/**
* @brief Reads register value.
* @param RegValue Address of the register to read
* @param ReadSize Number of bytes to read
* @retval Content of the register value
*/
uint32_t ili9341_ReadData(uint16_t RegValue, uint8_t ReadSize) {
uint32_t readvalue = 0;
/* Select: Chip Select low */
LCD_CS_LOW();
/* Reset WRX to send command */
LCD_WRX_LOW();
ili9341_Write(RegValue);
readvalue = ili9341_Read(ReadSize);
/* Set WRX to send data */
LCD_WRX_HIGH();
/* Deselect: Chip Select high */
LCD_CS_HIGH();
return readvalue;
}
void ili9341_init(void) {
/* Initialize ILI9341 low level bus layer ----------------------------------*/
ili9341_spi_init();
ili9341_WriteReg(LCD_DISPLAY_OFF);
/* Configure LCD */
ili9341_WriteReg(0xCA);
ili9341_WriteData(0xC3);
ili9341_WriteData(0x08);
ili9341_WriteData(0x50);
ili9341_WriteReg(LCD_POWERB);
ili9341_WriteData(0x00);
ili9341_WriteData(0xC1);
ili9341_WriteData(0x30);
ili9341_WriteReg(LCD_POWER_SEQ);
ili9341_WriteData(0x64);
ili9341_WriteData(0x03);
ili9341_WriteData(0x12);
ili9341_WriteData(0x81);
ili9341_WriteReg(LCD_DTCA);
ili9341_WriteData(0x85);
ili9341_WriteData(0x00);
ili9341_WriteData(0x78);
ili9341_WriteReg(LCD_POWERA);
ili9341_WriteData(0x39);
ili9341_WriteData(0x2C);
ili9341_WriteData(0x00);
ili9341_WriteData(0x34);
ili9341_WriteData(0x02);
ili9341_WriteReg(LCD_PRC);
ili9341_WriteData(0x20);
ili9341_WriteReg(LCD_DTCB);
ili9341_WriteData(0x00);
ili9341_WriteData(0x00);
ili9341_WriteReg(LCD_FRMCTR1);
ili9341_WriteData(0x00);
ili9341_WriteData(0x1B);
ili9341_WriteReg(LCD_DFC);
ili9341_WriteData(0x0A);
ili9341_WriteData(0xA2);
ili9341_WriteReg(LCD_POWER1);
ili9341_WriteData(0x10);
ili9341_WriteReg(LCD_POWER2);
ili9341_WriteData(0x10);
ili9341_WriteReg(LCD_VCOM1);
ili9341_WriteData(0x45);
ili9341_WriteData(0x15);
ili9341_WriteReg(LCD_VCOM2);
ili9341_WriteData(0x90);
ili9341_WriteReg(LCD_MAC);
ili9341_WriteData(0xC8);
ili9341_WriteReg(LCD_3GAMMA_EN);
ili9341_WriteData(0x00);
ili9341_WriteReg(LCD_RGB_INTERFACE);
ili9341_WriteData(0xC2);
ili9341_WriteReg(LCD_DFC);
ili9341_WriteData(0x0A);
ili9341_WriteData(0xA7);
ili9341_WriteData(0x27);
ili9341_WriteData(0x04);
/* Colomn address set */
ili9341_WriteReg(LCD_COLUMN_ADDR);
ili9341_WriteData(0x00);
ili9341_WriteData(0x00);
ili9341_WriteData(0x00);
ili9341_WriteData(0xEF);
/* Page address set */
ili9341_WriteReg(LCD_PAGE_ADDR);
ili9341_WriteData(0x00);
ili9341_WriteData(0x00);
ili9341_WriteData(0x01);
ili9341_WriteData(0x3F);
ili9341_WriteReg(LCD_INTERFACE);
ili9341_WriteData(0x01);
ili9341_WriteData(0x00);
ili9341_WriteData(0x06);
ili9341_WriteReg(LCD_GRAM);
HAL_Delay(200);
ili9341_WriteReg(LCD_GAMMA);
ili9341_WriteData(0x01);
ili9341_WriteReg(LCD_PGAMMA);
ili9341_WriteData(0x0F);
ili9341_WriteData(0x29);
ili9341_WriteData(0x24);
ili9341_WriteData(0x0C);
ili9341_WriteData(0x0E);
ili9341_WriteData(0x09);
ili9341_WriteData(0x4E);
ili9341_WriteData(0x78);
ili9341_WriteData(0x3C);
ili9341_WriteData(0x09);
ili9341_WriteData(0x13);
ili9341_WriteData(0x05);
ili9341_WriteData(0x17);
ili9341_WriteData(0x11);
ili9341_WriteData(0x00);
ili9341_WriteReg(LCD_NGAMMA);
ili9341_WriteData(0x00);
ili9341_WriteData(0x16);
ili9341_WriteData(0x1B);
ili9341_WriteData(0x04);
ili9341_WriteData(0x11);
ili9341_WriteData(0x07);
ili9341_WriteData(0x31);
ili9341_WriteData(0x33);
ili9341_WriteData(0x42);
ili9341_WriteData(0x05);
ili9341_WriteData(0x0C);
ili9341_WriteData(0x0A);
ili9341_WriteData(0x28);
ili9341_WriteData(0x2F);
ili9341_WriteData(0x0F);
ili9341_WriteReg(LCD_SLEEP_OUT);
HAL_Delay(200);
ili9341_WriteReg(LCD_DISPLAY_ON);
/* GRAM start writing */
ili9341_WriteReg(LCD_GRAM);
}

@ -0,0 +1,13 @@
#ifndef _ILI9341_SPI_H
#define _ILI9341_SPI_H
#define ILI9341_HSYNC ((uint32_t)9) /* Horizontal synchronization */
#define ILI9341_HBP ((uint32_t)29) /* Horizontal back porch */
#define ILI9341_HFP ((uint32_t)2) /* Horizontal front porch */
#define ILI9341_VSYNC ((uint32_t)1) /* Vertical synchronization */
#define ILI9341_VBP ((uint32_t)3) /* Vertical back porch */
#define ILI9341_VFP ((uint32_t)2) /* Vertical front porch */
void ili9341_init(void);
#endif //_ILI9341_SPI_H

@ -0,0 +1,393 @@
/*
* 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);
*/

@ -0,0 +1,351 @@
/*
* 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) {
}
*/

@ -46,12 +46,12 @@ uint8_t *const DISPLAY_DATA_ADDRESS = 0;
uint16_t cursor_x = 0;
uint16_t cursor_y = 0;
uint16_t window_x0 = 0;
uint16_t window_y0 = MAX_DISPLAY_RESX - 1;
uint16_t window_y0 = DISPLAY_RESX - 1;
uint16_t window_x1 = 0;
uint16_t window_y1 = MAX_DISPLAY_RESY - 1;
uint16_t window_y1 = DISPLAY_RESY - 1;
void display_pixeldata(uint16_t c) {
((uint16_t *)LCD_FRAME_BUFFER)[(cursor_y * MAX_DISPLAY_RESX) + cursor_x] = c;
((uint16_t *)LCD_FRAME_BUFFER)[(cursor_y * DISPLAY_RESX) + cursor_x] = c;
cursor_x++;
@ -83,9 +83,9 @@ void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) {
/* Layer Init */
Layercfg.WindowX0 = 0;
Layercfg.WindowX1 = MAX_DISPLAY_RESX;
Layercfg.WindowX1 = DISPLAY_RESX;
Layercfg.WindowY0 = 0;
Layercfg.WindowY1 = MAX_DISPLAY_RESY;
Layercfg.WindowY1 = DISPLAY_RESY;
Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
Layercfg.FBStartAdress = FB_Address;
Layercfg.Alpha = 255;
@ -95,8 +95,8 @@ void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) {
Layercfg.Backcolor.Red = 0;
Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
Layercfg.ImageWidth = MAX_DISPLAY_RESX;
Layercfg.ImageHeight = MAX_DISPLAY_RESY;
Layercfg.ImageWidth = DISPLAY_RESX;
Layercfg.ImageHeight = DISPLAY_RESY;
HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex);
@ -380,7 +380,7 @@ void display_efficient_clear(void) {
uint8_t *display_get_wr_addr(void) {
uint32_t address = LCD_FRAME_BUFFER;
/* Get the rectangle start address */
address = (address + (2 * ((cursor_y)*MAX_DISPLAY_RESX + (cursor_x))));
address = (address + (2 * ((cursor_y)*DISPLAY_RESX + (cursor_x))));
return (uint8_t *)address;
}
@ -413,7 +413,7 @@ void display_shift_window(uint16_t pixels) {
}
uint16_t display_get_window_offset(void) {
return MAX_DISPLAY_RESX - display_get_window_width();
return DISPLAY_RESX - display_get_window_width();
}
void display_finish_actions(void) {}

@ -5,8 +5,8 @@
#include STM32_HAL_H
#define TREZOR_FONT_BPP 4
#define DISPLAY_FRAMEBUFFER_WIDTH MAX_DISPLAY_RESX
#define DISPLAY_FRAMEBUFFER_HEIGHT MAX_DISPLAY_RESY
#define DISPLAY_FRAMEBUFFER_WIDTH DISPLAY_RESX
#define DISPLAY_FRAMEBUFFER_HEIGHT DISPLAY_RESY
#define DISPLAY_FRAMEBUFFER_OFFSET_X 0
#define DISPLAY_FRAMEBUFFER_OFFSET_Y 0
#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save