Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backlight driver refactoring #3950

Merged
merged 1 commit into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 171 additions & 104 deletions core/embed/trezorhal/stm32f4/backlight_pwm.c
Original file line number Diff line number Diff line change
@@ -1,212 +1,279 @@
/*
* 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)

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

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);
}
// Backlight driver instance
static backlight_driver_t g_backlight_driver = {
.initialized = false,
};

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

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

void backlight_pwm_init(void) {
// init peripherals
// 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

#define LED_PWM_SLOW_TIM_PERIOD \
(10000) // about 10Hz (with PSC = (SystemCoreClock / 1000000) - 1)
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
}

void backlight_pwm_set_slow(void) {
uint32_t prev_arr = BACKLIGHT_PWM_TIM->ARR;
uint32_t prev_ccr1 = BACKLIGHT_PWM_TIM->CCR1;
drv->initialized = false;
}

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

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