blob: aa35a127b1a590717dc78699db98256cedc0b3e6 [file] [log] [blame]
/* Copyright 2018 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Power and battery LED state control
*/
#include "battery.h"
#include "charge_state.h"
#include "chipset.h"
#include "console.h"
#include "ec_commands.h"
#include "extpower.h"
#include "hooks.h"
#include "led_common.h"
#include "led_onoff_states.h"
#include "system.h"
#include "util.h"
#define CPRINTS(format, args...) cprints(CC_GPIO, format, ##args)
/*
* In order to support the battery LED being optional (ex. for Chromeboxes),
* set up default battery table, setter, and variables.
*/
__overridable struct led_descriptor led_bat_state_table[LED_NUM_STATES]
[LED_NUM_PHASES];
__overridable const int led_charge_lvl_1;
__overridable const int led_charge_lvl_2;
__overridable void led_set_color_battery(enum ec_led_colors color)
{
}
#ifndef CONFIG_CHARGER
/* Include for the sake of compilation */
int charge_get_percent(void);
#endif
static int led_get_charge_percent(void)
{
return DIV_ROUND_NEAREST(charge_get_display_charge(), 10);
}
static enum led_states led_get_state(void)
{
int charge_lvl;
enum led_states new_state = LED_NUM_STATES;
if (!IS_ENABLED(CONFIG_CHARGER))
return new_state;
switch (led_pwr_get_state()) {
case LED_PWRS_CHARGE:
/* Get percent charge */
charge_lvl = led_get_charge_percent();
/* Determine which charge state to use */
if (charge_lvl < led_charge_lvl_1)
new_state = STATE_CHARGING_LVL_1;
else if (charge_lvl < led_charge_lvl_2)
new_state = STATE_CHARGING_LVL_2;
else if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
new_state = STATE_CHARGING_FULL_S5;
else
new_state = STATE_CHARGING_FULL_CHARGE;
break;
case LED_PWRS_DISCHARGE_FULL:
if (extpower_is_present()) {
if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
new_state = STATE_CHARGING_FULL_S5;
else
new_state = STATE_CHARGING_FULL_CHARGE;
break;
}
__fallthrough;
case LED_PWRS_DISCHARGE /* and LED_PWRS_DISCHARGE_FULL */:
if (chipset_in_state(CHIPSET_STATE_ON)) {
#ifdef CONFIG_LED_ONOFF_STATES_BAT_LOW
if (led_get_charge_percent() <
CONFIG_LED_ONOFF_STATES_BAT_LOW)
new_state = STATE_DISCHARGE_S0_BAT_LOW;
else
#endif
new_state = STATE_DISCHARGE_S0;
} else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND))
new_state = STATE_DISCHARGE_S3;
else
new_state = STATE_DISCHARGE_S5;
break;
case LED_PWRS_ERROR:
new_state = STATE_BATTERY_ERROR;
break;
case LED_PWRS_CHARGE_NEAR_FULL:
if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
new_state = STATE_CHARGING_FULL_S5;
else
new_state = STATE_CHARGING_FULL_CHARGE;
break;
case LED_PWRS_IDLE: /* External power connected in IDLE */
if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
new_state = STATE_DISCHARGE_S5;
else
new_state = STATE_DISCHARGE_S0;
break;
case LED_PWRS_FORCED_IDLE:
new_state = STATE_FACTORY_TEST;
break;
default:
/* Other states don't alter LED behavior */
break;
}
return new_state;
}
__overridable enum led_states board_led_get_state(enum led_states desired_state)
{
return desired_state;
}
static void led_update_battery(void)
{
static uint8_t ticks, period;
static int led_state = LED_NUM_STATES;
int phase;
enum led_states desired_state = led_get_state();
desired_state = board_led_get_state(desired_state);
/*
* We always need to check the current state since the value could
* have been manually overwritten. If we're in a new valid state,
* update our ticks and period info. If our new state isn't defined,
* continue using the previous one.
*/
if (desired_state != led_state && desired_state < LED_NUM_STATES) {
/*
* Allow optional CHARGING_FULL_S5 state to fall back to
* FULL_CHARGE if not defined.
*/
if (desired_state == STATE_CHARGING_FULL_S5 &&
led_bat_state_table[desired_state][LED_PHASE_0].time == 0)
desired_state = STATE_CHARGING_FULL_CHARGE;
/* State is changing */
led_state = desired_state;
/* Reset ticks and period when state changes */
ticks = 0;
period = led_bat_state_table[led_state][LED_PHASE_0].time +
led_bat_state_table[led_state][LED_PHASE_1].time;
}
/* If this state is undefined, turn the LED off */
if (period == 0) {
CPRINTS("Undefined LED behavior for battery state %d,"
"turning off LED",
led_state);
led_set_color_battery(LED_OFF);
return;
}
/*
* Determine which phase of the state table to use. The phase is
* determined if it falls within first phase time duration.
*/
phase = ticks < led_bat_state_table[led_state][LED_PHASE_0].time ? 0 :
1;
ticks = (ticks + 1) % period;
/* Set the color for the given state and phase */
led_set_color_battery(led_bat_state_table[led_state][phase].color);
}
/*
* In order to support the power LED being optional, set up default power LED
* table and setter
*/
__overridable const struct led_descriptor
led_pwr_state_table[PWR_LED_NUM_STATES][LED_NUM_PHASES];
__overridable void led_set_color_power(enum ec_led_colors color)
{
}
static enum pwr_led_states pwr_led_get_state(void)
{
if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) {
if (extpower_is_present())
return PWR_LED_STATE_SUSPEND_AC;
else
return PWR_LED_STATE_SUSPEND_NO_AC;
} else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
if (system_can_boot_ap())
return PWR_LED_STATE_OFF;
else
return PWR_LED_STATE_OFF_LOW_POWER;
} else if (chipset_in_state(CHIPSET_STATE_ON)) {
return PWR_LED_STATE_ON;
}
return PWR_LED_NUM_STATES;
}
static void led_update_power(void)
{
static uint8_t ticks, period;
static enum pwr_led_states led_state = PWR_LED_NUM_STATES;
int phase;
enum pwr_led_states desired_state = pwr_led_get_state();
/*
* If we're in a new valid state, update our ticks and period info.
* Otherwise, continue to use old state
*/
if (desired_state != led_state && desired_state < PWR_LED_NUM_STATES) {
/*
* Allow optional OFF_LOW_POWER state to fall back to
* OFF not defined, as indicated by no specified phase 0 time.
*/
if (desired_state == PWR_LED_STATE_OFF_LOW_POWER &&
led_pwr_state_table[desired_state][LED_PHASE_0].time == 0)
desired_state = PWR_LED_STATE_OFF;
/* State is changing */
led_state = desired_state;
/* Reset ticks and period when state changes */
ticks = 0;
period = led_pwr_state_table[led_state][LED_PHASE_0].time +
led_pwr_state_table[led_state][LED_PHASE_1].time;
}
/* If this state is undefined, turn the LED off */
if (period == 0) {
CPRINTS("Undefined LED behavior for power state %d,"
"turning off LED",
led_state);
led_set_color_power(LED_OFF);
return;
}
/*
* Determine which phase of the state table to use. The phase is
* determined if it falls within first phase time duration.
*/
phase = ticks < led_pwr_state_table[led_state][LED_PHASE_0].time ? 0 :
1;
ticks = (ticks + 1) % period;
/* Set the color for the given state and phase */
led_set_color_power(led_pwr_state_table[led_state][phase].color);
}
static void led_init(void)
{
/* If battery LED is enabled, set it to "off" to start with */
if (led_auto_control_is_enabled(EC_LED_ID_BATTERY_LED))
led_set_color_battery(LED_OFF);
/* If power LED is enabled, set it to "off" to start with */
if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED))
led_set_color_power(LED_OFF);
}
DECLARE_HOOK(HOOK_INIT, led_init, HOOK_PRIO_DEFAULT);
/* Called by hook task every hook tick (200 msec) */
static void led_update(void)
{
/*
* If battery LED is enabled, set its state based on our power and
* charge
*/
if (led_auto_control_is_enabled(EC_LED_ID_BATTERY_LED))
led_update_battery();
if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED))
led_update_power();
}
DECLARE_HOOK(HOOK_TICK, led_update, HOOK_PRIO_DEFAULT);
OSZAR »