From 59961984c3e2510d0e20604477181c9c2e09f12e Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 14 Jun 2024 11:48:55 +0200 Subject: [PATCH] feat(core/embed): prepare backlight driver for low power mode [no changelog] --- core/embed/trezorhal/stm32f4/backlight_pwm.c | 277 +++++++++++------- core/embed/trezorhal/stm32f4/backlight_pwm.h | 60 +++- core/embed/trezorhal/stm32f4/common.c | 2 +- .../trezorhal/stm32f4/displays/st7789v.c | 4 +- .../stm32f4/xdisplay/st-7789/display_driver.c | 4 +- 5 files changed, 227 insertions(+), 120 deletions(-) diff --git a/core/embed/trezorhal/stm32f4/backlight_pwm.c b/core/embed/trezorhal/stm32f4/backlight_pwm.c index 7072b26f2d..a65e84e0fb 100644 --- a/core/embed/trezorhal/stm32f4/backlight_pwm.c +++ b/core/embed/trezorhal/stm32f4/backlight_pwm.c @@ -1,63 +1,81 @@ +/* + * 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 . + */ + +#include +#include #include "backlight_pwm.h" +#include "common.h" #include STM32_HAL_H #include TREZOR_BOARD +// Requested PWM Timer clock frequency [Hz] #define TIM_FREQ 10000000 - +// Prescaler divider for the PWM Timer #define LED_PWM_PRESCALER (SystemCoreClock / TIM_FREQ - 1) - -#define LED_PWM_PRESCALER_SLOW (SystemCoreClock / 1000000 - 1) // 1 MHz - +// Period of the PWM Timer #define LED_PWM_TIM_PERIOD (TIM_FREQ / BACKLIGHT_PWM_FREQ) -static int BACKLIGHT = -1; +// Backlight driver state +typedef struct { + // Set if driver is initialized + bool initialized; + // Current backlight level in range 0-255 + int current_level; -static int pwm_period = 0; +} backlight_driver_t; -int backlight_pwm_set(int val) { - if (BACKLIGHT != val && val >= 0 && val <= 255) { - // TPS61043 goes to shutdown when duty cycle is 0 (after 32ms), - // so we need to set GPIO to high for at least 500us - // to wake it up. - if (BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR == 0) { - GPIO_InitTypeDef GPIO_InitStructure = {0}; +// Backlight driver instance +static backlight_driver_t g_backlight_driver = { + .initialized = false, +}; - HAL_GPIO_WritePin(BACKLIGHT_PWM_PORT, BACKLIGHT_PWM_PIN, GPIO_PIN_SET); - // LCD_PWM/PA7 (backlight control) - GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStructure.Pull = GPIO_NOPULL; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN; - HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure); +void backlight_pwm_init(backlight_action_t action) { + backlight_driver_t *drv = &g_backlight_driver; - hal_delay_us(500); - - GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; - GPIO_InitStructure.Pull = GPIO_NOPULL; - GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStructure.Alternate = BACKLIGHT_PWM_TIM_AF; - GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN; - HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure); - } - - BACKLIGHT = val; - BACKLIGHT_PWM_TIM->CCR1 = (pwm_period * val) / 255; + if (drv->initialized) { + return; } - return BACKLIGHT; -} -int backlight_pwm_get(void) { return BACKLIGHT; } + memset(drv, 0, sizeof(backlight_driver_t)); -void backlight_pwm_init(void) { - // init peripherals + int initial_level = 0; + + if (action == BACKLIGHT_RETAIN) { + // We expect the BACKLIGHT_PWM_TIM to be already initialized + // (e.g. by the bootloader or boardloader) + uint32_t prev_arr = BACKLIGHT_PWM_TIM->ARR; + uint32_t prev_ccr1 = BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR; + + initial_level = (prev_ccr1 * 255) / (prev_arr + 1); + if (initial_level > 255) { + initial_level = 255; + } + } + + // Enable peripheral clocks BACKLIGHT_PWM_PORT_CLK_EN(); BACKLIGHT_PWM_TIM_CLK_EN(); + // Initialize PWM GPIO GPIO_InitTypeDef GPIO_InitStructure = {0}; - - // LCD_PWM (backlight control) GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; @@ -65,40 +83,40 @@ void backlight_pwm_init(void) { GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN; HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure); + // Initialize PWM timer + uint32_t tmpcr1 = 0; - /* Select the Counter Mode */ + // Select the Counter Mode tmpcr1 |= TIM_COUNTERMODE_UP; - /* Set the clock division */ + // Set the clock division tmpcr1 |= (uint32_t)TIM_CLOCKDIVISION_DIV1; - /* Set the auto-reload preload */ + // Set the auto-reload preload #ifdef STM32U5 tmpcr1 |= TIM_AUTORELOAD_PRELOAD_DISABLE; #endif BACKLIGHT_PWM_TIM->CR1 = tmpcr1; - /* Set the Autoreload value */ + // Set the Autoreload value BACKLIGHT_PWM_TIM->ARR = (uint32_t)LED_PWM_TIM_PERIOD - 1; - /* Set the Prescaler value */ + // Set the Prescaler value BACKLIGHT_PWM_TIM->PSC = LED_PWM_PRESCALER; - /* Set the Repetition Counter value */ + // Set the Repetition Counter value BACKLIGHT_PWM_TIM->RCR = 0; - /* Generate an update event to reload the Prescaler - and the repetition counter (only for advanced timer) value immediately */ + // Generate an update event to reload the Prescaler + // and the repetition counter (only for advanced timer) value immediately BACKLIGHT_PWM_TIM->EGR = TIM_EGR_UG; - pwm_period = LED_PWM_TIM_PERIOD; - - /* Set the Preload enable bit for channel1 */ + // Set the Preload enable bit for channel1 BACKLIGHT_PWM_TIM->CCMR1 |= TIM_CCMR1_OC1PE; - /* Configure the Output Fast mode */ + // Configure the Output Fast mode BACKLIGHT_PWM_TIM->CCMR1 &= ~TIM_CCMR1_OC1FE; BACKLIGHT_PWM_TIM->CCMR1 |= TIM_OCFAST_DISABLE; @@ -106,107 +124,156 @@ void backlight_pwm_init(void) { uint32_t tmpccer; uint32_t tmpcr2; - /* Get the TIMx CCER register value */ + // Get the TIMx CCER register value tmpccer = BACKLIGHT_PWM_TIM->CCER; - /* Disable the Channel 1: Reset the CC1E Bit */ + // Disable the Channel 1: Reset the CC1E Bit BACKLIGHT_PWM_TIM->CCER &= ~TIM_CCER_CC1E; tmpccer |= TIM_CCER_CC1E; - /* Get the TIMx CR2 register value */ + // Get the TIMx CR2 register value tmpcr2 = BACKLIGHT_PWM_TIM->CR2; - /* Get the TIMx CCMR1 register value */ + // Get the TIMx CCMR1 register value tmpccmrx = BACKLIGHT_PWM_TIM->CCMR1; - /* Reset the Output Compare Mode Bits */ + // Reset the Output Compare Mode Bits tmpccmrx &= ~TIM_CCMR1_OC1M; tmpccmrx &= ~TIM_CCMR1_CC1S; - /* Select the Output Compare Mode */ + // Select the Output Compare Mode tmpccmrx |= BACKLIGHT_PWM_TIM_OCMODE; - /* Reset the Output Polarity level */ + // Reset the Output Polarity level tmpccer &= ~TIM_CCER_CC1P; - /* Set the Output Compare Polarity */ + // Set the Output Compare Polarity tmpccer |= TIM_OCPOLARITY_HIGH; if (IS_TIM_CCXN_INSTANCE(BACKLIGHT_PWM_TIM, TIM_CHANNEL_1)) { - /* Check parameters */ + // Check parameters assert_param(IS_TIM_OCN_POLARITY(OC_Config->OCNPolarity)); - /* Reset the Output N Polarity level */ + // Reset the Output N Polarity level tmpccer &= ~TIM_CCER_CC1NP; - /* Set the Output N Polarity */ + // Set the Output N Polarity tmpccer |= TIM_OCNPOLARITY_HIGH; - /* Set the Output N State */ + // Set the Output N State tmpccer |= TIM_CCER_CC1NE; } if (IS_TIM_BREAK_INSTANCE(BACKLIGHT_PWM_TIM)) { - /* Check parameters */ + // Check parameters assert_param(IS_TIM_OCNIDLE_STATE(OC_Config->OCNIdleState)); assert_param(IS_TIM_OCIDLE_STATE(OC_Config->OCIdleState)); - /* Reset the Output Compare and Output Compare N IDLE State */ + // Reset the Output Compare and Output Compare N IDLE State tmpcr2 &= ~TIM_CR2_OIS1; tmpcr2 &= ~TIM_CR2_OIS1N; - /* Set the Output Idle state */ + // Set the Output Idle state tmpcr2 |= TIM_OCIDLESTATE_SET; - /* Set the Output N Idle state */ + // Set the Output N Idle state tmpcr2 |= TIM_OCNIDLESTATE_SET; } - /* Write to TIMx CR2 */ + // Write to TIMx CR2 BACKLIGHT_PWM_TIM->CR2 = tmpcr2; - - /* Write to TIMx CCMR1 */ + // Write to TIMx CCMR1 BACKLIGHT_PWM_TIM->CCMR1 = tmpccmrx; - - /* Set the Capture Compare Register value */ + // Set the Capture Compare Register value BACKLIGHT_PWM_TIM->CCR1 = 0; - - /* Write to TIMx CCER */ + // Write to TIMx CCER BACKLIGHT_PWM_TIM->CCER = tmpccer; - backlight_pwm_set(0); - BACKLIGHT_PWM_TIM->BDTR |= TIM_BDTR_MOE; BACKLIGHT_PWM_TIM->CR1 |= TIM_CR1_CEN; + + drv->initialized = true; + + backlight_pwm_set(initial_level); } -void backlight_pwm_reinit(void) { - uint32_t prev_arr = BACKLIGHT_PWM_TIM->ARR; - uint32_t prev_ccr1 = BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR; +void backlight_pwm_deinit(backlight_action_t action) { + backlight_driver_t *drv = &g_backlight_driver; - uint32_t prev_val = (prev_ccr1 * 255) / (prev_arr + 1); - prev_val = prev_val > 255 ? 255 : prev_val; - BACKLIGHT = prev_val; + if (!drv->initialized) { + return; + } - pwm_period = LED_PWM_TIM_PERIOD; - BACKLIGHT_PWM_TIM->PSC = LED_PWM_PRESCALER; - BACKLIGHT_PWM_TIM->CR1 |= TIM_CR1_ARPE; - BACKLIGHT_PWM_TIM->CR2 |= TIM_CR2_CCPC; - BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR = (pwm_period * prev_val) / 255; - BACKLIGHT_PWM_TIM->ARR = LED_PWM_TIM_PERIOD - 1; -} + if (action == BACKLIGHT_RETAIN) { + // We keep both the GPIO and the timer running #ifdef TREZOR_MODEL_T + // This code here is for backward compatibility with the old + // bootloader that used a different PWM settings. -#define LED_PWM_SLOW_TIM_PERIOD \ - (10000) // about 10Hz (with PSC = (SystemCoreClock / 1000000) - 1) +// about 10Hz (with PSC = (SystemCoreClock / 1000000) - 1) +#define LED_PWM_SLOW_TIM_PERIOD (10000) +#define LED_PWM_PRESCALER_SLOW (SystemCoreClock / 1000000 - 1) // 1 MHz -void backlight_pwm_set_slow(void) { - uint32_t prev_arr = BACKLIGHT_PWM_TIM->ARR; - uint32_t prev_ccr1 = BACKLIGHT_PWM_TIM->CCR1; - - uint32_t prev_val = (prev_ccr1 * 255) / (prev_arr + 1); - prev_val = prev_val > 255 ? 255 : prev_val; - - pwm_period = LED_PWM_SLOW_TIM_PERIOD; - BACKLIGHT_PWM_TIM->PSC = LED_PWM_PRESCALER_SLOW; - BACKLIGHT_PWM_TIM->CR1 |= TIM_CR1_ARPE; - BACKLIGHT_PWM_TIM->CR2 |= TIM_CR2_CCPC; - BACKLIGHT_PWM_TIM->ARR = LED_PWM_SLOW_TIM_PERIOD - 1; - BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR = pwm_period * prev_val / 255; -} + BACKLIGHT_PWM_TIM->PSC = LED_PWM_PRESCALER_SLOW; + BACKLIGHT_PWM_TIM->CR1 |= TIM_CR1_ARPE; + BACKLIGHT_PWM_TIM->CR2 |= TIM_CR2_CCPC; + BACKLIGHT_PWM_TIM->ARR = LED_PWM_SLOW_TIM_PERIOD - 1; + BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR = + (LED_PWM_SLOW_TIM_PERIOD * drv->current_level) / 255; #endif + } else { + // TODO: deinitialize GPIOs and the TIMER + } + + drv->initialized = false; +} + +// Generate a pulse on the backlight control pin to wake up the TPS61043 +static void backlight_pwm_wakeup_pulse(void) { + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + HAL_GPIO_WritePin(BACKLIGHT_PWM_PORT, BACKLIGHT_PWM_PIN, GPIO_PIN_SET); + + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN; + HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure); + + hal_delay_us(500); + + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Alternate = BACKLIGHT_PWM_TIM_AF; + GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN; + HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure); +} + +int backlight_pwm_set(int level) { + backlight_driver_t *drv = &g_backlight_driver; + + if (!drv->initialized) { + return 0; + } + + if (level >= 0 && level <= 255) { + // TPS61043 goes to shutdown when duty cycle is 0 (after 32ms), + // so we need to set GPIO to high for at least 500us + // to wake it up. + if (BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR == 0 && level != 0) { + backlight_pwm_wakeup_pulse(); + } + + BACKLIGHT_PWM_TIM->CCR1 = (LED_PWM_TIM_PERIOD * level) / 255; + + drv->current_level = level; + } + + return drv->current_level; +} + +int backlight_pwm_get(void) { + backlight_driver_t *drv = &g_backlight_driver; + + if (!drv->initialized) { + return 0; + } + + return drv->current_level; +} diff --git a/core/embed/trezorhal/stm32f4/backlight_pwm.h b/core/embed/trezorhal/stm32f4/backlight_pwm.h index 696c129d01..49f7c808c0 100644 --- a/core/embed/trezorhal/stm32f4/backlight_pwm.h +++ b/core/embed/trezorhal/stm32f4/backlight_pwm.h @@ -1,17 +1,57 @@ +/* + * 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 CORE_BACKLIGHT_H -#define CORE_BACKLIGHT_H +#ifndef TREZORHAL_BACKLIGHT_H +#define TREZORHAL_BACKLIGHT_H -#include "common.h" +// Action to be taken when initializing or +// deinitializing the backlight driver +typedef enum { + BACKLIGHT_RESET, + BACKLIGHT_RETAIN, +} backlight_action_t; +// Initialize the backlight driver +// +// If the action is set to `BACKLIGHT_RESET`, the backlight level +// is set to zero level. If the action is set to `BACKLIGHT_RETAIN`, +// the backlight level is not changed (if possible). +void backlight_pwm_init(backlight_action_t action); + +// Deinitialize the backlight driver +// +// If the action is set to `BACKLIGHT_RESET`, the backlight driver +// is completely deinitialized. If the action is set to `BACKLIGHT_RETAIN`, +// the driver is deinitialized as much as possible but the backlight +// is kept on. +void backlight_pwm_deinit(backlight_action_t action); + +// Sets the backlight level in range 0-255 and returns the actual level set. +// +// If the level is outside the range, the function has no effect +// and just returns the actual level set. If the backlight driver +// is not initialized, the function returns 0. int backlight_pwm_set(int val); +// Gets the backlight level in range 0-255 +// +// Returns 0 if the backlight driver is not initialized. int backlight_pwm_get(void); -void backlight_pwm_init(void); - -void backlight_pwm_reinit(void); - -void backlight_pwm_set_slow(void); - -#endif // CORE_BACKLIGHT_H +#endif // TREZORHAL_BACKLIGHT_H diff --git a/core/embed/trezorhal/stm32f4/common.c b/core/embed/trezorhal/stm32f4/common.c index 70e4828c7b..88c397c27c 100644 --- a/core/embed/trezorhal/stm32f4/common.c +++ b/core/embed/trezorhal/stm32f4/common.c @@ -129,7 +129,7 @@ void ensure_compatible_settings(void) { #endif display_orientation(0); set_core_clock(CLOCK_168_MHZ); - backlight_pwm_set_slow(); + backlight_pwm_deinit(BACKLIGHT_RETAIN); #endif } diff --git a/core/embed/trezorhal/stm32f4/displays/st7789v.c b/core/embed/trezorhal/stm32f4/displays/st7789v.c index b0b03d9e1c..4e3c0fb99c 100644 --- a/core/embed/trezorhal/stm32f4/displays/st7789v.c +++ b/core/embed/trezorhal/stm32f4/displays/st7789v.c @@ -397,7 +397,7 @@ void display_init(void) { __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_FMC_CLK_ENABLE(); - backlight_pwm_init(); + backlight_pwm_init(BACKLIGHT_RESET); #ifdef STM32F4 #define DISPLAY_GPIO_SPEED GPIO_SPEED_FREQ_VERY_HIGH @@ -477,7 +477,7 @@ void display_reinit(void) { DISPLAY_ORIENTATION = 0; panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); - backlight_pwm_reinit(); + backlight_pwm_init(BACKLIGHT_RETAIN); #ifdef TREZOR_MODEL_T uint32_t id = display_identify(); diff --git a/core/embed/trezorhal/stm32f4/xdisplay/st-7789/display_driver.c b/core/embed/trezorhal/stm32f4/xdisplay/st-7789/display_driver.c index 74a49e4f1a..e68851858f 100644 --- a/core/embed/trezorhal/stm32f4/xdisplay/st-7789/display_driver.c +++ b/core/embed/trezorhal/stm32f4/xdisplay/st-7789/display_driver.c @@ -51,7 +51,7 @@ void display_init(void) { display_io_init_fmc(); display_panel_init(); display_panel_set_little_endian(); - backlight_pwm_init(); + backlight_pwm_init(BACKLIGHT_RESET); #ifdef XFRAMEBUFFER display_io_init_te_interrupt(); @@ -69,7 +69,7 @@ void display_reinit(void) { // Important for model T as this is not set in boardloader display_panel_set_little_endian(); display_panel_init_gamma(); - backlight_pwm_reinit(); + backlight_pwm_init(BACKLIGHT_RETAIN); #ifdef XFRAMEBUFFER display_io_init_te_interrupt();