feat(core): touch wake-up feature introduction
What changed, and why it matters
This commit adds a new hardware feature for the Trezor T3W1 device that lets the user wake the device from suspend mode by touching the screen. It changes how the display and touch drivers are turned off and back on, and adds a new wake-up flag for touch events. The feature is currently disabled by default in the build configuration (TOUCH_WAKEUP_ENABLED is set to 0). There is no direct evidence in the commit that this fixes a security vulnerability; it appears to be a normal feature addition.
Treat as a feature commit rather than a security patch. If reviewing for security, verify that the new EXTI interrupt handler cannot be triggered spuriously to wake the device unexpectedly, that touch controller monitor mode re-entry timing does not introduce race conditions, and that the panel_suspend TODOs are completed before the feature is enabled. No immediate security action is indicated by the commit content alone.
Security signals we found
New interrupt-driven wake-up path added to touch driver
Display suspend/resume logic refactored with shared power/reset lines between display and touch controllers
Touch controller placed in monitor mode during suspend with automatic re-entry after 1 second
TODO stubs for panel_suspend on two of three panel variants (lx200d2406a and stm32u5a9j-dk) return false
Feature is disabled by default via TOUCH_WAKEUP_ENABLED=0 in build configs
Evidence from the diff
The commit introduces touch-based wake-up for the T3W1 model. It refactors display initialization/deinitialization into lower-level helper functions (display_init_ll/display_deinit_ll) and adds display_suspend/display_resume APIs. The touch controller (FT3168) is placed into monitor mode during suspend so it can generate an interrupt on touch, and a new EXTI interrupt handler sets WAKEUP_FLAG_TOUCH. The feature is gated by the TOUCH_WAKEUP_ENABLED macro, which is set to 0 in all T3W1 board configurations, making it disabled by default. The commit also updates Python bindings and suspend handling to recognize the new touch wake-up flag.
Changed components
core/embed/io/display/ltdc_dsi/display_driver.ccore/embed/io/display/ltdc_dsi/panels/lx250a2401a/lx250a2401a.ccore/embed/io/touch/ft3168/ft3168.ccore/embed/io/suspend/stm32u5/suspend_io.ccore/embed/models/T3W1 board headerscore/src/trezor/power_management/suspend.pyInspect captured patch +505 / −57
diff --git a/core/embed/io/display/inc/io/display.h b/core/embed/io/display/inc/io/display.h
index 1edac7d6..4415b73f 100644
--- a/core/embed/io/display/inc/io/display.h
+++ b/core/embed/io/display/inc/io/display.h
@@ -70,6 +70,24 @@ bool display_init(display_content_mode_t mode);
// `display_init(DISPLAY_RETAIN_CONTENT)`.
void display_deinit(display_content_mode_t mode);
+#ifdef USE_SUSPEND
+// Wakeup parameters used to restore display state after suspension.
+typedef struct {
+ // Backlight level
+ uint8_t backlight_level;
+} display_wakeup_params_t;
+
+// Suspends the display driver.
+//
+// Saves the current display state into `wakeup_params`.
+void display_suspend(display_wakeup_params_t *wakeup_params);
+
+// Resumes the display driver.
+//
+// Restores the display state from `wakeup_params`.
+void display_resume(const display_wakeup_params_t *wakeup_params);
+#endif // USE_SUSPEND
+
// Allows unprivileged access to the display framebuffer from
// perspective of the GTZC (Global TrustZone Controller).
void display_set_unpriv_access(bool unpriv);
diff --git a/core/embed/io/display/ltdc_dsi/display_driver.c b/core/embed/io/display/ltdc_dsi/display_driver.c
index 91331936..7d0b9b32 100644
--- a/core/embed/io/display/ltdc_dsi/display_driver.c
+++ b/core/embed/io/display/ltdc_dsi/display_driver.c
@@ -313,38 +313,16 @@ bool display_set_fb(uint32_t fb_addr) {
// This implementation does not support `mode` parameter, it
// behaves as if `mode` is always `DISPLAY_RESET_CONTENT`.
-bool display_init(display_content_mode_t mode) {
+static bool display_init_ll(display_content_mode_t mode) {
display_driver_t *drv = &g_display_driver;
- if (drv->initialized) {
- return true;
- }
-
- GPIO_InitTypeDef GPIO_InitStructure = {0};
-
-#ifdef DISPLAY_PWREN_PIN
- DISPLAY_PWREN_CLK_ENA();
- HAL_GPIO_WritePin(DISPLAY_PWREN_PORT, DISPLAY_PWREN_PIN, GPIO_PIN_RESET);
- GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStructure.Pull = GPIO_NOPULL;
- GPIO_InitStructure.Speed = GPIO_SPEED_LOW;
- GPIO_InitStructure.Pin = DISPLAY_PWREN_PIN;
- HAL_GPIO_Init(DISPLAY_PWREN_PORT, &GPIO_InitStructure);
-#endif
-
#ifdef DISPLAY_RESET_PIN
- DISPLAY_RESET_CLK_ENA();
- HAL_GPIO_WritePin(GPIOE, DISPLAY_RESET_PIN, GPIO_PIN_RESET);
- GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStructure.Pull = GPIO_NOPULL;
- GPIO_InitStructure.Speed = GPIO_SPEED_LOW;
- GPIO_InitStructure.Pin = DISPLAY_RESET_PIN;
- HAL_GPIO_Init(DISPLAY_RESET_PORT, &GPIO_InitStructure);
-
+ // Toggle the RESET pin
+ HAL_GPIO_WritePin(DISPLAY_RESET_PORT, DISPLAY_RESET_PIN, GPIO_PIN_RESET);
systick_delay_ms(10);
HAL_GPIO_WritePin(DISPLAY_RESET_PORT, DISPLAY_RESET_PIN, GPIO_PIN_SET);
systick_delay_ms(120);
-#endif
+#endif // DISPLAY_RESET_PIN
#ifdef USE_BACKLIGHT
backlight_init(BACKLIGHT_RESET, GAMMA_EXP);
@@ -357,27 +335,27 @@ bool display_init(display_content_mode_t mode) {
#endif
if (!display_pll_init()) {
- goto cleanup;
+ return false;
}
if (!display_dsi_init(drv)) {
- goto cleanup;
+ return false;
}
if (!display_ltdc_init(drv, fb_addr)) {
- goto cleanup;
+ return false;
}
/* Start DSI */
if (HAL_DSI_Start(&drv->hlcd_dsi) != HAL_OK) {
- goto cleanup;
+ return false;
}
if (!panel_init(drv)) {
- goto cleanup;
+ return false;
}
if (HAL_LTDC_ProgramLineEvent(&drv->hlcd_ltdc, LINE_EVENT_GENERAL_LINE) !=
HAL_OK) {
- goto cleanup;
+ return false;
}
/* Enable LTDC interrupt */
@@ -416,6 +394,48 @@ bool display_init(display_content_mode_t mode) {
drv->refresh_rate_timeout_set = true;
#endif
+ return true;
+}
+
+// This implementation does not support `mode` parameter, it
+// behaves as if `mode` is always `DISPLAY_RESET_CONTENT`.
+bool display_init(display_content_mode_t mode) {
+ display_driver_t *drv = &g_display_driver;
+
+ if (drv->initialized) {
+ return true;
+ }
+
+#ifdef DISPLAY_PWREN_PIN
+ {
+ GPIO_InitTypeDef GPIO_InitStructure = {0};
+ DISPLAY_PWREN_CLK_ENA();
+ HAL_GPIO_WritePin(DISPLAY_PWREN_PORT, DISPLAY_PWREN_PIN, GPIO_PIN_RESET);
+ GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
+ GPIO_InitStructure.Pull = GPIO_NOPULL;
+ GPIO_InitStructure.Speed = GPIO_SPEED_LOW;
+ GPIO_InitStructure.Pin = DISPLAY_PWREN_PIN;
+ HAL_GPIO_Init(DISPLAY_PWREN_PORT, &GPIO_InitStructure);
+ }
+#endif
+
+#ifdef DISPLAY_RESET_PIN
+ {
+ GPIO_InitTypeDef GPIO_InitStructure = {0};
+ DISPLAY_RESET_CLK_ENA();
+ HAL_GPIO_WritePin(DISPLAY_RESET_PORT, DISPLAY_RESET_PIN, GPIO_PIN_RESET);
+ GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
+ GPIO_InitStructure.Pull = GPIO_NOPULL;
+ GPIO_InitStructure.Speed = GPIO_SPEED_LOW;
+ GPIO_InitStructure.Pin = DISPLAY_RESET_PIN;
+ HAL_GPIO_Init(DISPLAY_RESET_PORT, &GPIO_InitStructure);
+ }
+#endif
+
+ if (!display_init_ll(mode)) {
+ goto cleanup;
+ }
+
drv->initialized = true;
return true;
@@ -426,7 +446,7 @@ cleanup:
// This implementation does not support `mode` parameter, it
// behaves as if `mode` is always `DISPLAY_RESET_CONTENT`.
-void display_deinit(display_content_mode_t mode) {
+static void display_deinit_ll(display_content_mode_t mode) {
display_driver_t *drv = &g_display_driver;
gfx_bitblt_deinit();
@@ -448,6 +468,14 @@ void display_deinit(display_content_mode_t mode) {
display_gfxmmu_deinit(drv);
#endif
display_pll_deinit();
+}
+
+// This implementation does not support `mode` parameter, it
+// behaves as if `mode` is always `DISPLAY_RESET_CONTENT`.
+void display_deinit(display_content_mode_t mode) {
+ display_driver_t *drv = &g_display_driver;
+
+ display_deinit_ll(mode);
#ifdef DISPLAY_RESET_PIN
// Release the RESET pin
@@ -604,6 +632,71 @@ void display_refresh_rate_config(void) {
}
#endif // REFRESH_RATE_SCALING_SUPPORTED
+#ifdef USE_SUSPEND
+void display_suspend(display_wakeup_params_t *wakeup_params) {
+#if TOUCH_WAKEUP_ENABLED == 1
+ display_driver_t *drv = &g_display_driver;
+
+ memset(wakeup_params, 0, sizeof(display_wakeup_params_t));
+
+ if (!drv->initialized) {
+ // The driver isn't initialized, wrong control flow applied
+ return;
+ }
+
+ if (!drv->suspended) {
+ drv->wakeup_params.backlight_level = display_get_backlight();
+
+ panel_suspend(drv);
+
+ display_deinit_ll(DISPLAY_RESET_CONTENT);
+
+ drv->suspended = true;
+ }
+
+ memcpy(wakeup_params, &drv->wakeup_params, sizeof(display_wakeup_params_t));
+#else
+ wakeup_params->backlight_level = display_get_backlight();
+ display_deinit(DISPLAY_RESET_CONTENT);
+#endif // TOUCH_WAKEUP_ENABLED
+}
+
+void display_resume(const display_wakeup_params_t *wakeup_params) {
+#if TOUCH_WAKEUP_ENABLED == 1
+ display_driver_t *drv = &g_display_driver;
+
+ if (!drv->initialized) {
+ // The driver isn't initialized, wrong control flow applied
+ return;
+ }
+
+ if (!drv->suspended) {
+ // The driver isn't suspended, nothing to resume
+ return;
+ }
+
+ if (!display_init_ll(DISPLAY_RESET_CONTENT)) {
+ goto cleanup;
+ }
+
+ if (!display_set_backlight(wakeup_params->backlight_level)) {
+ goto cleanup;
+ }
+
+ drv->suspended = false;
+
+ return;
+
+cleanup:
+ display_deinit(DISPLAY_RESET_CONTENT);
+ return;
+#else
+ display_init(DISPLAY_RESET_CONTENT);
+ display_set_backlight(wakeup_params->backlight_level);
+#endif // TOUCH_WAKEUP_ENABLED
+}
+#endif // USE_SUSPEND
+
bool display_set_backlight(uint8_t level) {
display_driver_t *drv = &g_display_driver;
diff --git a/core/embed/io/display/ltdc_dsi/display_internal.h b/core/embed/io/display/ltdc_dsi/display_internal.h
index f9a1777c..cb5a6fea 100644
--- a/core/embed/io/display/ltdc_dsi/display_internal.h
+++ b/core/embed/io/display/ltdc_dsi/display_internal.h
@@ -23,6 +23,8 @@
#include <trezor_bsp.h>
#include <trezor_types.h>
+#include <io/display.h>
+
#include "../fb_queue/fb_queue.h"
#ifdef DISPLAY_PANEL_LX200D2406A
@@ -105,6 +107,13 @@ typedef struct {
GFXMMU_HandleTypeDef hlcd_gfxmmu;
#endif
+#if defined(KERNEL_MODE) && defined(USE_SUSPEND)
+ // Set if the driver is currently suspended
+ bool suspended;
+ // Display wakeup parameters
+ display_wakeup_params_t wakeup_params;
+#endif // KERNEL_MODE && USE_SUSPEND
+
} display_driver_t;
extern display_driver_t g_display_driver;
@@ -125,6 +134,10 @@ void display_ensure_refreshed(void);
bool panel_init(display_driver_t *drv);
+#ifdef USE_SUSPEND
+bool panel_suspend(display_driver_t *drv);
+#endif // USE_SUSPEND
+
#ifdef DISPLAY_GFXMMU
const uint32_t *panel_lut_get(void);
diff --git a/core/embed/io/display/ltdc_dsi/panels/lx200d2406a/lx200d2406a.c b/core/embed/io/display/ltdc_dsi/panels/lx200d2406a/lx200d2406a.c
index e4f05058..8b34cdbc 100644
--- a/core/embed/io/display/ltdc_dsi/panels/lx200d2406a/lx200d2406a.c
+++ b/core/embed/io/display/ltdc_dsi/panels/lx200d2406a/lx200d2406a.c
@@ -250,4 +250,12 @@ bool panel_init(display_driver_t *drv) {
return true;
}
+
+#ifdef USE_SUSPEND
+bool panel_suspend(display_driver_t *drv) {
+ // TODO: Implement proper suspend sequence for this panel
+ return false;
+}
+#endif // USE_SUSPEND
+
#endif // KERNEL_MODE
diff --git a/core/embed/io/display/ltdc_dsi/panels/lx250a2401a/lx250a2401a.c b/core/embed/io/display/ltdc_dsi/panels/lx250a2401a/lx250a2401a.c
index 95135dc8..439e07d7 100644
--- a/core/embed/io/display/ltdc_dsi/panels/lx250a2401a/lx250a2401a.c
+++ b/core/embed/io/display/ltdc_dsi/panels/lx250a2401a/lx250a2401a.c
@@ -523,4 +523,41 @@ bool panel_init(display_driver_t *drv) {
return true;
}
-#endif
+
+#ifdef USE_SUSPEND
+bool panel_suspend(display_driver_t *drv) {
+ HAL_StatusTypeDef ret;
+
+ // 1) DCS Display OFF (panel blanks)
+ ret = HAL_DSI_ShortWrite(&drv->hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, 0x28,
+ 0x00);
+ if (ret != HAL_OK) return false;
+ systick_delay_ms(20);
+
+ // 2) DCS Sleep In (power down internal blocks)
+ ret = HAL_DSI_ShortWrite(&drv->hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, 0x10,
+ 0x00);
+ if (ret != HAL_OK) return false;
+ systick_delay_ms(120);
+
+ // 3) Vendor Deep Standby enter
+ // Enter DSTB Mode Flow:
+ // Step1: 0xFF:0x77/0x01/0x00/0x00/0x00/0x80 (Deep Standby Mode Enable)
+ // Step2: 0xFF:0x77/0x01/0x00/0x00/0x80 (Deep Standby Mode Active)
+ ret = HAL_DSI_LongWrite(&drv->hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 6, 0xFF,
+ (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x00, 0x80});
+ if (ret != HAL_OK) return false;
+
+ ret = HAL_DSI_LongWrite(&drv->hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 5, 0xFF,
+ (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x80});
+ if (ret != HAL_OK) return false;
+
+ // Delay to ensure the panel has entered deep standby mode before proceeding
+ // with power down
+ systick_delay_ms(20);
+
+ return true;
+}
+#endif // USE_SUSPEND
+
+#endif // KERNEL_MODE
diff --git a/core/embed/io/display/ltdc_dsi/panels/stm32u5a9j-dk/stm32u5a9j-dk.c b/core/embed/io/display/ltdc_dsi/panels/stm32u5a9j-dk/stm32u5a9j-dk.c
index 3dccef95..f9cb72e8 100644
--- a/core/embed/io/display/ltdc_dsi/panels/stm32u5a9j-dk/stm32u5a9j-dk.c
+++ b/core/embed/io/display/ltdc_dsi/panels/stm32u5a9j-dk/stm32u5a9j-dk.c
@@ -1,3 +1,22 @@
+/*
+ * 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/>.
+ */
+
#ifdef KERNEL_MODE
#include <trezor_rtl.h>
@@ -1152,4 +1171,12 @@ bool panel_init(display_driver_t* drv) {
return true;
}
-#endif
+
+#ifdef USE_SUSPEND
+bool panel_suspend(display_driver_t* drv) {
+ // TODO: Implement proper suspend sequence for this panel
+ return false;
+}
+#endif // USE_SUSPEND
+
+#endif // KERNEL_MODE
diff --git a/core/embed/io/suspend/inc/io/suspend.h b/core/embed/io/suspend/inc/io/suspend.h
index 6f345a4f..82bf853e 100644
--- a/core/embed/io/suspend/inc/io/suspend.h
+++ b/core/embed/io/suspend/inc/io/suspend.h
@@ -30,6 +30,7 @@ typedef uint16_t wakeup_flags_t;
#define WAKEUP_FLAG_NFC (1 << 3) /** NFC event */
#define WAKEUP_FLAG_RTC (1 << 4) /** RTC wake-up timer */
#define WAKEUP_FLAG_USB (1 << 5) /** USB WIRE communication */
+#define WAKEUP_FLAG_TOUCH (1 << 6) /** Touch pressed */
/**
* @brief Puts device into suspend mode (actually STOP2 mode on STM32U5)
diff --git a/core/embed/io/suspend/inc/io/suspend_io.h b/core/embed/io/suspend/inc/io/suspend_io.h
index d63739dc..005dc569 100644
--- a/core/embed/io/suspend/inc/io/suspend_io.h
+++ b/core/embed/io/suspend/inc/io/suspend_io.h
@@ -19,6 +19,10 @@
#pragma once
+#ifdef USE_DISPLAY
+#include <io/display.h>
+#endif
+
#ifdef USE_BLE
#include <io/ble.h>
#endif
@@ -40,9 +44,9 @@ void suspend_cpu(void);
* used to restore them after wake-up.
*/
typedef struct {
-#ifdef USE_BACKLIGHT
- /** Backlight level */
- uint8_t backlight_level;
+#ifdef USE_DISPLAY
+ /** State of the display driver */
+ display_wakeup_params_t display;
#endif
#ifdef USE_BLE
/** State of the ble driver */
diff --git a/core/embed/io/suspend/stm32u5/suspend_io.c b/core/embed/io/suspend/stm32u5/suspend_io.c
index b5bb78da..306ad498 100644
--- a/core/embed/io/suspend/stm32u5/suspend_io.c
+++ b/core/embed/io/suspend/stm32u5/suspend_io.c
@@ -70,15 +70,14 @@ void suspend_drivers_phase1(power_save_wakeup_params_t *wakeup_params) {
rgb_led_set_wakeup_params(&wakeup_params->rgb_led);
rgb_led_set_color(RGBLED_OFF);
#endif
-#ifdef USE_TOUCH
- touch_deinit();
-#endif
#ifdef USE_BLE
ble_suspend(&wakeup_params->ble);
#endif
+#ifdef USE_TOUCH
+ touch_suspend();
+#endif
#ifdef USE_DISPLAY
- wakeup_params->backlight_level = display_get_backlight();
- display_deinit(DISPLAY_RESET_CONTENT);
+ display_suspend(&wakeup_params->display);
#endif
}
@@ -92,14 +91,13 @@ void suspend_drivers_phase2(void) {
#endif
}
+// Reinitialize all drivers that were stopped earlier
void resume_drivers(const power_save_wakeup_params_t *wakeup_params) {
#ifdef USE_DISPLAY
- // Reinitialize all drivers that were stopped earlier
- display_init(DISPLAY_RESET_CONTENT);
- display_set_backlight(wakeup_params->backlight_level);
+ display_resume(&wakeup_params->display);
#endif
#ifdef USE_TOUCH
- touch_init();
+ touch_resume();
#endif
#ifdef USE_HAPTIC
ts_t status = haptic_init();
diff --git a/core/embed/io/touch/ft3168/ft3168.c b/core/embed/io/touch/ft3168/ft3168.c
index 59806790..11b55ec4 100644
--- a/core/embed/io/touch/ft3168/ft3168.c
+++ b/core/embed/io/touch/ft3168/ft3168.c
@@ -23,8 +23,13 @@
#ifdef KERNEL_MODE
#include <io/touch.h>
+#ifdef USE_SUSPEND
+#include <io/suspend.h>
+#endif
#include <sys/i2c_bus.h>
+#include <sys/irq.h>
#include <sys/logging.h>
+#include <sys/mpu.h>
#include <sys/systick.h>
#include "ft3168.h"
@@ -38,6 +43,9 @@ LOG_DECLARE(touch_driver)
// #define TOUCH_TRACE_REGS
+#define FT3168_P_MONITOR_AUTO_ENTRY_DEFAULT (FT3168_P_MONITOR_AUTO_ENTRY_ON)
+#define FT3168_TIMEENTERMONITOR_DEFAULT 10 // In seconds
+
typedef struct {
// Set if the driver is initialized
secbool initialized;
@@ -53,6 +61,13 @@ typedef struct {
// Last reported touch state
uint32_t state;
+#ifdef USE_SUSPEND
+ // Set if the driver is currently suspended
+ secbool suspended;
+ // EXTI handle for touch interrupt line
+ EXTI_HandleTypeDef exti;
+#endif // USE_SUSPEND
+
} touch_driver_t;
// Touch driver instance
@@ -127,12 +142,10 @@ static secbool ft3168_write_reg(i2c_bus_t* bus, uint8_t reg, uint8_t value) {
// Wake up the touch controller from monitor mode.
//
// The FT3168 touch controller switches from active mode to monitor mode
-// after a period of inactivity (the default setting is ~12s).
-// This feature cannot be disabled (at least in the current controller
-// firmware). When in this mode, it fails to respond to the first I2C command —
-// writes are not ACKed, and reads return 0x00 or garbage data.
-// To avoid this issue, we need to wake up the controller before
-// sending any commands to it.
+// after a period of inactivity. When in this mode, it fails to respond to
+// the first I2C command — writes are not ACKed, and reads return 0x00
+// or garbage data. To avoid this issue, we need to wake up the controller
+// before sending any commands to it.
static void ft3168_wake_up(i2c_bus_t* bus) {
uint8_t temp;
// Wake up the touch controller by reading one of its registers
@@ -143,6 +156,42 @@ static void ft3168_wake_up(i2c_bus_t* bus) {
systick_delay_ms(1);
}
+// Sets the power mode of the touch controller.
+//
+// `mode` can be one of the following values:
+// 0x00 - P_ACTIVE_MODE
+// 0x01 - P_MONITOR_MODE
+// 0x03 - P_HIBERNATE_MODE
+// Returns `sectrue` if the config sequence succeeds or `secfalse` if it fails.
+__attribute__((unused)) static secbool ft3168_power_mode_set(
+ i2c_bus_t* bus, power_mode_t mode) {
+ secbool ret = sectrue;
+
+ // Ensure the touch controller is awake (just a precaution).
+ // DEBUGGING WARNING: after switching the controller to MONITOR mode,
+ // the first I2C command may fail - BE CAREFUL WHEN SETTING BP's.
+ ft3168_wake_up(bus);
+
+ if (P_ACTIVE_MODE == mode) {
+ // Configure the defaults of automatic transition to monitor mode
+ ret &= ft3168_write_reg(bus, FT3168_REG_G_TIMEENTERMONITOR,
+ FT3168_TIMEENTERMONITOR_DEFAULT);
+ ret &= ft3168_write_reg(bus, FT3168_REG_G_CTRL,
+ FT3168_P_MONITOR_AUTO_ENTRY_DEFAULT);
+ } else if (P_MONITOR_MODE == mode) {
+ // Enable the automatic transition to monitor mode after 1s (in case
+ // the touch controller wakes up when it shouldn't)
+ ret &= ft3168_write_reg(bus, FT3168_REG_G_TIMEENTERMONITOR, 1);
+ ret &= ft3168_write_reg(bus, FT3168_REG_G_CTRL,
+ FT3168_P_MONITOR_AUTO_ENTRY_ON);
+ }
+
+ // Set the touch controller to the specified power mode
+ ret &= ft3168_write_reg(bus, FT3168_REG_G_PMODE, (uint8_t)mode);
+
+ return ret;
+}
+
// Powers down the touch controller and puts all
// the pins in the proper state to save power.
static void ft3168_power_down(void) {
@@ -249,10 +298,13 @@ static secbool ft3168_configure(i2c_bus_t* i2c_bus) {
// Set touch controller to the interrupt trigger mode.
// Basically, CTPM generates a pulse when new data is available.
FT3168_REG_G_MODE,
- 0x01,
+ FT3168_INT_TRIG_MODE,
FT3168_REG_TH_GROUP,
TOUCH_SENSITIVITY,
- };
+ FT3168_REG_G_CTRL,
+ FT3168_P_MONITOR_AUTO_ENTRY_DEFAULT,
+ FT3168_REG_G_TIMEENTERMONITOR,
+ FT3168_TIMEENTERMONITOR_DEFAULT};
_Static_assert(sizeof(config) % 2 == 0);
@@ -300,6 +352,7 @@ secbool touch_init(void) {
goto cleanup;
}
+ // Ensure the touch controller is awake (just a precaution).
ft3168_wake_up(driver->i2c_bus);
// Configure the touch controller
@@ -311,6 +364,17 @@ secbool touch_init(void) {
goto cleanup;
}
+#ifdef USE_SUSPEND
+ // Setup interrupt handler (enabled in touch_suspend())
+ EXTI_ConfigTypeDef EXTI_Config = {0};
+ EXTI_Config.GPIOSel = TOUCH_EXTI_INTERRUPT_GPIOSEL;
+ EXTI_Config.Line = TOUCH_EXTI_INTERRUPT_LINE;
+ EXTI_Config.Mode = EXTI_MODE_INTERRUPT;
+ EXTI_Config.Trigger = EXTI_TRIGGER_RISING;
+ HAL_EXTI_SetConfigLine(&driver->exti, &EXTI_Config);
+ NVIC_SetPriority(TOUCH_EXTI_INTERRUPT_NUM, IRQ_PRI_NORMAL);
+#endif // USE_SUSPEND
+
driver->init_ticks = systick_ms();
driver->read_ticks = driver->init_ticks;
driver->initialized = sectrue;
@@ -324,6 +388,13 @@ cleanup:
void touch_deinit(void) {
touch_driver_t* driver = &g_touch_driver;
+
+#ifdef USE_SUSPEND
+ // Disable the interrupt
+ NVIC_DisableIRQ(TOUCH_EXTI_INTERRUPT_NUM);
+ HAL_EXTI_ClearConfigLine(&driver->exti);
+#endif // USE_SUSPEND
+
touch_poll_deinit();
i2c_bus_close(driver->i2c_bus);
if (sectrue == driver->initialized) {
@@ -332,6 +403,82 @@ void touch_deinit(void) {
memset(driver, 0, sizeof(touch_driver_t));
}
+#ifdef USE_SUSPEND
+void touch_suspend(void) {
+#if TOUCH_WAKEUP_ENABLED == 1
+ touch_driver_t* driver = &g_touch_driver;
+
+ if (secfalse == driver->initialized) {
+ // The driver isn't initialized, wrong control flow applied
+ return;
+ }
+
+ if (sectrue == driver->suspended) {
+ // The driver is already suspended
+ return;
+ }
+
+ touch_poll_deinit();
+
+ // Set the touch driver to monitor mode (in case it fails, the controller
+ // will switch to monitor mode anyway after some time of inactivity)
+ ft3168_power_mode_set(driver->i2c_bus, P_MONITOR_MODE);
+
+ driver->suspended = sectrue;
+
+ // Enable the interrupt to wake up on touch
+ __HAL_GPIO_EXTI_CLEAR_FLAG(TOUCH_EXTI_INTERRUPT_PIN);
+ NVIC_ClearPendingIRQ(TOUCH_EXTI_INTERRUPT_NUM);
+ NVIC_EnableIRQ(TOUCH_EXTI_INTERRUPT_NUM);
+#else
+ touch_deinit();
+#endif // TOUCH_WAKEUP_ENABLED
+}
+
+void touch_resume(void) {
+#if TOUCH_WAKEUP_ENABLED == 1
+ touch_driver_t* driver = &g_touch_driver;
+
+ if (secfalse == driver->initialized) {
+ // The driver isn't initialized, wrong control flow applied
+ return;
+ }
+
+ if (secfalse == driver->suspended) {
+ // The driver isn't suspended, nothing to resume
+ return;
+ }
+
+ // Disable the interrupt for normal operation
+ NVIC_DisableIRQ(TOUCH_EXTI_INTERRUPT_NUM);
+
+ // Ensure the touch controller is awake (just a precaution).
+ ft3168_wake_up(driver->i2c_bus);
+
+ // Configure the touch controller (the display and touch controllers share
+ // the same power and reset lines => we need to configure the touch controller
+ // again after resuming from suspend).
+ if (secfalse == ft3168_configure(driver->i2c_bus)) {
+ goto cleanup;
+ }
+
+ if (!touch_poll_init()) {
+ goto cleanup;
+ }
+
+ driver->suspended = secfalse;
+
+ return;
+
+cleanup:
+ touch_deinit();
+ return;
+#else
+ touch_init();
+#endif // TOUCH_WAKEUP_ENABLED
+}
+#endif // USE_SUSPEND
+
void touch_power_set(bool on) {
if (on) {
ft3168_power_up();
@@ -359,6 +506,7 @@ secbool touch_set_sensitivity(uint8_t value) {
touch_driver_t* driver = &g_touch_driver;
if (sectrue == driver->initialized) {
+ // Ensure the touch controller is awake (just a precaution).
ft3168_wake_up(driver->i2c_bus);
return ft3168_write_reg(driver->i2c_bus, FT3168_REG_TH_GROUP, value);
} else {
@@ -381,6 +529,7 @@ uint8_t touch_get_version(void) {
systick_delay_ms(1);
}
+ // Ensure the touch controller is awake (just a precaution).
ft3168_wake_up(driver->i2c_bus);
uint8_t fw_version = 0;
@@ -526,4 +675,24 @@ uint32_t touch_get_state(void) {
return driver->state;
}
+#ifdef USE_SUSPEND
+void TOUCH_EXTI_INTERRUPT_HANDLER(void) {
+ IRQ_LOG_ENTER();
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DEFAULT);
+
+ touch_driver_t* driver = &g_touch_driver;
+
+ // Clear the EXTI line pending bit
+ __HAL_GPIO_EXTI_CLEAR_FLAG(TOUCH_EXTI_INTERRUPT_PIN);
+
+ if (secfalse != driver->initialized && secfalse != driver->suspended) {
+ // Inform the powerctl module about touch press
+ wakeup_flags_set(WAKEUP_FLAG_TOUCH);
+ }
+
+ mpu_restore(mpu_mode);
+ IRQ_LOG_EXIT();
+}
+#endif // USE_SUSPEND
+
#endif // KERNEL_MODE
diff --git a/core/embed/io/touch/ft3168/ft3168.h b/core/embed/io/touch/ft3168/ft3168.h
index 0d9c38ca..02ed0338 100644
--- a/core/embed/io/touch/ft3168/ft3168.h
+++ b/core/embed/io/touch/ft3168/ft3168.h
@@ -48,11 +48,27 @@
// Threshold for touch detection
#define FT3168_REG_TH_GROUP 0x80
+// Monitor mode switch. Allow entry into monitor mode?
+// 0x01: Allow
+// 0x00: Disable
+#define FT3168_REG_G_CTRL 0x86
+
+// No touch to enter monitor delay. If no touch occurs within a specified time,
+// it enters MONITOR mode. This mode needs to be used in conjunction with the
+// "monitor mode switch" parameter. The unit is seconds.
+#define FT3168_REG_G_TIMEENTERMONITOR 0x87
+
// Mode register
// 0x00 - interrupt polling mode
// 0x01 - interrupt trigger mode
#define FT3168_REG_G_MODE 0xA4
+// Chip operating modes. Power consumption mode
+// 0x00: P_ACTIVE
+// 0x01: P_MONITOR
+// 0x03: P_HIBERNATE
+#define FT3168_REG_G_PMODE 0xA5
+
// Firmware version
#define FT3168_REG_FIRMID 0xA6
@@ -70,3 +86,27 @@
// ------------------------------------------------------------
#define FT3168_GESTURE_NONE 0x00
+
+// ------------------------------------------------------------
+// Monitor mode switch (see FT3168_REG_G_CTRL)
+// ------------------------------------------------------------
+
+#define FT3168_P_MONITOR_AUTO_ENTRY_ON 0x01
+#define FT3168_P_MONITOR_AUTO_ENTRY_OFF 0x00
+
+// ------------------------------------------------------------
+// Interrupt modes(see FT3168_REG_G_MODE)
+// ------------------------------------------------------------
+
+#define FT3168_INT_POL_MODE 0x00
+#define FT3168_INT_TRIG_MODE 0x01
+
+// ------------------------------------------------------------
+// Power modes (see FT3168_REG_G_PMODE)
+// ------------------------------------------------------------
+
+typedef enum {
+ P_ACTIVE_MODE = 0x00,
+ P_MONITOR_MODE = 0x01,
+ P_HIBERNATE_MODE = 0x03
+} power_mode_t;
diff --git a/core/embed/io/touch/inc/io/touch.h b/core/embed/io/touch/inc/io/touch.h
index 3077d300..32c1b0d6 100644
--- a/core/embed/io/touch/inc/io/touch.h
+++ b/core/embed/io/touch/inc/io/touch.h
@@ -36,6 +36,19 @@ secbool touch_init(void);
// The function deinitializes touch controller and powers it off.
void touch_deinit();
+#ifdef USE_SUSPEND
+// Suspends the touch driver
+//
+// The function suspends the touch controller.
+void touch_suspend(void);
+
+// Resumes the touch driver
+//
+// The function resumes touch controller's normal functionality as it was
+// before suspension.
+void touch_resume(void);
+#endif // USE_SUSPEND
+
// Powers on/off the touch controller
//
// The function is used to test touch power supply during production.
diff --git a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h
index cb0c514e..a7a53ed5 100644
--- a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h
+++ b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h
@@ -130,6 +130,11 @@
#define TOUCH_I2C_INSTANCE 2
#define TOUCH_INT_PORT GPIOC
#define TOUCH_INT_PIN GPIO_PIN_3
+#define TOUCH_EXTI_INTERRUPT_GPIOSEL EXTI_GPIOC
+#define TOUCH_EXTI_INTERRUPT_LINE EXTI_LINE_3
+#define TOUCH_EXTI_INTERRUPT_PIN GPIO_PIN_3
+#define TOUCH_EXTI_INTERRUPT_NUM EXTI3_IRQn
+#define TOUCH_EXTI_INTERRUPT_HANDLER EXTI3_IRQHandler
#define HAPTIC_CHIP_DRV2624
#define DRV262X_I2C_INSTANCE 2
diff --git a/core/embed/models/T3W1/boards/trezor_t3w1_revB.h b/core/embed/models/T3W1/boards/trezor_t3w1_revB.h
index a4b7aeab..34fda122 100644
--- a/core/embed/models/T3W1/boards/trezor_t3w1_revB.h
+++ b/core/embed/models/T3W1/boards/trezor_t3w1_revB.h
@@ -130,6 +130,11 @@
#define TOUCH_I2C_INSTANCE 2
#define TOUCH_INT_PORT GPIOC
#define TOUCH_INT_PIN GPIO_PIN_3
+#define TOUCH_EXTI_INTERRUPT_GPIOSEL EXTI_GPIOC
+#define TOUCH_EXTI_INTERRUPT_LINE EXTI_LINE_3
+#define TOUCH_EXTI_INTERRUPT_PIN GPIO_PIN_3
+#define TOUCH_EXTI_INTERRUPT_NUM EXTI3_IRQn
+#define TOUCH_EXTI_INTERRUPT_HANDLER EXTI3_IRQHandler
#define HAPTIC_CHIP_DRV2624
#define DRV262X_I2C_INSTANCE 2
diff --git a/core/embed/models/T3W1/boards/trezor_t3w1_revC.h b/core/embed/models/T3W1/boards/trezor_t3w1_revC.h
index 3728b948..4f0cca49 100644
--- a/core/embed/models/T3W1/boards/trezor_t3w1_revC.h
+++ b/core/embed/models/T3W1/boards/trezor_t3w1_revC.h
@@ -148,6 +148,11 @@
#define TOUCH_I2C_INSTANCE 4
#define TOUCH_INT_PORT GPIOC
#define TOUCH_INT_PIN GPIO_PIN_3
+#define TOUCH_EXTI_INTERRUPT_GPIOSEL EXTI_GPIOC
+#define TOUCH_EXTI_INTERRUPT_LINE EXTI_LINE_3
+#define TOUCH_EXTI_INTERRUPT_PIN GPIO_PIN_3
+#define TOUCH_EXTI_INTERRUPT_NUM EXTI3_IRQn
+#define TOUCH_EXTI_INTERRUPT_HANDLER EXTI3_IRQHandler
#define HAPTIC_CHIP_DRV2624
#define DRV262X_I2C_INSTANCE 2
diff --git a/core/embed/projects/prodtest/cmd/prodtest_power_manager.c b/core/embed/projects/prodtest/cmd/prodtest_power_manager.c
index 4852c87f..826fc95f 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_power_manager.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_power_manager.c
@@ -97,6 +97,10 @@ void prodtest_pm_suspend(cli_t* cli) {
strcat(flags_str, "RTC ");
}
+ if (wakeup_flags & WAKEUP_FLAG_TOUCH) {
+ strcat(flags_str, "TOUCH ");
+ }
+
if (wakeup_flags == 0) {
cli_trace(cli, "Woken up by unknown reason.");
}
diff --git a/core/embed/upymod/modtrezorio/modtrezorio-pm.h b/core/embed/upymod/modtrezorio/modtrezorio-pm.h
index 1d735119..cf53b23e 100644
--- a/core/embed/upymod/modtrezorio/modtrezorio-pm.h
+++ b/core/embed/upymod/modtrezorio/modtrezorio-pm.h
@@ -28,6 +28,7 @@
/// WAKEUP_FLAG_NFC: int
/// WAKEUP_FLAG_RTC: int
/// WAKEUP_FLAG_USB: int
+/// WAKEUP_FLAG_TOUCH: int
///
/// # Power manager event flags:
/// EVENT_POWER_STATUS_CHANGED: int
@@ -60,7 +61,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_pm_soc_obj, mod_trezorio_pm_soc);
/// """
/// Suspends the device. Returns wakeup flag. Raises RuntimeError on
/// failure.
-/// Wakeup flags: BUTTON=1, POWER=2, BLE=4, NFC=8, RTC=16
+/// Wakeup flags: BUTTON=1, POWER=2, BLE=4, NFC=8, RTC=16, USB=32, TOUCH=64
/// """
STATIC mp_obj_t mod_trezorio_pm_suspend() {
wakeup_flags_t wakeup_flags = 0;
@@ -139,6 +140,7 @@ STATIC const mp_rom_map_elem_t mod_trezorio_pm_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_WAKEUP_FLAG_NFC), MP_ROM_INT(WAKEUP_FLAG_NFC)},
{MP_ROM_QSTR(MP_QSTR_WAKEUP_FLAG_RTC), MP_ROM_INT(WAKEUP_FLAG_RTC)},
{MP_ROM_QSTR(MP_QSTR_WAKEUP_FLAG_USB), MP_ROM_INT(WAKEUP_FLAG_USB)},
+ {MP_ROM_QSTR(MP_QSTR_WAKEUP_FLAG_TOUCH), MP_ROM_INT(WAKEUP_FLAG_TOUCH)},
// Power manager event flags
{MP_ROM_QSTR(MP_QSTR_EVENT_POWER_STATUS_CHANGED), MP_ROM_INT(1 << 0)},
diff --git a/core/mocks/generated/trezorio/pm.pyi b/core/mocks/generated/trezorio/pm.pyi
index c3cd5199..16d25926 100644
--- a/core/mocks/generated/trezorio/pm.pyi
+++ b/core/mocks/generated/trezorio/pm.pyi
@@ -7,6 +7,7 @@ WAKEUP_FLAG_BLE: int
WAKEUP_FLAG_NFC: int
WAKEUP_FLAG_RTC: int
WAKEUP_FLAG_USB: int
+WAKEUP_FLAG_TOUCH: int
# Power manager event flags:
EVENT_POWER_STATUS_CHANGED: int
@@ -33,7 +34,7 @@ def suspend() -> int:
"""
Suspends the device. Returns wakeup flag. Raises RuntimeError on
failure.
- Wakeup flags: BUTTON=1, POWER=2, BLE=4, NFC=8, RTC=16
+ Wakeup flags: BUTTON=1, POWER=2, BLE=4, NFC=8, RTC=16, USB=32, TOUCH=64
"""
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA.py b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
index f9728c5b..0e4310a6 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
@@ -290,6 +290,7 @@ def configure(
"embed/sec/suspend/inc",
]
defines += [("USE_SUSPEND", "1")]
+ defines += [("TOUCH_WAKEUP_ENABLED", "0")]
if "power_manager" in features_wanted:
sources += [
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revB.py b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
index 8b99c0e7..25a97b45 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revB.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
@@ -304,6 +304,7 @@ def configure(
"embed/sec/suspend/inc",
]
defines += [("USE_SUSPEND", "1")]
+ defines += [("TOUCH_WAKEUP_ENABLED", "0")]
if "power_manager" in features_wanted:
sources += [
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index 30f0113b..7dc889d9 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -302,6 +302,7 @@ def configure(
"embed/sec/suspend/inc",
]
defines += [("USE_SUSPEND", "1")]
+ defines += [("TOUCH_WAKEUP_ENABLED", "0")]
if "power_manager" in features_wanted:
sources += [
diff --git a/core/src/trezor/power_management/suspend.py b/core/src/trezor/power_management/suspend.py
index 9ef26943..477e76ce 100644
--- a/core/src/trezor/power_management/suspend.py
+++ b/core/src/trezor/power_management/suspend.py
@@ -5,6 +5,7 @@ _HANDLED_WAKEUP_FLAGS = (
io.pm.WAKEUP_FLAG_BLE,
io.pm.WAKEUP_FLAG_USB,
io.pm.WAKEUP_FLAG_POWER,
+ io.pm.WAKEUP_FLAG_TOUCH,
)
if __debug__:
@@ -15,6 +16,7 @@ if __debug__:
io.pm.WAKEUP_FLAG_NFC: "NFC",
io.pm.WAKEUP_FLAG_RTC: "RTC",
io.pm.WAKEUP_FLAG_USB: "USB",
+ io.pm.WAKEUP_FLAG_TOUCH: "TOUCH",
}
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.