/* 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);