Files
OpenCellular/common/led_lp5562.c
Vic Yang 0e024b2bae Fix a bug that I2C error may cause incorrect LED color
If there is an I2C transaction error while setting LED color, the three
LED color channels might be in an inconsistent state. In this case, we
should explicitly set the LED state to INVALID so as to force the next
LED color update.

BUG=None
TEST=Build success
BRANCH=Spring

Change-Id: I1353044ef782481872d692f15748413ef539cb27
Signed-off-by: Vic Yang <victoryang@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/66314
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
2013-08-21 19:20:44 -07:00

195 lines
4.7 KiB
C

/* 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 "common.h"
#include "ec_commands.h"
#include "extpower.h"
#include "hooks.h"
#include "host_command.h"
#include "lp5562.h"
#include "pmu_tpschrome.h"
#include "smart_battery.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;
}
/*****************************************************************************/
/* 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);