326 lines
9.4 KiB
C
326 lines
9.4 KiB
C
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*
|
|
* Power and battery LED control for Coral
|
|
*/
|
|
|
|
#include "battery.h"
|
|
#include "charge_state.h"
|
|
#include "chipset.h"
|
|
#include "ec_commands.h"
|
|
#include "extpower.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "led_common.h"
|
|
#include "system.h"
|
|
#include "util.h"
|
|
|
|
#define LED_ON_LVL 0
|
|
#define LED_OFF_LVL 1
|
|
#define LED_INDEFINITE -1
|
|
#define LED_ONE_SEC (1000 / HOOK_TICK_INTERVAL_MS)
|
|
#define LED_CHARGE_LEVEL_1_DEFAULT 100
|
|
#define LED_CHARGE_LEVEL_1_ROBO 5
|
|
#define LED_POWER_BLINK_ON_MSEC 3000
|
|
#define LED_POWER_BLINK_OFF_MSEC 600
|
|
#define LED_POWER_ON_TICKS (LED_POWER_BLINK_ON_MSEC / HOOK_TICK_INTERVAL_MS)
|
|
#define LED_POWER_OFF_TICKS (LED_POWER_BLINK_OFF_MSEC / HOOK_TICK_INTERVAL_MS)
|
|
|
|
const enum ec_led_id supported_led_ids[] = {
|
|
EC_LED_ID_BATTERY_LED};
|
|
|
|
const int supported_led_ids_count = ARRAY_SIZE(supported_led_ids);
|
|
|
|
#define GPIO_LED_COLOR_1 GPIO_BAT_LED_AMBER
|
|
#define GPIO_LED_COLOR_2 GPIO_BAT_LED_BLUE
|
|
#define GPIO_LED_COLOR_3 GPIO_POW_LED
|
|
|
|
enum led_phase {
|
|
LED_PHASE_0,
|
|
LED_PHASE_1,
|
|
LED_NUM_PHASES
|
|
};
|
|
|
|
enum led_color {
|
|
LED_OFF,
|
|
LED_COLOR_1,
|
|
LED_COLOR_2,
|
|
LED_COLOR_BOTH,
|
|
LED_COLOR_COUNT /* Number of colors, not a color itself */
|
|
};
|
|
|
|
enum led_states {
|
|
STATE_CHARGING_LVL_1,
|
|
STATE_CHARGING_LVL_2,
|
|
STATE_CHARGING_LVL_3,
|
|
STATE_DISCHARGE_S0,
|
|
STATE_DISCHARGE_S3,
|
|
STATE_DISCHARGE_S5,
|
|
STATE_BATTERY_ERROR,
|
|
STATE_FACTORY_TEST,
|
|
LED_NUM_STATES
|
|
};
|
|
|
|
struct led_descriptor {
|
|
int8_t color;
|
|
int8_t time;
|
|
};
|
|
|
|
struct led_info {
|
|
enum led_states state;
|
|
uint8_t charge_lvl_1;
|
|
const struct led_descriptor (*state_table)[LED_NUM_PHASES];
|
|
void (*update_power)(void);
|
|
};
|
|
|
|
/*
|
|
* LED state tables describe the desired LED behavior for a each possible
|
|
* state. The LED state is based on both chip power state and the battery charge
|
|
* level. The first parameter is the color and the 2nd parameter is the time in
|
|
* ticks, where each tick is 200 msec. If the time parameter is set to -1, that
|
|
* means it is a non-blinking pattern.
|
|
*/
|
|
|
|
/* COLOR_1 = Amber, COLOR_2 = Blue */
|
|
static const struct led_descriptor led_default_state_table[][LED_NUM_PHASES] = {
|
|
{ {LED_COLOR_1, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_COLOR_1, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_1, 1 * LED_ONE_SEC }, {LED_OFF, 3 * LED_ONE_SEC} },
|
|
{ {LED_OFF, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_1, 1 * LED_ONE_SEC}, {LED_OFF, 1 * LED_ONE_SEC} },
|
|
{ {LED_COLOR_1, 2 * LED_ONE_SEC}, {LED_COLOR_2, 2 * LED_ONE_SEC} },
|
|
};
|
|
|
|
/* COLOR_1 = Green, COLOR_2 = Red */
|
|
static const struct led_descriptor led_robo_state_table[][LED_NUM_PHASES] = {
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_BOTH, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_1, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_OFF, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_OFF, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_OFF, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, 1 * LED_ONE_SEC}, {LED_OFF, 1 * LED_ONE_SEC} },
|
|
{ {LED_COLOR_2, 2 * LED_ONE_SEC}, {LED_COLOR_1, 2 * LED_ONE_SEC} },
|
|
};
|
|
|
|
static const struct led_descriptor led_nasher_state_table[][LED_NUM_PHASES] = {
|
|
{ {LED_COLOR_1, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_COLOR_1, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_2, 1 * LED_ONE_SEC}, {LED_OFF, 1 * LED_ONE_SEC} },
|
|
{ {LED_OFF, LED_INDEFINITE}, {LED_OFF, LED_INDEFINITE} },
|
|
{ {LED_COLOR_1, 1 * LED_ONE_SEC}, {LED_OFF, 1 * LED_ONE_SEC} },
|
|
{ {LED_COLOR_1, 2 * LED_ONE_SEC}, {LED_COLOR_2, 2 * LED_ONE_SEC} },
|
|
};
|
|
|
|
static struct led_info led;
|
|
|
|
static int led_set_color_battery(enum led_color color)
|
|
{
|
|
switch (color) {
|
|
case LED_OFF:
|
|
gpio_set_level(GPIO_LED_COLOR_1, LED_OFF_LVL);
|
|
gpio_set_level(GPIO_LED_COLOR_2, LED_OFF_LVL);
|
|
break;
|
|
case LED_COLOR_1:
|
|
gpio_set_level(GPIO_LED_COLOR_1, LED_ON_LVL);
|
|
gpio_set_level(GPIO_LED_COLOR_2, LED_OFF_LVL);
|
|
break;
|
|
case LED_COLOR_2:
|
|
gpio_set_level(GPIO_LED_COLOR_1, LED_OFF_LVL);
|
|
gpio_set_level(GPIO_LED_COLOR_2, LED_ON_LVL);
|
|
break;
|
|
case LED_COLOR_BOTH:
|
|
gpio_set_level(GPIO_LED_COLOR_1, LED_ON_LVL);
|
|
gpio_set_level(GPIO_LED_COLOR_2, LED_ON_LVL);
|
|
break;
|
|
default:
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static void led_set_color_power(int level)
|
|
{
|
|
gpio_set_level(GPIO_POWER_LED, level);
|
|
}
|
|
|
|
void led_get_brightness_range(enum ec_led_id led_id, uint8_t *brightness_range)
|
|
{
|
|
brightness_range[EC_LED_COLOR_BLUE] = 1;
|
|
brightness_range[EC_LED_COLOR_AMBER] = 1;
|
|
brightness_range[EC_LED_COLOR_RED] = 1;
|
|
brightness_range[EC_LED_COLOR_GREEN] = 1;
|
|
}
|
|
|
|
int led_set_brightness(enum ec_led_id led_id, const uint8_t *brightness)
|
|
{
|
|
if (brightness[EC_LED_COLOR_BLUE] != 0)
|
|
led_set_color_battery(LED_COLOR_2);
|
|
else if (brightness[EC_LED_COLOR_AMBER] != 0)
|
|
led_set_color_battery(LED_COLOR_1);
|
|
else if (brightness[EC_LED_COLOR_RED] != 0)
|
|
led_set_color_battery(LED_COLOR_2);
|
|
else if (brightness[EC_LED_COLOR_GREEN] != 0)
|
|
led_set_color_battery(LED_COLOR_1);
|
|
else
|
|
led_set_color_battery(LED_OFF);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static enum led_states led_get_state(void)
|
|
{
|
|
int charge_lvl;
|
|
enum led_states new_state = LED_NUM_STATES;
|
|
|
|
switch (charge_get_state()) {
|
|
case PWR_STATE_CHARGE:
|
|
/* Get percent charge */
|
|
charge_lvl = charge_get_percent();
|
|
/* Determine which charge state to use */
|
|
new_state = charge_lvl <= led.charge_lvl_1 ?
|
|
STATE_CHARGING_LVL_1 : STATE_CHARGING_LVL_2;
|
|
break;
|
|
case PWR_STATE_DISCHARGE_FULL:
|
|
if (extpower_is_present()) {
|
|
new_state = STATE_CHARGING_LVL_3;
|
|
break;
|
|
}
|
|
/* Intentional fall-through */
|
|
case PWR_STATE_DISCHARGE /* and PWR_STATE_DISCHARGE_FULL */:
|
|
if (chipset_in_state(CHIPSET_STATE_ON))
|
|
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 PWR_STATE_ERROR:
|
|
new_state = STATE_BATTERY_ERROR;
|
|
break;
|
|
case PWR_STATE_CHARGE_NEAR_FULL:
|
|
new_state = STATE_CHARGING_LVL_3;
|
|
break;
|
|
case PWR_STATE_IDLE: /* External power connected in IDLE */
|
|
if (charge_get_flags() & CHARGE_FLAG_FORCE_IDLE)
|
|
new_state = STATE_FACTORY_TEST;
|
|
else
|
|
new_state = STATE_DISCHARGE_S0;
|
|
break;
|
|
default:
|
|
/* Other states don't alter LED behavior */
|
|
break;
|
|
}
|
|
|
|
return new_state;
|
|
}
|
|
|
|
static void led_update_battery(void)
|
|
{
|
|
static int ticks;
|
|
int phase;
|
|
enum led_states desired_state = led_get_state();
|
|
|
|
/* Get updated state based on power state and charge level */
|
|
if (desired_state < LED_NUM_STATES && desired_state != led.state) {
|
|
/* State is changing */
|
|
led.state = desired_state;
|
|
/* Reset ticks counter when state changes */
|
|
ticks = 0;
|
|
}
|
|
|
|
/*
|
|
* Determine the which phase of the state table to use. Assume it's
|
|
* phase 0. If the time values for both phases of the current state are
|
|
* not -1, then this state uses some blinking pattern. The phase is then
|
|
* determined by taking the modulo of ticks by the blinking pattern
|
|
* period.
|
|
*/
|
|
phase = 0;
|
|
if ((led.state_table[led.state][LED_PHASE_0].time != LED_INDEFINITE) &&
|
|
(led.state_table[led.state][LED_PHASE_1].time != LED_INDEFINITE)) {
|
|
int period;
|
|
|
|
period = led.state_table[led.state][LED_PHASE_0].time +
|
|
led.state_table[led.state][LED_PHASE_1].time;
|
|
if (period)
|
|
phase = ticks % period <
|
|
led.state_table[led.state][LED_PHASE_0].time ?
|
|
0 : 1;
|
|
}
|
|
|
|
/* Set the color for the given state and phase */
|
|
led_set_color_battery(led.state_table[led.state][phase].color);
|
|
ticks++;
|
|
}
|
|
|
|
static void led_robo_update_power(void)
|
|
{
|
|
int level;
|
|
static int ticks;
|
|
|
|
if (chipset_in_state(CHIPSET_STATE_ON)) {
|
|
/* In S0 power LED is always on */
|
|
level = LED_ON_LVL;
|
|
ticks = 0;
|
|
} else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) &&
|
|
led.state <= STATE_CHARGING_LVL_3) {
|
|
int period;
|
|
|
|
/*
|
|
* If in suspend/standby and the device is charging, then the
|
|
* power LED is off for 600 msec, on for 3 seconds.
|
|
*/
|
|
period = LED_POWER_ON_TICKS + LED_POWER_OFF_TICKS;
|
|
level = ticks % period < LED_POWER_OFF_TICKS ?
|
|
LED_OFF_LVL : LED_ON_LVL;
|
|
ticks++;
|
|
} else {
|
|
level = LED_OFF_LVL;
|
|
ticks = 0;
|
|
}
|
|
|
|
led_set_color_power(level);
|
|
}
|
|
|
|
/* Called by hook task every hook tick (200 msec) */
|
|
static void led_update(void)
|
|
{
|
|
/* Update battery LED */
|
|
if (led_auto_control_is_enabled(EC_LED_ID_BATTERY_LED)) {
|
|
led_update_battery();
|
|
if (led.update_power != NULL)
|
|
(*led.update_power)();
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_TICK, led_update, HOOK_PRIO_DEFAULT);
|
|
|
|
static void led_init(void)
|
|
{
|
|
int sku = system_get_sku_id();
|
|
|
|
if ((sku >= 70 && sku <= 79) || (sku >= 124 && sku <= 125) ||
|
|
(sku >= 144 && sku <= 145)) {
|
|
led.charge_lvl_1 = LED_CHARGE_LEVEL_1_ROBO;
|
|
led.state_table = led_robo_state_table;
|
|
led.update_power = led_robo_update_power;
|
|
} else if (sku >= 160 && sku <= 166) {
|
|
led.charge_lvl_1 = LED_CHARGE_LEVEL_1_DEFAULT;
|
|
led.state_table = led_nasher_state_table;
|
|
led.update_power = NULL;
|
|
} else {
|
|
led.charge_lvl_1 = LED_CHARGE_LEVEL_1_DEFAULT;
|
|
led.state_table = led_default_state_table;
|
|
led.update_power = NULL;
|
|
}
|
|
led_set_color_battery(LED_OFF);
|
|
}
|
|
/* Make sure this comes after SKU ID hook */
|
|
DECLARE_HOOK(HOOK_INIT, led_init, HOOK_PRIO_DEFAULT + 2);
|