feat(core): implementation of higher granularity of TS7 backlight.
What changed, and why it matters
This commit rewrites the backlight driver for the Trezor Safe hardware wallet's display boost circuit. It changes from 32 coarse brightness steps to 256 smoother steps by using a DMA-linked-list double-buffer to stream PWM pulse widths to the LED driver chip. The change is a feature implementation, not a security fix. There are some code-quality red flags—unused return values, commented-out error handlers, TODOs, and a commented-out infinite loop on a serious error—but nothing in the commit itself demonstrates an exploitable vulnerability.
Treat as a normal feature commit, not a security patch. If reviewing for product safety, verify that (1) HAL_DMAEx_List return codes are checked and failures fail safe (backlight off), (2) the commented-out DMA error/abort/suspend callbacks are either enabled or replaced with safe shutdown logic, (3) the DMA not-ready branch in backlight_set has a defined safe fallback instead of a silent no-op, and (4) shared state between irq-locked foreground code and the DMA IRQ callback is race-free.
Security signals we found
Multiple HAL API return values are stored but not checked ((void)ret), which could mask DMA initialization or runtime failures.
Error, abort, and suspend DMA callbacks are defined but commented out, leaving no runtime handling for DMA errors.
A commented-out infinite loop was intended as the fallback when DMA is not in READY state during backlight_set.
The driver uses irq_lock()/irq_unlock() around shared-variable updates, but the DMA callback also touches the same volatile state without explicit synchronization analysis in the diff.
The commit is marked [no changelog] and framed purely as a feature, with no security claims.
Evidence from the diff
The patch refactors core/embed/io/backlight/stm32u5/tps61062.c to drive the TPS61062 backlight LED driver with GPDMA in circular linked-list mode. It replaces a single-shot DMA transfer of up to 34 32-bit words with two 10 ms buffers of 16-bit halfwords, updated in an interrupt callback. Key changes: MAX_STEPS corrected from 32 to 31, new state machine (BACKLIGHT_OFF/ON), double-buffer indices, DMA_NodeTypeDef/DMA_QListTypeDef setup, HAL_DMAEx_List_* APIs, and a GPDMA1_Channel3_IRQHandler. Several HAL return values are captured in ret but then (void)ret, error/abort/suspend callbacks are commented out, and a fallback while(1) on DMA not-ready is also commented out. The commit message frames this as a feature (‘higher granularity of TS7 backlight’) and explicitly says ‘[no changelog]’.
Changed components
core/embed/io/backlight/stm32u5/tps61062.cTrezor Safe 7 (TS7) / D002 DISC2 board backlight subsystemGPDMA1 Channel 3 / TIM3 update DMA requestTPS61062 backlight LED driver control pathInspect captured patch +355 / −75
diff --git a/core/embed/io/backlight/stm32u5/tps61062.c b/core/embed/io/backlight/stm32u5/tps61062.c
index 99d9df253..8ed3b3d74 100644
--- a/core/embed/io/backlight/stm32u5/tps61062.c
+++ b/core/embed/io/backlight/stm32u5/tps61062.c
@@ -17,6 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+// #pragma GCC optimize("O0")
+
#ifdef KERNEL_MODE
#include <sys/irq.h>
@@ -40,28 +42,57 @@
#define TIM_PULSE(width) \
(TIMER_PERIOD - (width) * TIMER_PERIOD / MAX_PULSE_WIDTH_US)
-#define MAX_STEPS 32
+#define MAX_STEPS \
+ 31 // TPS DAC steps (0-31) where 0 means ~15.6mV at Rs and 31 means ~500mV at
+ // Rs (1 steps ~15.6mV)
#define DEFAULT_STEP 16 // DAC value after reset
+#define DEFAULT_LEVEL \
+ ((DEFAULT_STEP) * (BACKLIGHT_MAX_LEVEL) / \
+ (MAX_STEPS)) // Approximated default level after reset
+
+#define REG_LOOP_PERIOD_US 10000 // 10ms
+#define DMA_BUF_LENGTH \
+ (REG_LOOP_PERIOD_US / MAX_PULSE_WIDTH_US) // no samples per period
+#define DMA_BUF_COUNT 2 // 2 buffers for double buffering
+
+typedef enum { BACKLIGHT_OFF = 0, BACKLIGHT_ON = 1 } backlight_state_t;
// Backlight driver state
typedef struct {
// Set if driver is initialized
bool initialized;
- // Level requested (0-255)
+ // Current state
+ backlight_state_t state;
+
+ // Requested values (via API)
uint8_t requested_level;
- uint8_t current_level;
+ volatile uint8_t requested_level_limited;
+ volatile int requested_step;
- // Current step in range 0-32
- int current_step;
+ // Latched values (currently being sent into TPS)
+ volatile uint8_t latched_level[DMA_BUF_COUNT];
+ volatile int latched_step[DMA_BUF_COUNT];
+
+ // Current values set (inside TPS)
+ volatile uint8_t current_level;
+ volatile int current_step;
// Max backlight level
uint8_t max_level;
- DMA_HandleTypeDef dma;
TIM_HandleTypeDef tim;
- uint32_t pwm_data[MAX_STEPS + 2]; // max steps + 2 for start and end
+ DMA_HandleTypeDef dma;
+
+ DMA_NodeTypeDef dma_node[DMA_BUF_COUNT];
+ DMA_QListTypeDef dma_queue;
+
+ // Double buffer for DMA
+ uint16_t pwm_data[DMA_BUF_COUNT][DMA_BUF_LENGTH];
+
+ volatile uint8_t locked_buf_idx;
+ volatile uint8_t prepare_buf_idx;
} backlight_driver_t;
@@ -69,9 +100,15 @@ static backlight_driver_t g_backlight_driver = {
.initialized = false,
};
-static void backlight_control_up(uint32_t *data, int steps);
-static void backlight_control_down(uint32_t *data, int steps);
-static void backlight_shutdown();
+static void backlight_control_up(uint16_t *data, int steps);
+static void backlight_control_down(uint16_t *data, int steps);
+static void backlight_shutdown(void);
+
+static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma);
+// static void DMA_XferHalfCpltCallback(DMA_HandleTypeDef *hdma);
+// static void DMA_XferErrorCallback(DMA_HandleTypeDef *hdma);
+// static void DMA_XferAbortCallback(DMA_HandleTypeDef *hdma);
+// static void DMA_XferSuspendCallback(DMA_HandleTypeDef *hdma);
bool backlight_init(backlight_action_t action) {
backlight_driver_t *drv = &g_backlight_driver;
@@ -111,7 +148,11 @@ bool backlight_init(backlight_action_t action) {
HAL_TIM_PWM_Init(&drv->tim);
TIM_OC_InitTypeDef TIM_OC_InitStructure = {0};
- TIM_OC_InitStructure.Pulse = 0;
+ TIM_OC_InitStructure.Pulse =
+ TIMER_PERIOD; // Make ILED to log 1 (by TIM.CCR1 value >= TIM.ARR) =>
+ // when EN gets activated, TPS will be IDLE (we don't risk
+ // it going into programming switched off state and
+ // maximizing its output current)
TIM_OC_InitStructure.OCMode = TIM_OCMODE_PWM1;
TIM_OC_InitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM_OC_InitStructure.OCFastMode = TIM_OCFAST_DISABLE;
@@ -129,32 +170,100 @@ bool backlight_init(backlight_action_t action) {
GPIO_ILED_InitStructure.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(TPS61062_ILED_PORT, &GPIO_ILED_InitStructure);
+ // GPDMA init (circular linked list mode with 2 nodes forming double buffer 1
+ // one buffer is at a time, the 2nd is prepared at DMA.TC event which occurs
+ // after buffers gets transferred)
+ HAL_StatusTypeDef ret = HAL_OK;
+
__HAL_RCC_GPDMA1_CLK_ENABLE();
- drv->dma.Instance = GPDMA1_Channel3;
- drv->dma.Init.Direction = DMA_MEMORY_TO_PERIPH;
- drv->dma.Init.Mode = DMA_NORMAL;
- drv->dma.Init.Request = GPDMA1_REQUEST_TIM3_UP;
- drv->dma.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
- drv->dma.Init.SrcInc = DMA_SINC_INCREMENTED;
- drv->dma.Init.DestInc = DMA_DINC_FIXED;
- drv->dma.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
- drv->dma.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
- drv->dma.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
- drv->dma.Init.SrcBurstLength = 1;
- drv->dma.Init.DestBurstLength = 1;
- drv->dma.Init.TransferAllocatedPort =
- DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0;
- drv->dma.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
- HAL_DMA_Init(&drv->dma);
- HAL_DMA_ConfigChannelAttributes(
+ drv->dma.Instance = GPDMA1_Channel3;
+ drv->dma.InitLinkedList.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
+ drv->dma.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
+ drv->dma.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT1;
+ drv->dma.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
+ drv->dma.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
+ ret = HAL_DMAEx_List_Init(&drv->dma);
+ ret = HAL_DMA_ConfigChannelAttributes(
&drv->dma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | DMA_CHANNEL_SRC_SEC |
DMA_CHANNEL_DEST_SEC);
+ DMA_NodeConfTypeDef pNodeConfig;
+
+ pNodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
+ pNodeConfig.Init.Request = GPDMA1_REQUEST_TIM3_UP;
+ pNodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
+ pNodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH;
+ pNodeConfig.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
+ pNodeConfig.Init.SrcInc = DMA_SINC_INCREMENTED;
+ pNodeConfig.Init.DestInc = DMA_DINC_FIXED;
+ pNodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
+ pNodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
+ pNodeConfig.Init.SrcBurstLength = 1;
+ pNodeConfig.Init.DestBurstLength = 1;
+ pNodeConfig.Init.TransferAllocatedPort =
+ DMA_SRC_ALLOCATED_PORT1 | DMA_DEST_ALLOCATED_PORT0;
+ pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
+ pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
+ pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
+ pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
+ pNodeConfig.SrcAddress = (uint32_t)drv->pwm_data[0];
+ pNodeConfig.DstAddress = (uint32_t)&drv->tim.Instance->CCR1;
+ pNodeConfig.DataSize = sizeof(drv->pwm_data[0]);
+#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
+ pNodeConfig.SrcSecure = DMA_CHANNEL_SRC_SEC;
+ pNodeConfig.DestSecure = DMA_CHANNEL_DEST_SEC;
+#endif /* defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) */
+
+ // Build dma_node Node
+ ret = HAL_DMAEx_List_BuildNode(&pNodeConfig, &drv->dma_node[0]);
+ memset(drv->pwm_data[0], 0xFF, sizeof(drv->pwm_data[0]));
+
+ // Insert dma_node to Queue
+ ret = HAL_DMAEx_List_InsertNode_Tail(&drv->dma_queue, &drv->dma_node[0]);
+
+ // Prepare second node for regular operation
+ pNodeConfig.SrcAddress = (uint32_t)drv->pwm_data[1];
+ pNodeConfig.DataSize = sizeof(drv->pwm_data[1]);
+
+ // Build dma_node Node
+ ret = HAL_DMAEx_List_BuildNode(&pNodeConfig, &drv->dma_node[1]);
+ memset(drv->pwm_data[1], 0xFF, sizeof(drv->pwm_data[1]));
+
+ // Insert dma_node to Queue
+ ret = HAL_DMAEx_List_InsertNode_Tail(&drv->dma_queue, &drv->dma_node[1]);
+
+ // Set circular mode
+ ret = HAL_DMAEx_List_SetCircularMode(&drv->dma_queue);
+
+ // Link the Queue to the DMA channel
+ ret = HAL_DMAEx_List_LinkQ(&drv->dma, &drv->dma_queue);
+
+ // Enable TIM DMA requests
__HAL_TIM_ENABLE_DMA(&drv->tim, TIM_DMA_UPDATE);
- HAL_TIM_Base_Start(&drv->tim);
- HAL_TIM_PWM_Start(&drv->tim, TIM_CHANNEL_1);
+ // Start TIM
+ ret = HAL_TIM_Base_Start(&drv->tim);
+ ret = HAL_TIM_PWM_Start(&drv->tim, TIM_CHANNEL_1);
+
+ // Register DMA callbacks
+ ret = HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_CPLT_CB_ID,
+ &DMA_XferCpltCallback);
+ // ret = HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_HALFCPLT_CB_ID,
+ // &DMA_XferHalfCpltCallback); ret = HAL_DMA_RegisterCallback(&drv->dma,
+ // HAL_DMA_XFER_ERROR_CB_ID, &DMA_XferErrorCallback); ret =
+ // HAL_DMA_RegisterCallback(&drv->dma, HAL_DMA_XFER_ABORT_CB_ID,
+ // &DMA_XferAbortCallback); ret = HAL_DMA_RegisterCallback(&drv->dma,
+ // HAL_DMA_XFER_SUSPEND_CB_ID, &DMA_XferSuspendCallback);
+ (void)ret;
+
+ // Configure and enable DMA IRQ
+ NVIC_SetPriority(GPDMA1_Channel3_IRQn, IRQ_PRI_NORMAL);
+ NVIC_EnableIRQ(GPDMA1_Channel3_IRQn);
+
+ // Set active buffer to the first one
+ drv->prepare_buf_idx = 0;
+ drv->locked_buf_idx = 1;
// Default no backlight max_level limit
drv->max_level = BACKLIGHT_MAX_LEVEL;
@@ -172,20 +281,46 @@ void backlight_deinit(backlight_action_t action) {
return;
}
- if (HAL_DMA_GetState(&drv->dma) == HAL_DMA_STATE_BUSY) {
- while (HAL_DMA_PollForTransfer(&drv->dma, HAL_DMA_FULL_TRANSFER,
- HAL_MAX_DELAY) != HAL_OK) {
+ if (action == BACKLIGHT_RESET) {
+ HAL_StatusTypeDef ret;
+
+ irq_key_t key = irq_lock();
+
+ // Abort the DMA. It's unclear what last data is transferred to TIM_CCR
+ // register and in what state the respective GPIO pin will be left in. CCR
+ // register is set to TIMER_PERIOD value inside "backlight_shutdown()"
+ // function.
+ if (drv->dma.State == HAL_DMA_STATE_BUSY) {
+ ret = HAL_DMA_Abort(
+ &drv->dma); // TODO: could be replaced with interrupt based variant
}
- }
- if (action == BACKLIGHT_RESET) {
+ irq_unlock(key);
+
backlight_shutdown();
+
+ NVIC_DisableIRQ(GPDMA1_Channel3_IRQn);
+
+ ret = HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_CPLT_CB_ID);
+ // ret = HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_HALFCPLT_CB_ID);
+ // ret = HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_ERROR_CB_ID);
+ // ret = HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_ABORT_CB_ID);
+ // ret = HAL_DMA_UnRegisterCallback(&drv->dma, HAL_DMA_XFER_SUSPEND_CB_ID);
+
+ ret = HAL_DMAEx_List_UnLinkQ(&drv->dma);
+ ret = HAL_DMAEx_List_DeInit(&drv->dma);
+
HAL_GPIO_DeInit(TPS61062_ILED_PORT, TPS61062_ILED_PIN);
HAL_GPIO_DeInit(TPS61062_EN_PORT, TPS61062_EN_PIN);
__HAL_RCC_TIM3_FORCE_RESET();
__HAL_RCC_TIM3_RELEASE_RESET();
__HAL_RCC_TIM3_CLK_DISABLE();
+
+ (void)ret;
+
+ // Move the state to OFF
+ drv->state = BACKLIGHT_OFF;
}
drv->initialized = false;
@@ -193,6 +328,7 @@ void backlight_deinit(backlight_action_t action) {
bool backlight_set(uint8_t val) {
backlight_driver_t *drv = &g_backlight_driver;
+ HAL_StatusTypeDef ret = HAL_OK;
if (!drv->initialized) {
return false;
@@ -201,62 +337,118 @@ bool backlight_set(uint8_t val) {
// Capture requested level.
drv->requested_level = val;
+ // Limit requested level by max_level
uint8_t requested_level_limited = drv->requested_level;
if (drv->requested_level > drv->max_level) {
requested_level_limited = drv->max_level;
}
// No action required
- if (requested_level_limited == drv->current_level) {
+ if (requested_level_limited == drv->requested_level_limited) {
return true;
}
- // New backlight level
- drv->current_level = requested_level_limited;
+ irq_key_t key = irq_lock();
- int set_step = MAX_STEPS * drv->current_level / BACKLIGHT_MAX_LEVEL;
+ // Save the new value into the shared variable so that it can be used inside
+ // DMA callback
+ drv->requested_level_limited = requested_level_limited;
+ drv->requested_step =
+ MAX_STEPS * drv->requested_level_limited / BACKLIGHT_MAX_LEVEL;
+
+ // Requested level is 0 => shutdown backlight
+ if (drv->requested_level_limited == 0) {
+ if (drv->dma.State == HAL_DMA_STATE_BUSY) {
+ ret = HAL_DMA_Abort(
+ &drv->dma); // TODO: could be replaced with interrupt based variant
+ }
+
+ irq_unlock(key);
- if (set_step == 0) {
backlight_shutdown();
- drv->current_step = 0;
- return true;
- }
- if (HAL_DMA_GetState(&drv->dma) == HAL_DMA_STATE_BUSY) {
- while (HAL_DMA_PollForTransfer(&drv->dma, HAL_DMA_FULL_TRANSFER,
- HAL_MAX_DELAY) != HAL_OK) {
+ // Clearing buffer, preparation for the next time
+ memset(drv->pwm_data, 0xFF, sizeof(drv->pwm_data));
+
+ // Clear the control data
+ for (int i = 0; i < DMA_BUF_COUNT; i++) {
+ drv->latched_level[i] = 0;
+ drv->latched_step[i] = 0;
}
- }
- int pwm_data_idx = 0;
- memset(drv->pwm_data, 0, sizeof(drv->pwm_data));
+ // Update values to reflect the backlight is off
+ drv->current_level = 0;
+ drv->current_step = 0;
- if (drv->current_step == 0) {
- HAL_GPIO_WritePin(TPS61062_EN_PORT, TPS61062_EN_PIN, GPIO_PIN_SET);
- // if brightness control is shutdown, start with initial pulse
- drv->pwm_data[0] = TIMER_PERIOD;
- pwm_data_idx++;
- drv->current_step = DEFAULT_STEP;
- }
+ // Set active buffer to the first one
+ drv->prepare_buf_idx = 0;
+ drv->locked_buf_idx = 1;
- if (set_step > drv->current_step) {
- int steps = set_step - drv->current_step;
- backlight_control_up(&drv->pwm_data[pwm_data_idx], steps);
- pwm_data_idx += steps;
+ // Move the state to OFF
+ drv->state = BACKLIGHT_OFF;
- } else if (set_step < drv->current_step) {
- int steps = drv->current_step - set_step;
- backlight_control_down(&drv->pwm_data[pwm_data_idx], steps);
- pwm_data_idx += steps;
+ return true;
}
- drv->pwm_data[pwm_data_idx] = TIMER_PERIOD;
-
- HAL_DMA_Start(&drv->dma, (uint32_t)drv->pwm_data,
- (uint32_t)&drv->tim.Instance->CCR1,
- (pwm_data_idx + 1) * sizeof(uint32_t));
+ irq_unlock(key);
+
+ // In case this functions is called first time after init or in case backlight
+ // was switched off before, we need to prepare the buffers, DMA, etc. The DMA
+ // is not running in this case, nor its interrupts => no need to disable them.
+ if (drv->state == BACKLIGHT_OFF) {
+ if (HAL_DMA_GetState(&drv->dma) == HAL_DMA_STATE_READY) {
+ // Calculate the difference between the default state (the TPS EN pin
+ // shall be activated at the end of this scope, the TPS sets itself to
+ // default state = DEFAULT_STEP) and the wanted one
+ if (drv->requested_step > DEFAULT_STEP) {
+ backlight_control_up(
+ &drv->pwm_data[drv->prepare_buf_idx][1],
+ drv->requested_step -
+ DEFAULT_STEP); // Start from index 1, index 0 is already set
+ // (with buffer clear to make TIM not generate
+ // any pulse)
+ } else {
+ backlight_control_down(
+ &drv->pwm_data[drv->prepare_buf_idx][1],
+ DEFAULT_STEP -
+ drv->requested_step); // Start from index 1, index 0 is already
+ // set (with buffer clear to make TIM not
+ // generate any pulse)
+ }
+
+ // Set the current values to reflect the state after TPS EN gets activated
+ drv->current_level = DEFAULT_LEVEL;
+ drv->current_step = DEFAULT_STEP;
+
+ // Update the latched values to reflect what's about to happen when the
+ // DMA is started
+ drv->latched_step[drv->prepare_buf_idx] = drv->requested_step;
+ drv->latched_step[drv->locked_buf_idx] = drv->requested_step;
+ drv->latched_level[drv->prepare_buf_idx] = drv->requested_level_limited;
+ drv->latched_level[drv->locked_buf_idx] = drv->requested_level_limited;
+
+ // Swap indices (the buffer prepared now will be locked next time, the
+ // other one will be prepared next time)
+ drv->locked_buf_idx = drv->prepare_buf_idx;
+ drv->prepare_buf_idx = (drv->prepare_buf_idx + 1) % DMA_BUF_COUNT;
+
+ // Enable TPS
+ HAL_GPIO_WritePin(TPS61062_EN_PORT, TPS61062_EN_PIN, GPIO_PIN_SET);
+
+ // Start the DMA
+ ret = HAL_DMAEx_List_Start_IT(&drv->dma);
+
+ // Move the state to ON
+ drv->state = BACKLIGHT_ON;
+ } else {
+ // some serious problem occured
+ // while (1) {
+ // continue;
+ // }
+ }
+ }
- drv->current_step = set_step;
+ (void)ret;
return true;
}
@@ -268,7 +460,9 @@ uint8_t backlight_get(void) {
return 0;
}
- return drv->current_level;
+ return drv
+ ->requested_level_limited; // Returning the limited requested value as
+ // the current value is slightly delayed
}
// Set maximal backlight level
@@ -281,23 +475,109 @@ bool backlight_set_max_level(uint8_t max_level) {
drv->max_level = max_level;
- return backlight_set(drv->requested_level);
+ return backlight_set(
+ drv->requested_level); // The maximum values has been changed, so we need
+ // to reapply the requested value
}
-static void backlight_control_up(uint32_t *data, int steps) {
+static void backlight_control_up(uint16_t *data, int steps) {
for (int i = 0; i < steps; i++) {
data[i] = TIM_PULSE(BACKLIGHT_CONTROL_T_UP_US);
}
}
-static void backlight_control_down(uint32_t *data, int steps) {
+static void backlight_control_down(uint16_t *data, int steps) {
for (int i = 0; i < steps; i++) {
data[i] = TIM_PULSE(BACKLIGHT_CONTROL_T_DOWN_US);
}
}
-static void backlight_shutdown() {
+static void backlight_shutdown(void) {
+ backlight_driver_t *drv = &g_backlight_driver;
+
+ drv->tim.Instance->CCR1 = TIMER_PERIOD;
HAL_GPIO_WritePin(TPS61062_EN_PORT, TPS61062_EN_PIN, GPIO_PIN_RESET);
}
+// Transfer complete callback
+static void DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) {
+ backlight_driver_t *drv = &g_backlight_driver;
+
+ // update the current values with the latched ones as the programming sequence
+ // has finished
+ drv->current_level = drv->latched_level[drv->locked_buf_idx];
+ drv->current_step = drv->latched_step[drv->locked_buf_idx];
+
+ // Switch active buffer
+ drv->locked_buf_idx = drv->prepare_buf_idx;
+ drv->prepare_buf_idx = (drv->prepare_buf_idx + 1) % DMA_BUF_COUNT;
+
+ // Clear the buffer
+ memset(drv->pwm_data[drv->prepare_buf_idx], 0xFF,
+ sizeof(drv->pwm_data[drv->prepare_buf_idx]));
+
+ // if (drv->requested_level_limited !=
+ // drv->latched_level[drv->locked_buf_idx])
+ if (drv->requested_step != drv->latched_step[drv->locked_buf_idx]) {
+ // calculate the difference between the latched state and the wanted one
+ if (drv->requested_step > drv->latched_step[drv->locked_buf_idx]) {
+ backlight_control_up(
+ &drv->pwm_data[drv->prepare_buf_idx][0],
+ drv->requested_step - drv->latched_step[drv->locked_buf_idx]);
+ } else {
+ backlight_control_down(
+ &drv->pwm_data[drv->prepare_buf_idx][0],
+ drv->latched_step[drv->locked_buf_idx] - drv->requested_step);
+ }
+
+ // the buffer has been precalculated to reach the drv->requested_step value
+ // => update the drv->latched_step value
+ drv->latched_step[drv->prepare_buf_idx] = drv->requested_step;
+ drv->latched_level[drv->prepare_buf_idx] = drv->requested_level_limited;
+ } else {
+ drv->latched_step[drv->prepare_buf_idx] =
+ drv->latched_step[drv->locked_buf_idx];
+ drv->latched_level[drv->prepare_buf_idx] =
+ drv->latched_level[drv->locked_buf_idx];
+ }
+
+ (void)hdma;
+}
+
+// // Half transfer complete callback
+// static void DMA_XferHalfCpltCallback(DMA_HandleTypeDef *hdma)
+// {
+// // HAL_GPIO_WritePin(TPS61062_EN_PORT, TPS61062_EN_PIN, GPIO_PIN_RESET);
+
+// (void)hdma;
+// }
+
+// // Error callback
+// static void DMA_XferErrorCallback(DMA_HandleTypeDef *hdma)
+// {
+// (void)hdma;
+// }
+
+// // Abort callback
+// static void DMA_XferAbortCallback(DMA_HandleTypeDef *hdma)
+// {
+// (void)hdma;
+// }
+
+// // Suspend callback
+// static void DMA_XferSuspendCallback(DMA_HandleTypeDef *hdma)
+// {
+// (void)hdma;
+// }
+
+void GPDMA1_Channel3_IRQHandler(void) {
+ IRQ_LOG_ENTER();
+
+ backlight_driver_t *drv = &g_backlight_driver;
+
+ HAL_DMA_IRQHandler(&drv->dma);
+
+ IRQ_LOG_EXIT();
+}
+
#endif
Why this scored 18/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.