Skip to content

Commit

Permalink
feat(core/embed): prepare backlight driver for low power mode
Browse files Browse the repository at this point in the history
[no changelog]
  • Loading branch information
cepetr committed Jun 14, 2024
1 parent 450eed6 commit 08bc0fb
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 125 deletions.
272 changes: 169 additions & 103 deletions core/embed/trezorhal/stm32f4/backlight_pwm.c
Original file line number Diff line number Diff line change
@@ -1,212 +1,278 @@
/*
* 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 <string.h>

#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)
// Period of the PWM Timer
#define LED_PWM_TIM_PERIOD (TIM_FREQ / BACKLIGHT_PWM_FREQ)

#define LED_PWM_PRESCALER_SLOW (SystemCoreClock / 1000000 - 1) // 1 MHz

#define LED_PWM_TIM_PERIOD (TIM_FREQ / BACKLIGHT_PWM_FREQ)
// Backlight driver state
typedef struct {
// Set if driver is initialized
bool initialized;
// Current backlight level in range 0-255
backlight_level_t current_level;

static int BACKLIGHT = -1;
} backlight_driver_t;

static int pwm_period = 0;
// Backlight driver instance
backlight_driver_t g_backlight_driver;

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};

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);

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);
}
void backlight_pwm_init(backlight_action_t action) {
backlight_driver_t *drv = &g_backlight_driver;

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
backlight_level_t 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;
GPIO_InitStructure.Alternate = BACKLIGHT_PWM_TIM_AF;
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;

uint32_t tmpccmrx;
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.

// about 10Hz (with PSC = (SystemCoreClock / 1000000) - 1)
#define LED_PWM_SLOW_TIM_PERIOD (10000)
#define LED_PWM_PRESCALER_SLOW (SystemCoreClock / 1000000 - 1) // 1 MHz

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
}

#define LED_PWM_SLOW_TIM_PERIOD \
(10000) // about 10Hz (with PSC = (SystemCoreClock / 1000000) - 1)
drv->initialized = false;
}

void backlight_pwm_set_slow(void) {
uint32_t prev_arr = BACKLIGHT_PWM_TIM->ARR;
uint32_t prev_ccr1 = BACKLIGHT_PWM_TIM->CCR1;
// 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};

uint32_t prev_val = (prev_ccr1 * 255) / (prev_arr + 1);
prev_val = prev_val > 255 ? 255 : prev_val;
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);

hal_delay_us(500);

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;
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;
}
#endif
Loading

0 comments on commit 08bc0fb

Please sign in to comment.