blob: cbb1fd9ab93ba7ddf824d00d7f3cdb5b56fe26ce [file] [log] [blame]
/* Copyright (c) 2013 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.
*
* Battery LED state machine to drive RGB LED on LP5562
*/
#include "battery.h"
#include "battery_smart.h"
#include "common.h"
#include "driver/led/lp5562.h"
#include "extpower.h"
#include "hooks.h"
#include "host_command.h"
#include "pmu_tpschrome.h"
#include "timer.h"
#include "util.h"
#define GREEN_LED_THRESHOLD 94
/* Minimal interval between changing LED color to green and yellow. */
#define LED_WAIT_INTERVAL (15 * SECOND)
/* We use yellow LED instead of blue LED. Re-map colors here. */
#define LED_COLOR_NONE LP5562_COLOR_NONE
#define LED_COLOR_GREEN LP5562_COLOR_GREEN(0x10)
#define LED_COLOR_YELLOW LP5562_COLOR_BLUE(0x40)
#define LED_COLOR_RED LP5562_COLOR_RED(0x80)
/* LED states */
enum led_state_t {
LED_STATE_SOLID_RED,
LED_STATE_SOLID_GREEN,
LED_STATE_SOLID_YELLOW,
/* Not an actual state */
LED_STATE_OFF,
/* Used to force next LED color update */
LED_STATE_INVALID,
};
static enum led_state_t last_state = LED_STATE_OFF;
static int led_auto_control = 1;
static int set_led_color(enum led_state_t state)
{
int rv = EC_SUCCESS;
if (!led_auto_control || state == last_state)
return EC_SUCCESS;
switch (state) {
case LED_STATE_SOLID_RED:
rv = lp5562_set_color(LED_COLOR_RED);
break;
case LED_STATE_SOLID_GREEN:
rv = lp5562_set_color(LED_COLOR_GREEN);
break;
case LED_STATE_SOLID_YELLOW:
rv = lp5562_set_color(LED_COLOR_YELLOW);
break;
default:
break;
}
if (rv == EC_SUCCESS)
last_state = state;
else
last_state = LED_STATE_INVALID;
return rv;
}
/**
* Directly read state of charge (0-100) of battery.
*/
static int battery_state_of_charge(int *percent)
{
return sb_read(SB_RELATIVE_STATE_OF_CHARGE, percent);
}
/*****************************************************************************/
/* Host commands */
static int led_command_control(struct host_cmd_handler_args *args)
{
const struct ec_params_led_control *p = args->params;
struct ec_response_led_control *r = args->response;
int i;
uint8_t clipped[EC_LED_COLOR_COUNT];
/* Only support battery LED control */
if (p->led_id != EC_LED_ID_BATTERY_LED)
return EC_RES_INVALID_PARAM;
if (p->flags & EC_LED_FLAGS_AUTO) {
if (!extpower_is_present())
lp5562_poweroff();
last_state = LED_STATE_OFF;
led_auto_control = 1;
} else if (!(p->flags & EC_LED_FLAGS_QUERY)) {
for (i = 0; i < EC_LED_COLOR_COUNT; ++i)
clipped[i] = MIN(p->brightness[i], 0x80);
led_auto_control = 0;
if (!extpower_is_present())
lp5562_poweron();
if (lp5562_set_color((clipped[EC_LED_COLOR_RED] << 16) +
(clipped[EC_LED_COLOR_GREEN] << 8) +
clipped[EC_LED_COLOR_YELLOW]))
return EC_RES_ERROR;
}
r->brightness_range[EC_LED_COLOR_RED] = 0x80;
r->brightness_range[EC_LED_COLOR_GREEN] = 0x80;
r->brightness_range[EC_LED_COLOR_BLUE] = 0x0;
r->brightness_range[EC_LED_COLOR_YELLOW] = 0x80;
r->brightness_range[EC_LED_COLOR_WHITE] = 0x0;
args->response_size = sizeof(struct ec_response_led_control);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_LED_CONTROL,
led_command_control,
EC_VER_MASK(1));
/*****************************************************************************/
/* Hooks */
static void battery_led_update(void)
{
int rv;
int state_of_charge;
enum led_state_t state = LED_STATE_OFF;
/* Current states and next states */
static int led_power = -1;
int new_led_power;
/*
* The time before which we should not change LED
* color between green and yellow.
*/
static timestamp_t led_update_deadline = {.val = 0};
/* Determine LED power */
new_led_power = extpower_is_present();
if (new_led_power != led_power) {
if (new_led_power) {
rv = lp5562_poweron();
} else {
rv = lp5562_poweroff();
set_led_color(LED_STATE_OFF);
led_update_deadline.val = 0;
}
if (!rv)
led_power = new_led_power;
}
if (!new_led_power)
return;
/*
* LED power is controlled by accessory detection. We only
* set color here.
*/
switch (charge_get_state()) {
case ST_IDLE0:
case ST_BAD_COND:
case ST_PRE_CHARGING:
state = LED_STATE_SOLID_YELLOW;
break;
case ST_IDLE:
case ST_DISCHARGING:
case ST_CHARGING:
if (battery_state_of_charge(&state_of_charge)) {
/* Cannot talk to the battery. Set LED to red. */
state = LED_STATE_SOLID_RED;
break;
}
if (state_of_charge < GREEN_LED_THRESHOLD)
state = LED_STATE_SOLID_YELLOW;
else
state = LED_STATE_SOLID_GREEN;
break;
case ST_CHARGING_ERROR:
state = LED_STATE_SOLID_RED;
break;
}
if (state == LED_STATE_SOLID_GREEN ||
state == LED_STATE_SOLID_YELLOW) {
if (!timestamp_expired(led_update_deadline, NULL))
return;
led_update_deadline.val =
get_time().val + LED_WAIT_INTERVAL;
} else {
led_update_deadline.val = 0;
}
set_led_color(state);
}
DECLARE_HOOK(HOOK_SECOND, battery_led_update, HOOK_PRIO_DEFAULT);