refactor(core): extract backlight gamma correction to common file
What changed, and why it matters
This commit is a code cleanup: it moves the existing backlight brightness math into a shared header file and updates two hardware-specific backlight drivers to use it. There is no user-facing change, no bug fix, and no security change. It is purely a refactoring to avoid duplicating the same gamma-correction formula.
No security action required; treat as routine refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change extracts inline gamma_correction() implementations from core/embed/io/backlight/stm32/backlight_pwm.c and core/embed/io/backlight/stm32u5/tps61062.c into a new common header core/embed/io/backlight/backlight_gamma.h, adding an inverse function backlight_gamma_uncorrect(). The new uncorrect logic includes a small round-trip bias correction but is functionally equivalent to the previous inline inversion in backlight_pwm.c. No security boundary, cryptographic primitive, memory allocation, or user input parsing is modified.
Changed components
core/embed/io/backlight/backlight_gamma.hcore/embed/io/backlight/stm32/backlight_pwm.ccore/embed/io/backlight/stm32u5/tps61062.cInspect captured patch +105 / −64
diff --git a/core/embed/io/backlight/backlight_gamma.h b/core/embed/io/backlight/backlight_gamma.h
new file mode 100644
index 00000000..aef58650
--- /dev/null
+++ b/core/embed/io/backlight/backlight_gamma.h
@@ -0,0 +1,93 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include <math.h>
+
+// Applies gamma correction to a brightness input value.
+//
+// eq: OUT = ( ( (IN - k) / d ) ^ GAMMA) * q
+//
+// Parameters:
+// in - Input brightness value (e.g., 0-255).
+// in_offset - Minimum input value (k in the equation),
+// below which input is clamped.
+// in_max - Maximum input value (d + k in the equation).
+// gamma_exp - Gamma exponent (GAMMA in the equation).
+// out_max - Maximum output value (q in the equation).
+//
+// The transformation performed is:
+// OUT = ( ( (max(IN, in_offset) - in_offset) / (in_max - in_offset) ) ^
+// gamma_exp) * out_max
+//
+// This normalizes the input, applies gamma correction, and scales to the output
+// range.
+static inline uint32_t backlight_gamma_correct(uint8_t in, uint8_t in_offset,
+ uint8_t in_max, float gamma_exp,
+ uint32_t out_max) {
+ uint8_t clamped = in < in_offset ? in_offset : in;
+
+ float out = (float)(clamped - in_offset) /
+ (in_max - in_offset); // Input normalization to <0;1>
+ out = powf(out, gamma_exp); // Gamma correction
+ out = out * out_max; // Output denormalization to <0;out_max>
+
+ return (uint32_t)out;
+}
+
+// Inverts backlight_gamma_correct(), recovering the original brightness input
+// from a gamma-corrected output value.
+//
+// eq: IN = ( (OUT / q) ^ (1 / GAMMA) ) * d + k
+//
+// Parameters mirror backlight_gamma_correct():
+// out - Gamma-corrected output value (e.g., a PWM duty), 0..out_max.
+// in_offset - Minimum input value (k in the equation).
+// in_max - Maximum input value (d + k in the equation).
+// gamma_exp - Gamma exponent (GAMMA in the equation), must be non-zero.
+// out_max - Maximum output value (q in the equation).
+//
+// The result is clamped to the <in_offset; in_max> range.
+static inline uint8_t backlight_gamma_uncorrect(uint32_t out, uint8_t in_offset,
+ uint8_t in_max, float gamma_exp,
+ uint32_t out_max) {
+ float norm = (float)out / out_max; // Output normalization to <0;1>
+ if (norm > 1.0f) {
+ norm = 1.0f;
+ }
+ norm = powf(norm, 1.0f / gamma_exp); // Invert gamma correction
+
+ uint32_t in = in_offset + (uint32_t)(norm * (in_max - in_offset));
+
+ // backlight_gamma_correct() floors (cast to uint32_t), so it is not
+ // invertible: this inverse systematically underestimates by up to one input
+ // step. Correct that bias: if in+1 maps back to the same output, it is a
+ // better preimage (same brightness, higher API value), which keeps the
+ // correct/uncorrect round-trip stable on BACKLIGHT_RETAIN.
+ if (in < in_max &&
+ backlight_gamma_correct((uint8_t)(in + 1), in_offset, in_max, gamma_exp,
+ out_max) == out) {
+ in++;
+ }
+
+ return in < in_max ? (uint8_t)in : in_max;
+}
diff --git a/core/embed/io/backlight/stm32/backlight_pwm.c b/core/embed/io/backlight/stm32/backlight_pwm.c
index 751c3ebb..e28b7e1d 100644
--- a/core/embed/io/backlight/stm32/backlight_pwm.c
+++ b/core/embed/io/backlight/stm32/backlight_pwm.c
@@ -24,7 +24,7 @@
#include <io/backlight.h>
-#include <math.h>
+#include "../backlight_gamma.h"
// The backlight is built from several LED strings sharing a common anode. Each
// string returns through its own MCU pin acting as a low-side switch, so the
@@ -64,24 +64,6 @@ static backlight_driver_t g_backlight_driver = {
.initialized = false,
};
-// Applies gamma correction to a brightness input value and scales it to the
-// PWM compare range.
-//
-// OUT = ( ( (max(IN, in_offset) - in_offset) / (in_max - in_offset) ) ^
-// gamma_exp) * out_max
-static inline uint32_t gamma_correction(uint8_t in, uint8_t in_offset,
- uint8_t in_max, float gamma_exp,
- uint32_t out_max) {
- float out;
-
- out = (float)(MAX(in, in_offset) - in_offset) /
- (in_max - in_offset); // Input normalization to <0;1>
- out = powf(out, gamma_exp); // Gamma correction
- out = out * out_max; // Output denormalization to <0;out_max>
-
- return (uint32_t)out;
-}
-
bool backlight_init(backlight_action_t action, float gamma_exp) {
backlight_driver_t *drv = &g_backlight_driver;
@@ -100,14 +82,11 @@ bool backlight_init(backlight_action_t action, float gamma_exp) {
uint32_t arr = BACKLIGHT_PWM_TIM->ARR;
uint32_t ccr = __HAL_TIM_GET_COMPARE(&tim, g_pwm_channels[0]);
// The compare register holds a gamma-corrected PWM duty (see
- // backlight_set / backlight_gamma_correct), so invert the gamma curve to
- // recover the original linear brightness level. Feeding the raw duty in as
- // if it were linear would gamma-correct it a second time.
- float duty = (float)ccr / (arr + 1); // normalize duty to <0;1>
- float linear = powf(duty, 1.0f / gamma_exp); // invert gamma correction
- uint32_t level =
- (uint32_t)(INPUT_OFFSET + linear * (BACKLIGHT_MAX_LEVEL - INPUT_OFFSET));
- initial_level = MIN(level, BACKLIGHT_MAX_LEVEL);
+ // backlight_set), so invert the gamma curve to recover the original linear
+ // brightness level. Feeding the raw duty in as if it were linear would
+ // gamma-correct it again.
+ initial_level = backlight_gamma_uncorrect(
+ ccr, INPUT_OFFSET, BACKLIGHT_MAX_LEVEL, gamma_exp, arr + 1);
}
memset(drv, 0, sizeof(backlight_driver_t));
@@ -212,8 +191,8 @@ bool backlight_set(uint8_t val) {
uint32_t pulse = 0;
if (level >= INPUT_OFFSET) {
- pulse = gamma_correction(level, INPUT_OFFSET, BACKLIGHT_MAX_LEVEL,
- drv->gamma_exp, BACKLIGHT_PWM_TIM_PERIOD);
+ pulse = backlight_gamma_correct(level, INPUT_OFFSET, BACKLIGHT_MAX_LEVEL,
+ drv->gamma_exp, BACKLIGHT_PWM_TIM_PERIOD);
}
// The channels drive a shared-anode backlight in lockstep, so their duty
diff --git a/core/embed/io/backlight/stm32u5/tps61062.c b/core/embed/io/backlight/stm32u5/tps61062.c
index 6fb33a67..6c712c42 100644
--- a/core/embed/io/backlight/stm32u5/tps61062.c
+++ b/core/embed/io/backlight/stm32u5/tps61062.c
@@ -27,7 +27,7 @@
#include <io/backlight.h>
-#include <math.h>
+#include "../backlight_gamma.h"
#define BACKLIGHT_CONTROL_T_UP_US 30 // may be in range 1-75
#define BACKLIGHT_CONTROL_T_DOWN_US 198 // may be in range 180-300
@@ -180,37 +180,6 @@ static inline void buffer_steps_duty_cycle_set(uint8_t buf_idx) {
drv->pwm_data_dirty[buf_idx] = true;
}
-// Applies gamma correction to a brightness input value.
-//
-// eq: OUT = ( ( (IN - k) / d ) ^ GAMMA) * q
-//
-// Parameters:
-// in - Input brightness value (e.g., 0-255).
-// in_offset - Minimum input value (k in the equation),
-// below which input is clamped.
-// in_max - Maximum input value (d + k in the equation).
-// gamma_exp - Gamma exponent (GAMMA in the equation).
-// out_max - Maximum output value (q in the equation).
-//
-// The transformation performed is:
-// OUT = ( ( (max(IN, in_offset) - in_offset) / (in_max - in_offset) ) ^
-// gamma_exp) * out_max
-//
-// This normalizes the input, applies gamma correction, and scales to the output
-// range.
-static inline uint32_t gamma_correction(uint8_t in, uint8_t in_offset,
- uint8_t in_max, float gamma_exp,
- uint32_t out_max) {
- float out;
-
- out = (float)(MAX(in, in_offset) - in_offset) /
- (in_max - in_offset); // Input normalization to <0;1>
- out = powf(out, gamma_exp); // Gamma correction
- out = out * out_max; // Output denormalization to <0;out_max>
-
- return (uint32_t)out;
-}
-
bool backlight_init(backlight_action_t action, float gamma_exp) {
backlight_driver_t *drv = &g_backlight_driver;
@@ -407,9 +376,9 @@ bool backlight_set(uint8_t val) {
drv->requested_level_limited = requested_level_limited;
// Perform gamma correction of the requested level
- drv->requested_level_corrected =
- gamma_correction(drv->requested_level_limited, INPUT_OFFSET,
- BACKLIGHT_MAX_LEVEL, drv->gamma_exp, USTEPS_COUNT);
+ drv->requested_level_corrected = backlight_gamma_correct(
+ drv->requested_level_limited, INPUT_OFFSET, BACKLIGHT_MAX_LEVEL,
+ drv->gamma_exp, USTEPS_COUNT);
// Calculate the mapping of requested level to steps (quotient)
drv->requested_step =
Why this scored 15/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.