pebble/src/fw/drivers/backlight.c
2025-01-27 11:38:16 -08:00

181 lines
6.4 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/backlight.h"
#include <string.h>
#include <stdlib.h>
#include "board/board.h"
#include "debug/power_tracking.h"
#include "console/prompt.h"
#include "drivers/gpio.h"
#include "drivers/led_controller.h"
#include "drivers/periph_config.h"
#include "drivers/pwm.h"
#include "drivers/timer.h"
#include "kernel/util/stop.h"
#include "system/logging.h"
#include "system/passert.h"
// Parameters to a timer based PWM.
//
// ---------------- ---------- ---------- ---------- ----------
// | | | | | | | |
// | | | | | | | |
// | | | | | | | |
// | | | | | | | |
// | | | | | | | |
// | | | | | | | |
// | | | | | | | |
// ------- ------- ------- --------
//
// The resulting waveform has a frequency of PWM_OUTPUT_FREQUENCY_HZ. Inside each period, the timer
// counts up to TIMER_PERIOD_RESOLUTION. This means the counter increments at a rate of
// PWM_OUTPUT_FREQUENCY_HZ * TIMER_PERIOD_RESOLUTION, which is the frequency that our timer
// prescalar has to calculate. The duty cycle is defined by the TIM_Pulse parameter, which
// controls after which counter value the output waveform will become active. For example, a
// TIM_Pulse value of TIMER_PERIOD_RESOLUTION / 4 will result in an output waveform that will go
// active after 25% of it's period has elapsed.
//! The counter reload value. The timer will count from 0 to this value and then reset again.
//! The TIM_Pulse member below controls for how many of these counts the resulting PWM signal is
//! active for.
static const uint32_t TIMER_PERIOD_RESOLUTION = 1024;
//! The number of periods we have per second.
//! Note that we want BOARD_CONFIG_BACKLIGHT.timer.peripheral to have as short a period as
//! possible for power reasons.
static const uint32_t PWM_OUTPUT_FREQUENCY_HZ = 256;
static bool s_initialized = false;
static bool s_backlight_pwm_enabled = false;
//! Bitmask of who wants to hold the LED enable on.
//! see \ref led_enable, \ref led_disable, \ref LEDEnabler
static uint32_t s_led_enable;
static void prv_backlight_pwm_enable(bool on) {
pwm_enable(&BOARD_CONFIG_BACKLIGHT.pwm, on);
if (on != s_backlight_pwm_enabled) {
if (on) {
stop_mode_disable(InhibitorBacklight);
} else {
stop_mode_enable(InhibitorBacklight);
}
}
s_backlight_pwm_enabled = on;
}
void backlight_init(void) {
if (s_initialized) {
return;
}
s_led_enable = 0;
if (BOARD_CONFIG_BACKLIGHT.options & ActuatorOptions_Ctl) {
periph_config_acquire_lock();
gpio_output_init(&BOARD_CONFIG_BACKLIGHT.ctl, GPIO_OType_PP, GPIO_Speed_2MHz);
gpio_output_set(&BOARD_CONFIG_BACKLIGHT.ctl, false);
periph_config_release_lock();
s_initialized = true;
}
if (BOARD_CONFIG_BACKLIGHT.options & ActuatorOptions_Pwm) {
periph_config_acquire_lock();
pwm_init(&BOARD_CONFIG_BACKLIGHT.pwm,
TIMER_PERIOD_RESOLUTION,
TIMER_PERIOD_RESOLUTION * PWM_OUTPUT_FREQUENCY_HZ);
periph_config_release_lock();
s_initialized = true;
}
if (BOARD_CONFIG_BACKLIGHT.options & ActuatorOptions_IssiI2C) {
led_controller_init();
s_initialized = true;
}
}
// TODO: PBL-36077 Move to a generic 4v5 enable
void led_enable(LEDEnabler enabler) {
if (s_led_enable == 0) {
gpio_output_set(&BOARD_CONFIG_BACKLIGHT.ctl, true);
}
s_led_enable |= enabler;
}
// TODO: PBL-36077 Move to a generic 4v5 disable
void led_disable(LEDEnabler enabler) {
s_led_enable &= ~enabler;
if (s_led_enable == 0) {
gpio_output_set(&BOARD_CONFIG_BACKLIGHT.ctl, false);
}
}
void backlight_set_brightness(uint16_t brightness) {
if (BOARD_CONFIG_BACKLIGHT.options & ActuatorOptions_Ctl) {
if (brightness == 0) {
led_disable(LEDEnablerBacklight);
} else {
led_enable(LEDEnablerBacklight);
}
}
if (BOARD_CONFIG_BACKLIGHT.options & ActuatorOptions_Pwm) {
if (brightness == 0) {
if (s_backlight_pwm_enabled) {
prv_backlight_pwm_enable(false);
}
PWR_TRACK_BACKLIGHT("OFF", PWM_OUTPUT_FREQUENCY_HZ, 0);
} else {
if (!s_backlight_pwm_enabled) {
prv_backlight_pwm_enable(true);
}
// By setting higher values in the TIM_Pulse register, we're causing the output waveform
// to be low for a longer period of time, which causes the backlight to be brighter.
//
// The brightness value has a range of 0 to 0x3fff which is 2^15. The period of the timer
// counter is 2^10. We want to rescale the brightness range into a subset of the timer
// counter range. Different boards will have a different duty cycle that represent the
// "fully on" state.
const uint32_t pwm_scaling_factor = BACKLIGHT_BRIGHTNESS_MAX / TIMER_PERIOD_RESOLUTION;
const uint32_t desired_duty_cycle = brightness * BOARD_CONFIG.backlight_max_duty_cycle_percent
/ pwm_scaling_factor / 100;
pwm_set_duty_cycle(&BOARD_CONFIG_BACKLIGHT.pwm, desired_duty_cycle);
PWR_TRACK_BACKLIGHT("ON", PWM_OUTPUT_FREQUENCY_HZ,
(desired_duty_cycle * 100) / TIMER_PERIOD_RESOLUTION);
}
}
if (BOARD_CONFIG_BACKLIGHT.options & ActuatorOptions_IssiI2C) {
led_controller_backlight_set_brightness(brightness >> 8);
}
}
void command_backlight_ctl(const char *arg) {
const int bright_percent = atoi(arg);
if (bright_percent < 0 || bright_percent > 100) {
prompt_send_response("Invalid Brightness");
return;
}
backlight_set_brightness((BACKLIGHT_BRIGHTNESS_MAX * bright_percent) / 100);
prompt_send_response("OK");
}