From bd005e33df1b4de84d084ead7bc3ca50e56e7ae2 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 21 Sep 2021 20:09:27 +0200 Subject: [PATCH] refactor(core): decouple T1 button handling from touch [no changelog] --- core/SConscript.firmware | 6 +- .../extmod/modtrezorio/modtrezorio-poll.h | 32 +- core/embed/extmod/modtrezorio/modtrezorio.c | 25 +- core/embed/firmware/main.c | 3 +- core/embed/trezorhal/button.c | 40 +++ core/embed/trezorhal/button.h | 34 ++ core/embed/trezorhal/touch.c | 300 +++++++++++++++++- core/embed/trezorhal/touch_1.h | 54 ---- core/embed/trezorhal/touch_T.h | 292 ----------------- core/embed/unix/button.h | 1 + core/embed/unix/touch.c | 80 +++-- core/mocks/generated/trezorio/__init__.pyi | 7 + core/src/trezor/ui/__init__.py | 98 ++++-- 13 files changed, 555 insertions(+), 417 deletions(-) create mode 100644 core/embed/trezorhal/button.c create mode 100644 core/embed/trezorhal/button.h delete mode 100644 core/embed/trezorhal/touch_1.h delete mode 100644 core/embed/trezorhal/touch_T.h create mode 120000 core/embed/unix/button.h diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 80f5fd6367..0acb4e6191 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -347,7 +347,6 @@ SOURCE_TREZORHAL = [ 'embed/trezorhal/rng.c', 'embed/trezorhal/stm32.c', 'embed/trezorhal/systick.c', - 'embed/trezorhal/touch.c', 'embed/trezorhal/usb.c', 'embed/trezorhal/usbd_conf.c', 'embed/trezorhal/usbd_core.c', @@ -360,6 +359,11 @@ if TREZOR_MODEL == 'T': SOURCE_TREZORHAL += [ 'embed/trezorhal/sbu.c', 'embed/trezorhal/sdcard.c', + 'embed/trezorhal/touch.c', + ] +elif TREZOR_MODEL == '1': + SOURCE_TREZORHAL += [ + 'embed/trezorhal/button.c', ] CPPDEFINES_MOD += ['USE_SVC_SHUTDOWN'] diff --git a/core/embed/extmod/modtrezorio/modtrezorio-poll.h b/core/embed/extmod/modtrezorio/modtrezorio-poll.h index c52f7e8d30..e2fe334426 100644 --- a/core/embed/extmod/modtrezorio/modtrezorio-poll.h +++ b/core/embed/extmod/modtrezorio/modtrezorio-poll.h @@ -19,9 +19,11 @@ #include +#include "button.h" #include "display.h" #include "embed/extmod/trezorobj.h" +#define BUTTON_IFACE (254) #define TOUCH_IFACE (255) #define POLL_READ (0x0000) #define POLL_WRITE (0x0100) @@ -37,6 +39,8 @@ /// `list_ref[0]` - the interface number, including the mask /// `list_ref[1]` - for touch event, tuple of: /// (event_type, x_position, y_position) +/// - for button event (T1), tuple of: +/// (event type, button number) /// - for USB read event, received bytes /// /// If timeout occurs, False is returned, True otherwise. @@ -60,7 +64,10 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, const mp_uint_t iface = i & 0x00FF; const mp_uint_t mode = i & 0xFF00; - if (iface == TOUCH_IFACE) { + if (false) { + } +#if TREZOR_MODEL == T + else if (iface == TOUCH_IFACE) { const uint32_t evt = touch_read(); if (evt) { mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); @@ -94,7 +101,28 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, ret->items[1] = MP_OBJ_FROM_PTR(tuple); return mp_const_true; } - } else if (mode == POLL_READ) { + } +#elif TREZOR_MODEL == 1 + else if (iface == BUTTON_IFACE) { + const uint32_t evt = button_read(); + if (evt & (BTN_EVT_DOWN | BTN_EVT_UP)) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + uint32_t etype = (evt >> 24) & 0x3U; // button down/up + uint32_t en = evt & 0xFFFF; // button number + if (display_orientation(-1) == 180) { + en = (en == BTN_LEFT) ? BTN_RIGHT : BTN_LEFT; + } + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(etype); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(en); + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = MP_OBJ_FROM_PTR(tuple); + return mp_const_true; + } + } +#else +#error Unknown Trezor model +#endif + else if (mode == POLL_READ) { if (sectrue == usb_hid_can_read(iface)) { uint8_t buf[64] = {0}; int len = usb_hid_read(iface, buf, sizeof(buf)); diff --git a/core/embed/extmod/modtrezorio/modtrezorio.c b/core/embed/extmod/modtrezorio/modtrezorio.c index 3a8cd8994a..7f6c3bcc5e 100644 --- a/core/embed/extmod/modtrezorio/modtrezorio.c +++ b/core/embed/extmod/modtrezorio/modtrezorio.c @@ -27,6 +27,7 @@ #include +#include "button.h" #include "touch.h" #include "usb.h" @@ -59,6 +60,12 @@ /// TOUCH_MOVE: int # event id of touch move event /// TOUCH_END: int # event id of touch end event +/// BUTTON: int # interface id of button events +/// BUTTON_PRESSED: int # button down event +/// BUTTON_RELEASED: int # button up event +/// BUTTON_LEFT: int # button number of left button +/// BUTTON_RIGHT: int # button number of right button + /// WireInterface = Union[HID, WebUSB] /// if False: @@ -71,6 +78,19 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_fatfs), MP_ROM_PTR(&mod_trezorio_fatfs_module)}, {MP_ROM_QSTR(MP_QSTR_SBU), MP_ROM_PTR(&mod_trezorio_SBU_type)}, {MP_ROM_QSTR(MP_QSTR_sdcard), MP_ROM_PTR(&mod_trezorio_sdcard_module)}, + + {MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)}, + {MP_ROM_QSTR(MP_QSTR_TOUCH_START), MP_ROM_INT((TOUCH_START >> 24) & 0xFFU)}, + {MP_ROM_QSTR(MP_QSTR_TOUCH_MOVE), MP_ROM_INT((TOUCH_MOVE >> 24) & 0xFFU)}, + {MP_ROM_QSTR(MP_QSTR_TOUCH_END), MP_ROM_INT((TOUCH_END >> 24) & 0xFFU)}, +#elif TREZOR_MODEL == 1 + {MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_INT(BUTTON_IFACE)}, + {MP_ROM_QSTR(MP_QSTR_BUTTON_PRESSED), + MP_ROM_INT((BTN_EVT_DOWN >> 24) & 0x3U)}, + {MP_ROM_QSTR(MP_QSTR_BUTTON_RELEASED), + MP_ROM_INT((BTN_EVT_UP >> 24) & 0x3U)}, + {MP_ROM_QSTR(MP_QSTR_BUTTON_LEFT), MP_ROM_INT(BTN_LEFT)}, + {MP_ROM_QSTR(MP_QSTR_BUTTON_RIGHT), MP_ROM_INT(BTN_RIGHT)}, #endif {MP_ROM_QSTR(MP_QSTR_FlashOTP), MP_ROM_PTR(&mod_trezorio_FlashOTP_type)}, @@ -83,11 +103,6 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mod_trezorio_poll_obj)}, {MP_ROM_QSTR(MP_QSTR_POLL_READ), MP_ROM_INT(POLL_READ)}, {MP_ROM_QSTR(MP_QSTR_POLL_WRITE), MP_ROM_INT(POLL_WRITE)}, - - {MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)}, - {MP_ROM_QSTR(MP_QSTR_TOUCH_START), MP_ROM_INT((TOUCH_START >> 24) & 0xFFU)}, - {MP_ROM_QSTR(MP_QSTR_TOUCH_MOVE), MP_ROM_INT((TOUCH_MOVE >> 24) & 0xFFU)}, - {MP_ROM_QSTR(MP_QSTR_TOUCH_END), MP_ROM_INT((TOUCH_END >> 24) & 0xFFU)}, }; STATIC MP_DEFINE_CONST_DICT(mp_module_trezorio_globals, diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index 132eefdd49..660abcdfd9 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -36,6 +36,7 @@ #include "ports/stm32/pendsv.h" #include "bl_check.h" +#include "button.h" #include "common.h" #include "display.h" #include "flash.h" @@ -83,7 +84,7 @@ int main(void) { #if TREZOR_MODEL == 1 display_init(); - touch_init(); + button_init(); #endif #if TREZOR_MODEL == T diff --git a/core/embed/trezorhal/button.c b/core/embed/trezorhal/button.c new file mode 100644 index 0000000000..a766b65a2f --- /dev/null +++ b/core/embed/trezorhal/button.c @@ -0,0 +1,40 @@ +#include STM32_HAL_H +#include "button.h" + +#define BTN_PIN_LEFT GPIO_PIN_5 +#define BTN_PIN_RIGHT GPIO_PIN_2 + +void button_init(void) { + __HAL_RCC_GPIOC_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStructure; + + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLUP; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = BTN_PIN_LEFT | BTN_PIN_RIGHT; + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); +} + +uint32_t button_read(void) { + static char last_left = 0, last_right = 0; + char left = (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, BTN_PIN_LEFT)); + char right = (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, BTN_PIN_RIGHT)); + if (last_left != left) { + last_left = left; + if (left) { + return BTN_EVT_DOWN | BTN_LEFT; + } else { + return BTN_EVT_UP | BTN_LEFT; + } + } + if (last_right != right) { + last_right = right; + if (right) { + return BTN_EVT_DOWN | BTN_RIGHT; + } else { + return BTN_EVT_UP | BTN_RIGHT; + } + } + return 0; +} diff --git a/core/embed/trezorhal/button.h b/core/embed/trezorhal/button.h new file mode 100644 index 0000000000..eccf0982be --- /dev/null +++ b/core/embed/trezorhal/button.h @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +#ifndef TREZORHAL_BUTTON_H +#define TREZORHAL_BUTTON_H + +#include + +#define BTN_EVT_DOWN (1U << 24) +#define BTN_EVT_UP (1U << 25) + +#define BTN_LEFT 0 +#define BTN_RIGHT 1 + +void button_init(void); +uint32_t button_read(void); + +#endif diff --git a/core/embed/trezorhal/touch.c b/core/embed/trezorhal/touch.c index 2002fec6ba..8da5329441 100644 --- a/core/embed/trezorhal/touch.c +++ b/core/embed/trezorhal/touch.c @@ -18,15 +18,301 @@ */ #include STM32_HAL_H + +#include + +#include "common.h" +#include "secbool.h" + #include "touch.h" -#if TREZOR_MODEL == T -#include "touch_T.h" -#elif TREZOR_MODEL == 1 -#include "touch_1.h" -#else -#error Unknown Trezor model -#endif +#define TOUCH_ADDRESS \ + (0x38U << 1) // the HAL requires the 7-bit address to be shifted by one bit +#define TOUCH_PACKET_SIZE 7U +#define EVENT_PRESS_DOWN 0x00U +#define EVENT_CONTACT 0x80U +#define EVENT_LIFT_UP 0x40U +#define EVENT_NO_EVENT 0xC0U +#define GESTURE_NO_GESTURE 0x00U +#define X_POS_MSB (touch_data[3] & 0x0FU) +#define X_POS_LSB (touch_data[4]) +#define Y_POS_MSB (touch_data[5] & 0x0FU) +#define Y_POS_LSB (touch_data[6]) + +static I2C_HandleTypeDef i2c_handle; + +static void touch_default_pin_state(void) { + // set power off and other pins as per section 3.5 of FT6236 datasheet + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, + GPIO_PIN_SET); // CTP_ON/PB10 (active low) i.e.- CTPM power + // off when set/high/log 1 + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); // CTP_I2C_SCL/PB6 + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); // CTP_I2C_SDA/PB7 + HAL_GPIO_WritePin( + GPIOC, GPIO_PIN_4, + GPIO_PIN_RESET); // CTP_INT/PC4 normally an input, but drive low as an + // output while powered off + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, + GPIO_PIN_RESET); // CTP_REST/PC5 (active low) i.e.- CTPM + // held in reset until released + + // set above pins to OUTPUT / NOPULL + GPIO_InitTypeDef GPIO_InitStructure; + + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + GPIO_InitStructure.Pin = GPIO_PIN_4 | GPIO_PIN_5; + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); + + // in-case power was on, or CTPM was active make sure to wait long enough + // for these changes to take effect. a reset needs to be low for + // a minimum of 5ms. also wait for power circuitry to stabilize (if it + // changed). + HAL_Delay(100); // 100ms (being conservative) +} + +static void touch_active_pin_state(void) { + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); // CTP_ON/PB10 + HAL_Delay(10); // we need to wait until the circuit fully kicks-in + + GPIO_InitTypeDef GPIO_InitStructure; + + // configure CTP I2C SCL and SDA GPIO lines (PB6 & PB7) + GPIO_InitStructure.Mode = GPIO_MODE_AF_OD; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = + GPIO_SPEED_FREQ_LOW; // I2C is a KHz bus and low speed is still good into + // the low MHz + GPIO_InitStructure.Alternate = GPIO_AF4_I2C1; + GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + + // PC4 capacitive touch panel module (CTPM) interrupt (INT) input + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLUP; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_PIN_4; + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); + + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET); // release CTPM reset + HAL_Delay(310); // "Time of starting to report point after resetting" min is + // 300ms, giving an extra 10ms +} + +void touch_init(void) { touch_default_pin_state(); } + +void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) { + // enable I2C clock + __HAL_RCC_I2C1_CLK_ENABLE(); + // GPIO have already been initialised by touch_init +} + +void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c) { + __HAL_RCC_I2C1_CLK_DISABLE(); +} + +static void _i2c_init(void) { + if (i2c_handle.Instance) { + return; + } + + i2c_handle.Instance = I2C1; + i2c_handle.Init.ClockSpeed = 200000; + i2c_handle.Init.DutyCycle = I2C_DUTYCYCLE_16_9; + i2c_handle.Init.OwnAddress1 = 0xFE; // master + i2c_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; + i2c_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; + i2c_handle.Init.OwnAddress2 = 0; + i2c_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; + i2c_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; + + if (HAL_OK != HAL_I2C_Init(&i2c_handle)) { + ensure(secfalse, NULL); + return; + } +} + +static void _i2c_deinit(void) { + if (i2c_handle.Instance) { + HAL_I2C_DeInit(&i2c_handle); + i2c_handle.Instance = NULL; + } +} + +static void _i2c_ensure_pin(uint16_t GPIO_Pin, GPIO_PinState PinState) { + HAL_GPIO_WritePin(GPIOB, GPIO_Pin, PinState); + while (HAL_GPIO_ReadPin(GPIOB, GPIO_Pin) != PinState) + ; +} + +// I2C cycle described in section 2.9.7 of STM CD00288116 Errata sheet +// +// https://www.st.com/content/ccc/resource/technical/document/errata_sheet/7f/05/b0/bc/34/2f/4c/21/CD00288116.pdf/files/CD00288116.pdf/jcr:content/translations/en.CD00288116.pdf + +static void _i2c_cycle(void) { + // PIN6 is SCL, PIN7 is SDA + + // 1. Disable I2C peripheral + _i2c_deinit(); + + // 2. Configure SCL/SDA as GPIO OUTPUT Open Drain + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + HAL_Delay(50); + + // 3. Check SCL and SDA High level + _i2c_ensure_pin(GPIO_PIN_6, GPIO_PIN_SET); + _i2c_ensure_pin(GPIO_PIN_7, GPIO_PIN_SET); + // 4+5. Check SDA Low level + _i2c_ensure_pin(GPIO_PIN_7, GPIO_PIN_RESET); + // 6+7. Check SCL Low level + _i2c_ensure_pin(GPIO_PIN_6, GPIO_PIN_RESET); + // 8+9. Check SCL High level + _i2c_ensure_pin(GPIO_PIN_6, GPIO_PIN_SET); + // 10+11. Check SDA High level + _i2c_ensure_pin(GPIO_PIN_7, GPIO_PIN_SET); + + // 12. Configure SCL/SDA as Alternate function Open-Drain + GPIO_InitStructure.Mode = GPIO_MODE_AF_OD; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Alternate = GPIO_AF4_I2C1; + GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + HAL_Delay(50); + + // 13. Set SWRST bit in I2Cx_CR1 register + __HAL_RCC_I2C1_FORCE_RESET(); + HAL_Delay(50); + + // 14. Clear SWRST bit in I2Cx_CR1 register + __HAL_RCC_I2C1_RELEASE_RESET(); + + // 15. Enable the I2C peripheral + _i2c_init(); + HAL_Delay(10); +} + +void touch_power_on(void) { + if (i2c_handle.Instance) { + return; + } + + // turn on CTP circuitry + touch_active_pin_state(); + HAL_Delay(50); + + // I2C device interface configuration + _i2c_init(); + + // set register 0xA4 G_MODE to interrupt polling mode (0x00). basically, CTPM + // keeps this input line (to PC4) low while a finger is on the screen. + uint8_t touch_panel_config[] = {0xA4, 0x00}; + ensure( + sectrue * (HAL_OK == HAL_I2C_Master_Transmit( + &i2c_handle, TOUCH_ADDRESS, touch_panel_config, + sizeof(touch_panel_config), 10)), + NULL); + + touch_sensitivity(0x06); +} + +void touch_power_off(void) { + _i2c_deinit(); + // turn off CTP circuitry + HAL_Delay(50); + touch_default_pin_state(); +} + +void touch_sensitivity(uint8_t value) { + // set panel threshold (TH_GROUP) - default value is 0x12 + uint8_t touch_panel_threshold[] = {0x80, value}; + ensure(sectrue * + (HAL_OK == HAL_I2C_Master_Transmit( + &i2c_handle, TOUCH_ADDRESS, touch_panel_threshold, + sizeof(touch_panel_threshold), 10)), + NULL); +} + +uint32_t touch_is_detected(void) { + // check the interrupt line coming in from the CTPM. + // the line goes low when a touch event is actively detected. + // reference section 1.2 of "Application Note for FT6x06 CTPM". + // we configure the touch controller to use "interrupt polling mode". + return GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4); +} + +uint32_t touch_read(void) { + static uint8_t touch_data[TOUCH_PACKET_SIZE], + previous_touch_data[TOUCH_PACKET_SIZE]; + static uint32_t xy; + static int touching; + + int last_packet = 0; + if (!touch_is_detected()) { + // only poll when the touch interrupt is active. + // when it's inactive, we might need to read one last data packet to get to + // the TOUCH_END event, which clears the `touching` flag. + if (touching) { + last_packet = 1; + } else { + return 0; + } + } + + uint8_t outgoing[] = {0x00}; // start reading from address 0x00 + int result = HAL_I2C_Master_Transmit(&i2c_handle, TOUCH_ADDRESS, outgoing, + sizeof(outgoing), 1); + if (result != HAL_OK) { + if (result == HAL_BUSY) _i2c_cycle(); + return 0; + } + + if (HAL_OK != HAL_I2C_Master_Receive(&i2c_handle, TOUCH_ADDRESS, touch_data, + TOUCH_PACKET_SIZE, 1)) { + return 0; // read failure + } + + if (0 == memcmp(previous_touch_data, touch_data, TOUCH_PACKET_SIZE)) { + return 0; // polled and got the same event again + } else { + memcpy(previous_touch_data, touch_data, TOUCH_PACKET_SIZE); + } + + const uint32_t number_of_touch_points = + touch_data[2] & 0x0F; // valid values are 0, 1, 2 (invalid 0xF before + // first touch) (tested with FT6206) + const uint32_t event_flag = touch_data[3] & 0xC0; + if (touch_data[1] == GESTURE_NO_GESTURE) { + xy = touch_pack_xy((X_POS_MSB << 8) | X_POS_LSB, + (Y_POS_MSB << 8) | Y_POS_LSB); + if ((number_of_touch_points == 1) && (event_flag == EVENT_PRESS_DOWN)) { + touching = 1; + return TOUCH_START | xy; + } else if ((number_of_touch_points == 1) && (event_flag == EVENT_CONTACT)) { + return TOUCH_MOVE | xy; + } else if ((number_of_touch_points == 0) && (event_flag == EVENT_LIFT_UP)) { + touching = 0; + return TOUCH_END | xy; + } + } + + if (last_packet) { + // interrupt line is inactive, we didn't read valid touch data, and as far + // as we know, we never sent a TOUCH_END event. + touching = 0; + return TOUCH_END | xy; + } + + return 0; +} uint32_t touch_click(void) { uint32_t r = 0; diff --git a/core/embed/trezorhal/touch_1.h b/core/embed/trezorhal/touch_1.h deleted file mode 100644 index 2f66c7bf52..0000000000 --- a/core/embed/trezorhal/touch_1.h +++ /dev/null @@ -1,54 +0,0 @@ -#define BTN_PIN_LEFT GPIO_PIN_5 -#define BTN_PIN_RIGHT GPIO_PIN_2 - -#define DISPLAY_RESX 128 -#define DISPLAY_RESY 64 -#define BTN_LEFT_COORDS touch_pack_xy(0, DISPLAY_RESY - 1) -#define BTN_RIGHT_COORDS touch_pack_xy(DISPLAY_RESX - 1, DISPLAY_RESY - 1) - -void touch_init(void) { - __HAL_RCC_GPIOC_CLK_ENABLE(); - - GPIO_InitTypeDef GPIO_InitStructure; - - // PC4 capacitive touch panel module (CTPM) interrupt (INT) input - GPIO_InitStructure.Mode = GPIO_MODE_INPUT; - GPIO_InitStructure.Pull = GPIO_PULLUP; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStructure.Pin = BTN_PIN_LEFT | BTN_PIN_RIGHT; - HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); -} - -void touch_power_on(void) {} - -void touch_power_off(void) {} - -void touch_sensitivity(uint8_t value) { (void)value; } - -uint32_t touch_read(void) { - static char last_left = 0, last_right = 0; - char left = (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, BTN_PIN_LEFT)); - char right = (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, BTN_PIN_RIGHT)); - if (last_left != left) { - last_left = left; - if (left) { - return TOUCH_START | BTN_LEFT_COORDS; - } else { - return TOUCH_END | BTN_LEFT_COORDS; - } - } - if (last_right != right) { - last_right = right; - if (right) { - return TOUCH_START | BTN_RIGHT_COORDS; - } else { - return TOUCH_END | BTN_RIGHT_COORDS; - } - } - return 0; -} - -uint32_t touch_is_detected(void) { - return (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, BTN_PIN_LEFT)) || - (GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, BTN_PIN_RIGHT)); -} diff --git a/core/embed/trezorhal/touch_T.h b/core/embed/trezorhal/touch_T.h deleted file mode 100644 index 1a76ab3ab5..0000000000 --- a/core/embed/trezorhal/touch_T.h +++ /dev/null @@ -1,292 +0,0 @@ -#include - -#include "common.h" -#include "secbool.h" - -#define TOUCH_ADDRESS \ - (0x38U << 1) // the HAL requires the 7-bit address to be shifted by one bit -#define TOUCH_PACKET_SIZE 7U -#define EVENT_PRESS_DOWN 0x00U -#define EVENT_CONTACT 0x80U -#define EVENT_LIFT_UP 0x40U -#define EVENT_NO_EVENT 0xC0U -#define GESTURE_NO_GESTURE 0x00U -#define X_POS_MSB (touch_data[3] & 0x0FU) -#define X_POS_LSB (touch_data[4]) -#define Y_POS_MSB (touch_data[5] & 0x0FU) -#define Y_POS_LSB (touch_data[6]) - -static I2C_HandleTypeDef i2c_handle; - -static void touch_default_pin_state(void) { - // set power off and other pins as per section 3.5 of FT6236 datasheet - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, - GPIO_PIN_SET); // CTP_ON/PB10 (active low) i.e.- CTPM power - // off when set/high/log 1 - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); // CTP_I2C_SCL/PB6 - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); // CTP_I2C_SDA/PB7 - HAL_GPIO_WritePin( - GPIOC, GPIO_PIN_4, - GPIO_PIN_RESET); // CTP_INT/PC4 normally an input, but drive low as an - // output while powered off - HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, - GPIO_PIN_RESET); // CTP_REST/PC5 (active low) i.e.- CTPM - // held in reset until released - - // set above pins to OUTPUT / NOPULL - GPIO_InitTypeDef GPIO_InitStructure; - - GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStructure.Pull = GPIO_NOPULL; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_6 | GPIO_PIN_7; - HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); - GPIO_InitStructure.Pin = GPIO_PIN_4 | GPIO_PIN_5; - HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); - - // in-case power was on, or CTPM was active make sure to wait long enough - // for these changes to take effect. a reset needs to be low for - // a minimum of 5ms. also wait for power circuitry to stabilize (if it - // changed). - HAL_Delay(100); // 100ms (being conservative) -} - -static void touch_active_pin_state(void) { - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); // CTP_ON/PB10 - HAL_Delay(10); // we need to wait until the circuit fully kicks-in - - GPIO_InitTypeDef GPIO_InitStructure; - - // configure CTP I2C SCL and SDA GPIO lines (PB6 & PB7) - GPIO_InitStructure.Mode = GPIO_MODE_AF_OD; - GPIO_InitStructure.Pull = GPIO_NOPULL; - GPIO_InitStructure.Speed = - GPIO_SPEED_FREQ_LOW; // I2C is a KHz bus and low speed is still good into - // the low MHz - GPIO_InitStructure.Alternate = GPIO_AF4_I2C1; - GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7; - HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); - - // PC4 capacitive touch panel module (CTPM) interrupt (INT) input - GPIO_InitStructure.Mode = GPIO_MODE_INPUT; - GPIO_InitStructure.Pull = GPIO_PULLUP; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStructure.Pin = GPIO_PIN_4; - HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); - - HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET); // release CTPM reset - HAL_Delay(310); // "Time of starting to report point after resetting" min is - // 300ms, giving an extra 10ms -} - -void touch_init(void) { touch_default_pin_state(); } - -void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) { - // enable I2C clock - __HAL_RCC_I2C1_CLK_ENABLE(); - // GPIO have already been initialised by touch_init -} - -void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c) { - __HAL_RCC_I2C1_CLK_DISABLE(); -} - -static void _i2c_init(void) { - if (i2c_handle.Instance) { - return; - } - - i2c_handle.Instance = I2C1; - i2c_handle.Init.ClockSpeed = 200000; - i2c_handle.Init.DutyCycle = I2C_DUTYCYCLE_16_9; - i2c_handle.Init.OwnAddress1 = 0xFE; // master - i2c_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; - i2c_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; - i2c_handle.Init.OwnAddress2 = 0; - i2c_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; - i2c_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; - - if (HAL_OK != HAL_I2C_Init(&i2c_handle)) { - ensure(secfalse, NULL); - return; - } -} - -static void _i2c_deinit(void) { - if (i2c_handle.Instance) { - HAL_I2C_DeInit(&i2c_handle); - i2c_handle.Instance = NULL; - } -} - -static void _i2c_ensure_pin(uint16_t GPIO_Pin, GPIO_PinState PinState) { - HAL_GPIO_WritePin(GPIOB, GPIO_Pin, PinState); - while (HAL_GPIO_ReadPin(GPIOB, GPIO_Pin) != PinState) - ; -} - -// I2C cycle described in section 2.9.7 of STM CD00288116 Errata sheet -// -// https://www.st.com/content/ccc/resource/technical/document/errata_sheet/7f/05/b0/bc/34/2f/4c/21/CD00288116.pdf/files/CD00288116.pdf/jcr:content/translations/en.CD00288116.pdf - -static void _i2c_cycle(void) { - // PIN6 is SCL, PIN7 is SDA - - // 1. Disable I2C peripheral - _i2c_deinit(); - - // 2. Configure SCL/SDA as GPIO OUTPUT Open Drain - GPIO_InitTypeDef GPIO_InitStructure; - GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; - GPIO_InitStructure.Pull = GPIO_NOPULL; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7; - HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); - HAL_Delay(50); - - // 3. Check SCL and SDA High level - _i2c_ensure_pin(GPIO_PIN_6, GPIO_PIN_SET); - _i2c_ensure_pin(GPIO_PIN_7, GPIO_PIN_SET); - // 4+5. Check SDA Low level - _i2c_ensure_pin(GPIO_PIN_7, GPIO_PIN_RESET); - // 6+7. Check SCL Low level - _i2c_ensure_pin(GPIO_PIN_6, GPIO_PIN_RESET); - // 8+9. Check SCL High level - _i2c_ensure_pin(GPIO_PIN_6, GPIO_PIN_SET); - // 10+11. Check SDA High level - _i2c_ensure_pin(GPIO_PIN_7, GPIO_PIN_SET); - - // 12. Configure SCL/SDA as Alternate function Open-Drain - GPIO_InitStructure.Mode = GPIO_MODE_AF_OD; - GPIO_InitStructure.Pull = GPIO_NOPULL; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStructure.Alternate = GPIO_AF4_I2C1; - GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7; - HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); - HAL_Delay(50); - - // 13. Set SWRST bit in I2Cx_CR1 register - __HAL_RCC_I2C1_FORCE_RESET(); - HAL_Delay(50); - - // 14. Clear SWRST bit in I2Cx_CR1 register - __HAL_RCC_I2C1_RELEASE_RESET(); - - // 15. Enable the I2C peripheral - _i2c_init(); - HAL_Delay(10); -} - -void touch_power_on(void) { - if (i2c_handle.Instance) { - return; - } - - // turn on CTP circuitry - touch_active_pin_state(); - HAL_Delay(50); - - // I2C device interface configuration - _i2c_init(); - - // set register 0xA4 G_MODE to interrupt polling mode (0x00). basically, CTPM - // keeps this input line (to PC4) low while a finger is on the screen. - uint8_t touch_panel_config[] = {0xA4, 0x00}; - ensure( - sectrue * (HAL_OK == HAL_I2C_Master_Transmit( - &i2c_handle, TOUCH_ADDRESS, touch_panel_config, - sizeof(touch_panel_config), 10)), - NULL); - - touch_sensitivity(0x06); -} - -void touch_power_off(void) { - _i2c_deinit(); - // turn off CTP circuitry - HAL_Delay(50); - touch_default_pin_state(); -} - -void touch_sensitivity(uint8_t value) { - // set panel threshold (TH_GROUP) - default value is 0x12 - uint8_t touch_panel_threshold[] = {0x80, value}; - ensure(sectrue * - (HAL_OK == HAL_I2C_Master_Transmit( - &i2c_handle, TOUCH_ADDRESS, touch_panel_threshold, - sizeof(touch_panel_threshold), 10)), - NULL); -} - -uint32_t touch_is_detected(void) { - // check the interrupt line coming in from the CTPM. - // the line goes low when a touch event is actively detected. - // reference section 1.2 of "Application Note for FT6x06 CTPM". - // we configure the touch controller to use "interrupt polling mode". - return GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4); -} - -uint32_t touch_read(void) { - static uint8_t touch_data[TOUCH_PACKET_SIZE], - previous_touch_data[TOUCH_PACKET_SIZE]; - static uint32_t xy; - static int touching; - - int last_packet = 0; - if (!touch_is_detected()) { - // only poll when the touch interrupt is active. - // when it's inactive, we might need to read one last data packet to get to - // the TOUCH_END event, which clears the `touching` flag. - if (touching) { - last_packet = 1; - } else { - return 0; - } - } - - uint8_t outgoing[] = {0x00}; // start reading from address 0x00 - int result = HAL_I2C_Master_Transmit(&i2c_handle, TOUCH_ADDRESS, outgoing, - sizeof(outgoing), 1); - if (result != HAL_OK) { - if (result == HAL_BUSY) _i2c_cycle(); - return 0; - } - - if (HAL_OK != HAL_I2C_Master_Receive(&i2c_handle, TOUCH_ADDRESS, touch_data, - TOUCH_PACKET_SIZE, 1)) { - return 0; // read failure - } - - if (0 == memcmp(previous_touch_data, touch_data, TOUCH_PACKET_SIZE)) { - return 0; // polled and got the same event again - } else { - memcpy(previous_touch_data, touch_data, TOUCH_PACKET_SIZE); - } - - const uint32_t number_of_touch_points = - touch_data[2] & 0x0F; // valid values are 0, 1, 2 (invalid 0xF before - // first touch) (tested with FT6206) - const uint32_t event_flag = touch_data[3] & 0xC0; - if (touch_data[1] == GESTURE_NO_GESTURE) { - xy = touch_pack_xy((X_POS_MSB << 8) | X_POS_LSB, - (Y_POS_MSB << 8) | Y_POS_LSB); - if ((number_of_touch_points == 1) && (event_flag == EVENT_PRESS_DOWN)) { - touching = 1; - return TOUCH_START | xy; - } else if ((number_of_touch_points == 1) && (event_flag == EVENT_CONTACT)) { - return TOUCH_MOVE | xy; - } else if ((number_of_touch_points == 0) && (event_flag == EVENT_LIFT_UP)) { - touching = 0; - return TOUCH_END | xy; - } - } - - if (last_packet) { - // interrupt line is inactive, we didn't read valid touch data, and as far - // as we know, we never sent a TOUCH_END event. - touching = 0; - return TOUCH_END | xy; - } - - return 0; -} diff --git a/core/embed/unix/button.h b/core/embed/unix/button.h new file mode 120000 index 0000000000..1b202d3c5f --- /dev/null +++ b/core/embed/unix/button.h @@ -0,0 +1 @@ +../trezorhal/button.h \ No newline at end of file diff --git a/core/embed/unix/touch.c b/core/embed/unix/touch.c index 0fb9e9ea66..89cf649dec 100644 --- a/core/embed/unix/touch.c +++ b/core/embed/unix/touch.c @@ -18,22 +18,49 @@ */ #include +#include #include +extern void __shutdown(void); +extern const char *display_save(const char *prefix); + +static bool handle_emulator_events(const SDL_Event *event) { + switch (event->type) { + case SDL_KEYUP: + if (event->key.repeat) { + break; + } + switch (event->key.keysym.sym) { + case SDLK_ESCAPE: + __shutdown(); + return true; + case SDLK_p: + display_save("emu"); + return true; + } + break; + case SDL_QUIT: + __shutdown(); + return true; + } + return false; +} + +#if TREZOR_MODEL == T + #include "touch.h" extern int sdl_display_res_x, sdl_display_res_y; extern int sdl_touch_offset_x, sdl_touch_offset_y; -extern void __shutdown(void); -extern const char *display_save(const char *prefix); - uint32_t touch_read(void) { SDL_Event event; SDL_PumpEvents(); if (SDL_PollEvent(&event) > 0) { + if (handle_emulator_events(&event)) { + return 0; + } switch (event.type) { -#if TREZOR_MODEL == T case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONUP: { @@ -70,45 +97,50 @@ uint32_t touch_read(void) { } break; } -#endif -#if TREZOR_MODEL == 1 + } + } + return 0; +} + +#elif TREZOR_MODEL == 1 + +#include "button.h" + +uint32_t button_read(void) { + SDL_Event event; + SDL_PumpEvents(); + if (SDL_PollEvent(&event) > 0) { + if (handle_emulator_events(&event)) { + return 0; + } + switch (event.type) { case SDL_KEYDOWN: if (event.key.repeat) { break; } switch (event.key.keysym.sym) { case SDLK_LEFT: - return TOUCH_START | touch_pack_xy(0, sdl_display_res_y - 1); + return BTN_EVT_DOWN | BTN_LEFT; case SDLK_RIGHT: - return TOUCH_START | - touch_pack_xy(sdl_display_res_x - 1, sdl_display_res_y - 1); + return BTN_EVT_DOWN | BTN_RIGHT; } break; -#endif case SDL_KEYUP: if (event.key.repeat) { break; } switch (event.key.keysym.sym) { - case SDLK_ESCAPE: - __shutdown(); - break; - case SDLK_p: - display_save("emu"); - break; -#if TREZOR_MODEL == 1 case SDLK_LEFT: - return TOUCH_END | touch_pack_xy(0, sdl_display_res_y - 1); + return BTN_EVT_UP | BTN_LEFT; case SDLK_RIGHT: - return TOUCH_END | - touch_pack_xy(sdl_display_res_x - 1, sdl_display_res_y - 1); -#endif + return BTN_EVT_UP | BTN_RIGHT; } break; - case SDL_QUIT: - __shutdown(); - break; } } return 0; } + +#else +#error Unknown Trezor model +#endif diff --git a/core/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi index 6cefc82172..78a654a786 100644 --- a/core/mocks/generated/trezorio/__init__.pyi +++ b/core/mocks/generated/trezorio/__init__.pyi @@ -76,6 +76,8 @@ def poll(ifaces: Iterable[int], list_ref: list, timeout_ms: int) -> bool: `list_ref[0]` - the interface number, including the mask `list_ref[1]` - for touch event, tuple of: (event_type, x_position, y_position) + - for button event (T1), tuple of: + (event type, button number) - for USB read event, received bytes If timeout occurs, False is returned, True otherwise. """ @@ -194,6 +196,11 @@ TOUCH: int # interface id of the touch events TOUCH_START: int # event id of touch start event TOUCH_MOVE: int # event id of touch move event TOUCH_END: int # event id of touch end event +BUTTON: int # interface id of button events +BUTTON_PRESSED: int # button down event +BUTTON_RELEASED: int # button up event +BUTTON_LEFT: int # button number of left button +BUTTON_RIGHT: int # button number of right button WireInterface = Union[HID, WebUSB] if False: from . import fatfs, sdcard diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py index 1fe8af7cec..5762fde41b 100644 --- a/core/src/trezor/ui/__init__.py +++ b/core/src/trezor/ui/__init__.py @@ -239,30 +239,50 @@ class Component: def __init__(self) -> None: self.repaint = True - def dispatch(self, event: int, x: int, y: int) -> None: - if event is RENDER: - self.on_render() - elif event is io.TOUCH_START: - self.on_touch_start(x, y) - elif event is io.TOUCH_MOVE: - self.on_touch_move(x, y) - elif event is io.TOUCH_END: - self.on_touch_end(x, y) - elif event is REPAINT: - self.repaint = True + if utils.MODEL == "T": + + def dispatch(self, event: int, x: int, y: int) -> None: + if event is RENDER: + self.on_render() + elif event is io.TOUCH_START: + self.on_touch_start(x, y) + elif event is io.TOUCH_MOVE: + self.on_touch_move(x, y) + elif event is io.TOUCH_END: + self.on_touch_end(x, y) + elif event is REPAINT: + self.repaint = True + + def on_touch_start(self, x: int, y: int) -> None: + pass + + def on_touch_move(self, x: int, y: int) -> None: + pass + + def on_touch_end(self, x: int, y: int) -> None: + pass + + elif utils.MODEL == "1": + + def dispatch(self, event: int, x: int, y: int) -> None: + if event is RENDER: + self.on_render() + elif event is io.BUTTON_PRESSED: + self.on_button_pressed(x) + elif event is io.BUTTON_RELEASED: + self.on_button_released(x) + elif event is REPAINT: + self.repaint = True + + def on_button_pressed(self, button_number: int) -> None: + pass + + def on_button_released(self, button_number: int) -> None: + pass def on_render(self) -> None: pass - def on_touch_start(self, x: int, y: int) -> None: - pass - - def on_touch_move(self, x: int, y: int) -> None: - pass - - def on_touch_end(self, x: int, y: int) -> None: - pass - if __debug__: def read_content(self) -> list[str]: @@ -350,17 +370,33 @@ class Layout(Component): Usually overridden to add another tasks to the list.""" return self.handle_input(), self.handle_rendering() - def handle_input(self) -> loop.Task: # type: ignore - """Task that is waiting for the user input.""" - touch = loop.wait(io.TOUCH) - while True: - # Using `yield` instead of `await` to avoid allocations. - event, x, y = yield touch - workflow.idle_timer.touch() - self.dispatch(event, x, y) - # We dispatch a render event right after the touch. Quick and dirty - # way to get the lowest input-to-render latency. - self.dispatch(RENDER, 0, 0) + if utils.MODEL == "T": + + def handle_input(self) -> loop.Task: # type: ignore + """Task that is waiting for the user input.""" + touch = loop.wait(io.TOUCH) + while True: + # Using `yield` instead of `await` to avoid allocations. + event, x, y = yield touch + workflow.idle_timer.touch() + self.dispatch(event, x, y) + # We dispatch a render event right after the touch. Quick and dirty + # way to get the lowest input-to-render latency. + self.dispatch(RENDER, 0, 0) + + elif utils.MODEL == "1": + + def handle_input(self) -> loop.Task: # type: ignore + """Task that is waiting for the user input.""" + button = loop.wait(io.BUTTON) + while True: + event, button_num = yield button + workflow.idle_timer.touch() + self.dispatch(event, button_num, 0) + self.dispatch(RENDER, 0, 0) + + else: + raise ValueError("Unknown Trezor model") def _before_render(self) -> None: # Before the first render, we dim the display.