coreboot-libre-fam15h-rdimm/3rdparty/chromeec/board/coral/led.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);