summaryrefslogtreecommitdiff
path: root/common/charge_state_v1.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/charge_state_v1.c')
-rw-r--r--common/charge_state_v1.c1025
1 files changed, 1025 insertions, 0 deletions
diff --git a/common/charge_state_v1.c b/common/charge_state_v1.c
new file mode 100644
index 0000000000..53c1dda5d2
--- /dev/null
+++ b/common/charge_state_v1.c
@@ -0,0 +1,1025 @@
+/* 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 charging task and state machine.
+ */
+
+#include "battery.h"
+#include "charge_state.h"
+#include "charger.h"
+#include "chipset.h"
+#include "common.h"
+#include "console.h"
+#include "extpower.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "printf.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHARGER, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args)
+
+/* Voltage debounce time */
+#define DEBOUNCE_TIME (10 * SECOND)
+
+/* Timeout after AP battery shutdown warning before we kill the AP */
+#define LOW_BATTERY_SHUTDOWN_TIMEOUT_US (30 * SECOND)
+
+#ifndef BATTERY_AP_OFF_LEVEL
+#define BATTERY_AP_OFF_LEVEL 0
+#endif
+
+static const char * const state_name[] = CHARGE_STATE_NAME_TABLE;
+
+static int state_machine_force_idle;
+
+static unsigned user_current_limit = -1U;
+
+static int fake_state_of_charge = -1;
+
+/* Current power state context */
+static struct charge_state_context task_ctx;
+
+static inline int is_charger_expired(
+ struct charge_state_context *ctx, timestamp_t now)
+{
+ return now.val - ctx->charger_update_time.val > CHARGER_UPDATE_PERIOD;
+}
+
+static inline void update_charger_time(
+ struct charge_state_context *ctx, timestamp_t now)
+{
+ ctx->charger_update_time.val = now.val;
+}
+
+/**
+ * Update memory-mapped battery information, used by ACPI _BIF and/or _BIX.
+ */
+static void update_battery_info(void)
+{
+ char *batt_str;
+ int batt_serial;
+
+ /* Design Capacity of Full */
+ battery_design_capacity((int *)host_get_memmap(EC_MEMMAP_BATT_DCAP));
+
+ /* Design Voltage */
+ battery_design_voltage((int *)host_get_memmap(EC_MEMMAP_BATT_DVLT));
+
+ /* Last Full Charge Capacity */
+ battery_full_charge_capacity(
+ (int *)host_get_memmap(EC_MEMMAP_BATT_LFCC));
+
+ /* Cycle Count */
+ battery_cycle_count((int *)host_get_memmap(EC_MEMMAP_BATT_CCNT));
+
+ /* Battery Manufacturer string */
+ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MFGR);
+ memset(batt_str, 0, EC_MEMMAP_TEXT_MAX);
+ battery_manufacturer_name(batt_str, EC_MEMMAP_TEXT_MAX);
+
+ /* Battery Model string */
+ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MODEL);
+ memset(batt_str, 0, EC_MEMMAP_TEXT_MAX);
+ battery_device_name(batt_str, EC_MEMMAP_TEXT_MAX);
+
+ /* Battery Type string */
+ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_TYPE);
+ battery_device_chemistry(batt_str, EC_MEMMAP_TEXT_MAX);
+
+ /* Smart battery serial number is 16 bits */
+ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_SERIAL);
+ memset(batt_str, 0, EC_MEMMAP_TEXT_MAX);
+ if (battery_serial_number(&batt_serial) == 0)
+ snprintf(batt_str, EC_MEMMAP_TEXT_MAX, "%04X", batt_serial);
+
+ /* Battery data is now present */
+ *host_get_memmap(EC_MEMMAP_BATTERY_VERSION) = 1;
+}
+
+/**
+ * Prevent battery from going into deep discharge state
+ */
+static void low_battery_shutdown(struct charge_state_context *ctx)
+{
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
+ /* AP is off, so shut down the EC now */
+ CPRINTF("[%T charge force EC hibernate due to low battery]\n");
+ system_hibernate(0, 0);
+ } else if (!ctx->shutdown_warning_time.val) {
+ /* Warn AP battery level is so low we'll shut down */
+ CPRINTF("[%T charge warn shutdown due to low battery]\n");
+ ctx->shutdown_warning_time = get_time();
+ host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN);
+ } else if (get_time().val > ctx->shutdown_warning_time.val +
+ LOW_BATTERY_SHUTDOWN_TIMEOUT_US) {
+ /* Timeout waiting for AP to shut down, so kill it */
+ CPRINTF("[%T charge force shutdown due to low battery]\n");
+ chipset_force_shutdown();
+ }
+}
+
+int charge_keep_power_off(void)
+{
+ int charge;
+
+ if (BATTERY_AP_OFF_LEVEL == 0)
+ return 0;
+
+ if (battery_remaining_capacity(&charge))
+ return charge_get_state() != PWR_STATE_ERROR;
+
+ return charge <= BATTERY_AP_OFF_LEVEL;
+}
+
+#ifdef CONFIG_CHARGER_EN_GPIO
+#ifdef CONFIG_CHARGER_EN_ACTIVE_LOW
+static void charge_set_charger_en_gpio(int level)
+{
+ gpio_set_level(GPIO_CHARGER_EN_L, !level);
+}
+
+static int charge_get_charger_en_gpio(void)
+{
+ return !gpio_get_level(GPIO_CHARGER_EN_L);
+}
+#else
+static void charge_set_charger_en_gpio(int level)
+{
+ gpio_set_level(GPIO_CHARGER_EN, level);
+}
+
+static int charge_get_charger_en_gpio(void)
+{
+ return gpio_get_level(GPIO_CHARGER_EN);
+}
+#endif
+#endif
+
+/**
+ * Enable or disable charging, and set requested voltage and current. If either
+ * of voltage and current is set to 0, charging is disable.
+ *
+ * @param voltage Requested voltage in mV. Set -1 to preserve current value.
+ * @param current Requested current in mA. Set -1 to preserve current value.
+ */
+static int charge_request(int voltage, int current)
+{
+ int rv = EC_SUCCESS;
+
+ if (voltage == -1 && current == -1)
+ return EC_SUCCESS;
+
+#ifdef CONFIG_CHARGER_EN_GPIO
+ if (voltage == 0 || current == 0) {
+ charge_set_charger_en_gpio(0);
+ return EC_SUCCESS;
+ } else {
+ charge_set_charger_en_gpio(1);
+ }
+#endif
+
+ if (voltage != -1)
+ rv |= charger_set_voltage(voltage);
+ if (current != -1)
+ rv |= charger_set_current(current);
+
+ return rv;
+}
+
+/**
+ * Common handler for charging states.
+ *
+ * This handler gets battery charging parameters, charger state, ac state, and
+ * timestamp. It also fills memory map and issues power events on state change.
+ */
+static int state_common(struct charge_state_context *ctx)
+{
+ int rv, d;
+
+ struct charge_state_data *curr = &ctx->curr;
+ struct charge_state_data *prev = &ctx->prev;
+ struct batt_params *batt = &ctx->curr.batt;
+ uint8_t *batt_flags = ctx->memmap_batt_flags;
+
+ /* Copy previous state and init new state */
+ ctx->prev = ctx->curr;
+ curr->ts = get_time();
+ curr->error = 0;
+
+ /* Detect AC change */
+ curr->ac = charge_get_flags() & CHARGE_FLAG_EXTERNAL_POWER;
+ if (curr->ac != prev->ac) {
+ if (curr->ac) {
+ /* AC on
+ * Initialize charger to power on reset mode
+ */
+ rv = charger_post_init();
+ if (rv)
+ curr->error |= F_CHARGER_INIT;
+ }
+ }
+
+ if (curr->ac) {
+ *batt_flags |= EC_BATT_FLAG_AC_PRESENT;
+ if (charger_get_voltage(&curr->charging_voltage)) {
+ charge_request(0, 0);
+ curr->error |= F_CHARGER_VOLTAGE;
+ }
+ if (charger_get_current(&curr->charging_current)) {
+ charge_request(0, 0);
+ curr->error |= F_CHARGER_CURRENT;
+ }
+#ifdef CONFIG_CHARGER_EN_GPIO
+ if (!charge_get_charger_en_gpio()) {
+ curr->charging_voltage = 0;
+ curr->charging_current = 0;
+ }
+#endif
+ } else {
+ *batt_flags &= ~EC_BATT_FLAG_AC_PRESENT;
+ /* AC disconnected should get us out of force idle mode. */
+ state_machine_force_idle = 0;
+ }
+
+#if defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \
+ defined(CONFIG_BATTERY_PRESENT_GPIO)
+ if (!battery_is_present()) {
+ curr->error |= F_BATTERY_NOT_CONNECTED;
+ return curr->error;
+ }
+#endif
+
+ /* Read params and see if battery is responsive */
+ battery_get_params(batt);
+ if (!(batt->flags & BATT_FLAG_RESPONSIVE)) {
+ /* Check low battery condition and retry */
+ if (curr->ac && ctx->battery_responsive &&
+ !(curr->error & F_CHARGER_MASK)) {
+ ctx->battery_responsive = 0;
+ /*
+ * Try to revive ultra low voltage pack. Charge
+ * battery pack with minimum current and maximum
+ * voltage for 30 seconds.
+ */
+ charge_request(ctx->battery->voltage_max,
+ ctx->battery->precharge_current);
+ for (d = 0; d < 30; d++) {
+ sleep(1);
+ battery_get_params(batt);
+ if (batt->flags & BATT_FLAG_RESPONSIVE) {
+ ctx->battery_responsive = 1;
+ break;
+ }
+ }
+ }
+
+ /* Set error if battery is still unresponsive */
+ if (!(batt->flags & BATT_FLAG_RESPONSIVE)) {
+ curr->error |= F_BATTERY_UNRESPONSIVE;
+ return curr->error;
+ }
+ } else {
+ ctx->battery_responsive = 1;
+ }
+
+ /* Translate flags */
+ if (batt->flags & BATT_FLAG_BAD_ANY)
+ curr->error |= F_BATTERY_GET_PARAMS;
+ if (batt->flags & BATT_FLAG_BAD_VOLTAGE)
+ curr->error |= F_BATTERY_VOLTAGE;
+ if (batt->flags & BATT_FLAG_BAD_CHARGE_PERCENT)
+ curr->error |= F_BATTERY_STATE_OF_CHARGE;
+
+ *ctx->memmap_batt_volt = batt->voltage;
+
+ /* Memory mapped value: discharge rate */
+ *ctx->memmap_batt_rate = batt->current < 0 ?
+ -batt->current : batt->current;
+
+ /* Fake state of charge if necessary */
+ if (fake_state_of_charge >= 0) {
+ batt->state_of_charge = fake_state_of_charge;
+ curr->error &= ~F_BATTERY_STATE_OF_CHARGE;
+ }
+
+ if (batt->state_of_charge != prev->batt.state_of_charge) {
+ rv = battery_full_charge_capacity(&d);
+ if (!rv && d != *(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC)) {
+ *(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC) = d;
+ /* Notify host to re-read battery information */
+ host_set_single_event(EC_HOST_EVENT_BATTERY);
+ }
+ }
+
+ /* Prevent deep discharging */
+ if (!curr->ac) {
+ if ((batt->state_of_charge < BATTERY_LEVEL_SHUTDOWN &&
+ !(curr->error & F_BATTERY_STATE_OF_CHARGE)) ||
+ (batt->voltage <= ctx->battery->voltage_min &&
+ !(curr->error & F_BATTERY_VOLTAGE)))
+ low_battery_shutdown(ctx);
+ }
+
+ /* Check battery presence */
+ if (curr->error & F_BATTERY_MASK) {
+ *ctx->memmap_batt_flags &= ~EC_BATT_FLAG_BATT_PRESENT;
+ return curr->error;
+ }
+
+ *ctx->memmap_batt_flags |= EC_BATT_FLAG_BATT_PRESENT;
+
+ /* Battery charge level low */
+ if (batt->state_of_charge <= BATTERY_LEVEL_LOW &&
+ prev->batt.state_of_charge > BATTERY_LEVEL_LOW)
+ host_set_single_event(EC_HOST_EVENT_BATTERY_LOW);
+
+ /* Battery charge level critical */
+ if (batt->state_of_charge <= BATTERY_LEVEL_CRITICAL) {
+ *ctx->memmap_batt_flags |= EC_BATT_FLAG_LEVEL_CRITICAL;
+ /* Send battery critical host event */
+ if (prev->batt.state_of_charge > BATTERY_LEVEL_CRITICAL)
+ host_set_single_event(EC_HOST_EVENT_BATTERY_CRITICAL);
+ } else {
+ *ctx->memmap_batt_flags &= ~EC_BATT_FLAG_LEVEL_CRITICAL;
+ }
+
+#ifdef CONFIG_BATTERY_VENDOR_PARAMS
+ /* Apply battery pack vendor charging method */
+ battery_vendor_params(batt);
+#endif
+
+#ifdef CONFIG_CHARGER_CURRENT_LIMIT
+ if (batt->desired_current > CONFIG_CHARGER_CURRENT_LIMIT)
+ batt->desired_current = CONFIG_CHARGER_CURRENT_LIMIT;
+#endif
+ if (batt->desired_current > user_current_limit)
+ batt->desired_current = user_current_limit;
+
+ if (fake_state_of_charge >= 0)
+ *ctx->memmap_batt_cap =
+ fake_state_of_charge *
+ *(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC) / 100;
+ else if (battery_remaining_capacity(&d))
+ ctx->curr.error |= F_BATTERY_CAPACITY;
+ else
+ *ctx->memmap_batt_cap = d;
+
+ return ctx->curr.error;
+}
+
+/**
+ * Init state handler
+ *
+ * - check ac, charger, battery and temperature
+ * - initialize charger
+ * - new states: DISCHARGE, IDLE
+ */
+static enum charge_state state_init(struct charge_state_context *ctx)
+{
+ /* Stop charger, unconditionally */
+ charge_request(0, 0);
+
+ /* if battery was not detected initially, get battery info again */
+ if (ctx->battery == NULL)
+ ctx->battery = battery_get_info();
+
+ /* Update static battery info */
+ update_battery_info();
+
+ /* Clear shutdown timer */
+ ctx->shutdown_warning_time.val = 0;
+
+ /* If AC is not present, switch to discharging state */
+ if (!ctx->curr.ac)
+ return PWR_STATE_DISCHARGE;
+
+ /* Check general error conditions */
+ if (ctx->curr.error)
+ return PWR_STATE_ERROR;
+
+ /* Send battery event to host */
+ host_set_single_event(EC_HOST_EVENT_BATTERY);
+
+ return PWR_STATE_IDLE0;
+}
+
+/**
+ * Idle state handler
+ *
+ * - both charger and battery are online
+ * - detect charger and battery status change
+ * - new states: CHARGE, INIT
+ */
+static enum charge_state state_idle(struct charge_state_context *ctx)
+{
+ struct batt_params *batt = &ctx->curr.batt;
+
+ /* If we are forcing idle mode, then just stay in IDLE. */
+ if (state_machine_force_idle)
+ return PWR_STATE_UNCHANGE;
+
+ if (!ctx->curr.ac)
+ return PWR_STATE_REINIT;
+
+ if (ctx->curr.error)
+ return PWR_STATE_ERROR;
+
+ /* Prevent charging in idle mode */
+ if (ctx->curr.charging_voltage ||
+ ctx->curr.charging_current)
+ return PWR_STATE_REINIT;
+
+ if (batt->state_of_charge >= BATTERY_LEVEL_FULL)
+ return PWR_STATE_UNCHANGE;
+
+ /* Configure init charger state and switch to charge state */
+ if (batt->flags & BATT_FLAG_WANT_CHARGE) {
+ int want_current =
+ charger_closest_current(batt->desired_current);
+
+ CPRINTF("[%T Charge start %dmV %dmA]\n",
+ batt->desired_voltage, want_current);
+
+ if (charge_request(batt->desired_voltage, want_current))
+ return PWR_STATE_ERROR;
+
+ update_charger_time(ctx, get_time());
+
+ if (ctx->curr.batt.state_of_charge < BATTERY_LEVEL_NEAR_FULL)
+ return PWR_STATE_CHARGE;
+ else
+ return PWR_STATE_CHARGE_NEAR_FULL;
+ }
+
+ return PWR_STATE_UNCHANGE;
+}
+
+/**
+ * Charge state handler
+ *
+ * - detect battery status change
+ * - new state: INIT
+ */
+static enum charge_state state_charge(struct charge_state_context *ctx)
+{
+ struct charge_state_data *curr = &ctx->curr;
+ struct batt_params *batt = &ctx->curr.batt;
+ int debounce = 0;
+ int want_current;
+ int want_voltage;
+ timestamp_t now;
+
+ if (curr->error)
+ return PWR_STATE_ERROR;
+
+ /*
+ * Some chargers will reset out from underneath us. If this happens,
+ * reinitialize charging.
+ */
+ if (curr->charging_voltage == 0 ||
+ curr->charging_current == 0)
+ return PWR_STATE_REINIT;
+
+ if (!curr->ac)
+ return PWR_STATE_REINIT;
+
+ /* Stop charging if charging is no longer allowed */
+ if (!(batt->flags & BATT_FLAG_WANT_CHARGE)) {
+ if (charge_request(0, 0))
+ return PWR_STATE_ERROR;
+ return PWR_STATE_IDLE;
+ }
+
+ now = get_time();
+
+ /*
+ * Adjust desired voltage to one the charger can actually supply
+ * or else we'll keep asking for a voltage the charger can't actually
+ * supply.
+ */
+ want_voltage = charger_closest_voltage(batt->desired_voltage);
+
+ if (want_voltage != curr->charging_voltage) {
+ CPRINTF("[%T Charge voltage %dmV]\n", want_voltage);
+ if (charge_request(want_voltage, -1))
+ return PWR_STATE_ERROR;
+ update_charger_time(ctx, now);
+ }
+
+ /*
+ * Adjust desired current to one the charger can actually supply before
+ * we do debouncing, or else we'll keep asking for a current the
+ * charger can't actually supply.
+ */
+ want_current = charger_closest_current(batt->desired_current);
+
+ if (want_current == curr->charging_current) {
+ /* Tick charger watchdog */
+ if (!is_charger_expired(ctx, now))
+ return PWR_STATE_UNCHANGE;
+ } else if (want_current > curr->charging_current) {
+ if (!timestamp_expired(ctx->voltage_debounce_time, &now))
+ return PWR_STATE_UNCHANGE;
+ } else {
+ debounce = 1;
+ }
+
+ if (want_current != curr->charging_current) {
+ CPRINTF("[%T Charge current %dmA @ %dmV]\n",
+ want_current, batt->desired_voltage);
+ }
+
+ if (charge_request(-1, want_current))
+ return PWR_STATE_ERROR;
+
+ /* Update charger watchdog timer and debounce timer */
+ update_charger_time(ctx, now);
+ if (debounce)
+ ctx->voltage_debounce_time.val = now.val + DEBOUNCE_TIME;
+
+ return PWR_STATE_UNCHANGE;
+}
+
+/**
+ * Discharge state handler
+ *
+ * - detect ac status
+ * - new state: INIT
+ */
+static enum charge_state state_discharge(struct charge_state_context *ctx)
+{
+ struct batt_params *batt = &ctx->curr.batt;
+ int8_t bat_temp_c = DECI_KELVIN_TO_CELSIUS(batt->temperature);
+ if (ctx->curr.ac)
+ return PWR_STATE_REINIT;
+
+ if (ctx->curr.error)
+ return PWR_STATE_ERROR;
+
+ /* Handle overtemp in discharging state by powering off host */
+ if ((bat_temp_c >= ctx->battery->discharging_max_c ||
+ bat_temp_c < ctx->battery->discharging_min_c) &&
+ chipset_in_state(CHIPSET_STATE_ON)) {
+ CPRINTF("[%T charge force shutdown due to battery temp]\n");
+ chipset_force_shutdown();
+ host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN);
+ }
+ return PWR_STATE_UNCHANGE;
+}
+
+/**
+ * Error state handler
+ *
+ * - check charger and battery communication
+ * - log error
+ * - new state: INIT
+ */
+static enum charge_state state_error(struct charge_state_context *ctx)
+{
+ static int logged_error;
+
+ if (!ctx->curr.error) {
+ logged_error = 0;
+ return PWR_STATE_REINIT;
+ }
+
+ charge_request(0, 0);
+
+ /* Debug output */
+ if (ctx->curr.error != logged_error) {
+ CPRINTF("[%T Charge error: flag[%08b -> %08b], ac %d, "
+ " charger %s, battery %s\n",
+ logged_error, ctx->curr.error, ctx->curr.ac,
+ (ctx->curr.error & F_CHARGER_MASK) ? "(err)" : "ok",
+ (ctx->curr.error & F_BATTERY_MASK) ? "(err)" : "ok");
+
+ logged_error = ctx->curr.error;
+ }
+
+ return PWR_STATE_UNCHANGE;
+}
+
+/**
+ * Print charging progress
+ */
+static void charging_progress(struct charge_state_context *ctx)
+{
+ int seconds, minutes;
+
+ if (ctx->curr.batt.state_of_charge != ctx->prev.batt.state_of_charge) {
+ if (ctx->curr.ac)
+ battery_time_to_full(&minutes);
+ else
+ battery_time_to_empty(&minutes);
+
+ CPRINTF("[%T Battery %3d%% / %dh:%d]\n",
+ ctx->curr.batt.state_of_charge,
+ minutes / 60, minutes % 60);
+ return;
+ }
+
+ if (ctx->curr.charging_voltage != ctx->prev.charging_voltage &&
+ ctx->trickle_charging_time.val) {
+ /* Calculate minutes by dividing usec by 60 million. GNU
+ * toolchain generates architecture dependent calls instead of
+ * machine code when the divisor is large, so break the
+ * calculation into 2 lines.
+ */
+ seconds = (int)(get_time().val -
+ ctx->trickle_charging_time.val) / (int)SECOND;
+ minutes = seconds / 60;
+ CPRINTF("[%T Precharge CHG(%dmV) BATT(%dmV %dmA) "
+ "%dh:%d]\n", ctx->curr.charging_voltage,
+ ctx->curr.batt.voltage, ctx->curr.batt.current,
+ minutes / 60, minutes % 60);
+ }
+}
+
+enum charge_state charge_get_state(void)
+{
+ return task_ctx.curr.state;
+}
+
+uint32_t charge_get_flags(void)
+{
+ uint32_t flags = 0;
+
+ if (state_machine_force_idle)
+ flags |= CHARGE_FLAG_FORCE_IDLE;
+ if (extpower_is_present())
+ flags |= CHARGE_FLAG_EXTERNAL_POWER;
+
+ return flags;
+}
+
+int charge_get_percent(void)
+{
+ return task_ctx.curr.batt.state_of_charge;
+}
+
+int charge_temp_sensor_get_val(int idx, int *temp_ptr)
+{
+ const struct batt_params *batt = &task_ctx.curr.batt;
+
+ if (!(batt->flags & BATT_FLAG_RESPONSIVE))
+ return EC_ERROR_UNKNOWN;
+
+ *temp_ptr = C_TO_K(DECI_KELVIN_TO_CELSIUS(batt->temperature));
+ return EC_SUCCESS;
+}
+
+int charge_want_shutdown(void)
+{
+ return (charge_get_state() == PWR_STATE_DISCHARGE) &&
+ charge_get_percent() < BATTERY_LEVEL_SHUTDOWN;
+}
+
+static int charge_force_idle(int enable)
+{
+ if (enable) {
+ /*
+ * Force-idle state is only meaningful if external power is
+ * present. If it's not present we can't charge anyway...
+ */
+ if (!(charge_get_flags() & CHARGE_FLAG_EXTERNAL_POWER))
+ return EC_ERROR_UNKNOWN;
+ charger_post_init();
+ }
+ state_machine_force_idle = enable;
+ return EC_SUCCESS;
+}
+
+/**
+ * Battery charging task
+ */
+void charger_task(void)
+{
+ struct charge_state_context *ctx = &task_ctx;
+ timestamp_t ts;
+ int sleep_usec = POLL_PERIOD_SHORT, diff_usec, sleep_next;
+ enum charge_state new_state;
+ uint8_t batt_flags;
+
+ while (1) {
+ state_common(ctx);
+
+#ifdef CONFIG_CHARGER_TIMEOUT_HOURS
+ if (ctx->curr.state == PWR_STATE_CHARGE &&
+ ctx->charge_state_updated_time.val +
+ CONFIG_CHARGER_TIMEOUT_HOURS * HOUR < ctx->curr.ts.val) {
+ CPRINTF("[%T Charge timed out after %d hours]\n",
+ CONFIG_CHARGER_TIMEOUT_HOURS);
+ charge_force_idle(1);
+ }
+#endif /* CONFIG_CHARGER_TIMEOUT_HOURS */
+
+ switch (ctx->prev.state) {
+ case PWR_STATE_INIT:
+ case PWR_STATE_REINIT:
+ new_state = state_init(ctx);
+ break;
+ case PWR_STATE_IDLE0:
+ new_state = state_idle(ctx);
+ /* If still idling, move from IDLE0 to IDLE */
+ if (new_state == PWR_STATE_UNCHANGE)
+ new_state = PWR_STATE_IDLE;
+ break;
+ case PWR_STATE_IDLE:
+ new_state = state_idle(ctx);
+ break;
+ case PWR_STATE_DISCHARGE:
+ new_state = state_discharge(ctx);
+ break;
+ case PWR_STATE_CHARGE:
+ new_state = state_charge(ctx);
+ if (new_state == PWR_STATE_UNCHANGE &&
+ (ctx->curr.batt.state_of_charge >=
+ BATTERY_LEVEL_NEAR_FULL)) {
+ /* Almost done charging */
+ new_state = PWR_STATE_CHARGE_NEAR_FULL;
+ }
+ break;
+
+ case PWR_STATE_CHARGE_NEAR_FULL:
+ new_state = state_charge(ctx);
+ if (new_state == PWR_STATE_UNCHANGE &&
+ (ctx->curr.batt.state_of_charge <
+ BATTERY_LEVEL_NEAR_FULL)) {
+ /* Battery below almost-full threshold. */
+ new_state = PWR_STATE_CHARGE;
+ }
+ break;
+ case PWR_STATE_ERROR:
+ new_state = state_error(ctx);
+ break;
+ default:
+ CPRINTF("[%T Charge state %d undefined]\n",
+ ctx->curr.state);
+ ctx->curr.state = PWR_STATE_ERROR;
+ new_state = PWR_STATE_ERROR;
+ }
+
+ if (state_machine_force_idle &&
+ ctx->prev.state != PWR_STATE_IDLE0 &&
+ ctx->prev.state != PWR_STATE_IDLE &&
+ ctx->prev.state != PWR_STATE_INIT &&
+ ctx->prev.state != PWR_STATE_REINIT)
+ new_state = PWR_STATE_REINIT;
+
+ if (new_state) {
+ ctx->curr.state = new_state;
+ CPRINTF("[%T Charge state %s -> %s after %.6ld sec]\n",
+ state_name[ctx->prev.state],
+ state_name[new_state],
+ ctx->curr.ts.val -
+ ctx->charge_state_updated_time.val);
+ ctx->charge_state_updated_time = ctx->curr.ts;
+ hook_notify(HOOK_CHARGE_STATE_CHANGE);
+ }
+
+ switch (new_state) {
+ case PWR_STATE_IDLE0:
+ /*
+ * First time transitioning from init -> idle. Don't
+ * set the flags or LED yet because we may transition
+ * to charging on the next call and we don't want to
+ * blink the LED green.
+ */
+ sleep_usec = POLL_PERIOD_SHORT;
+ break;
+ case PWR_STATE_CHARGE_NEAR_FULL:
+ /*
+ * Battery is almost charged. The last few percent
+ * take a loooong time, so fall through and look like
+ * we're charged. This mirrors similar hacks at the
+ * ACPI/kernel/UI level.
+ */
+ case PWR_STATE_IDLE:
+ batt_flags = *ctx->memmap_batt_flags;
+ batt_flags &= ~EC_BATT_FLAG_CHARGING;
+ batt_flags &= ~EC_BATT_FLAG_DISCHARGING;
+ *ctx->memmap_batt_flags = batt_flags;
+
+ /* Charge done */
+ sleep_usec = (new_state == PWR_STATE_IDLE ?
+ POLL_PERIOD_LONG : POLL_PERIOD_CHARGE);
+ break;
+ case PWR_STATE_DISCHARGE:
+ batt_flags = *ctx->memmap_batt_flags;
+ batt_flags &= ~EC_BATT_FLAG_CHARGING;
+ batt_flags |= EC_BATT_FLAG_DISCHARGING;
+ *ctx->memmap_batt_flags = batt_flags;
+ sleep_usec = POLL_PERIOD_LONG;
+ break;
+ case PWR_STATE_CHARGE:
+ batt_flags = *ctx->memmap_batt_flags;
+ batt_flags |= EC_BATT_FLAG_CHARGING;
+ batt_flags &= ~EC_BATT_FLAG_DISCHARGING;
+ *ctx->memmap_batt_flags = batt_flags;
+
+ /* Charging */
+ sleep_usec = POLL_PERIOD_CHARGE;
+ break;
+ case PWR_STATE_ERROR:
+ /* Error */
+ sleep_usec = POLL_PERIOD_CHARGE;
+ break;
+ case PWR_STATE_UNCHANGE:
+ /* Don't change sleep duration */
+ break;
+ default:
+ /* Other state; poll quickly and hope it goes away */
+ sleep_usec = POLL_PERIOD_SHORT;
+ }
+
+#ifdef CONFIG_EXTPOWER_FALCO
+ watch_adapter_closely(ctx);
+ sleep_usec = EXTPOWER_FALCO_POLL_PERIOD;
+#endif
+
+ /* Show charging progress in console */
+ charging_progress(ctx);
+
+ ts = get_time();
+ diff_usec = (int)(ts.val - ctx->curr.ts.val);
+ sleep_next = sleep_usec - diff_usec;
+
+ if (ctx->curr.state == PWR_STATE_DISCHARGE &&
+ chipset_in_state(CHIPSET_STATE_ANY_OFF |
+ CHIPSET_STATE_SUSPEND)) {
+ /*
+ * Discharging and system is off or suspended, so no
+ * need to poll frequently. charge_hook() will wake us
+ * up if anything important changes.
+ */
+ sleep_next = POLL_PERIOD_VERY_LONG - diff_usec;
+ } else if (sleep_next < MIN_SLEEP_USEC) {
+ sleep_next = MIN_SLEEP_USEC;
+ } else if (sleep_next > MAX_SLEEP_USEC) {
+ sleep_next = MAX_SLEEP_USEC;
+ }
+
+ task_wait_event(sleep_next);
+ }
+}
+
+/*****************************************************************************/
+/* Hooks */
+
+/**
+ * Charge notification hook.
+ *
+ * This is triggered when the AC state changes or the system boots, so that
+ * we can update our charging state.
+ */
+static void charge_hook(void)
+{
+ /* Wake up the task now */
+ task_wake(TASK_ID_CHARGER);
+}
+DECLARE_HOOK(HOOK_CHIPSET_RESUME, charge_hook, HOOK_PRIO_DEFAULT);
+DECLARE_HOOK(HOOK_AC_CHANGE, charge_hook, HOOK_PRIO_DEFAULT);
+
+static void charge_init(void)
+{
+ struct charge_state_context *ctx = &task_ctx;
+
+ ctx->prev.state = PWR_STATE_INIT;
+ ctx->curr.state = PWR_STATE_INIT;
+ ctx->trickle_charging_time.val = 0;
+ ctx->battery = battery_get_info();
+ ctx->charger = charger_get_info();
+ /* Assume the battery is responsive until proven otherwise */
+ ctx->battery_responsive = 1;
+
+ /* Set up LPC direct memmap */
+ ctx->memmap_batt_volt =
+ (uint32_t *)host_get_memmap(EC_MEMMAP_BATT_VOLT);
+ ctx->memmap_batt_rate =
+ (uint32_t *)host_get_memmap(EC_MEMMAP_BATT_RATE);
+ ctx->memmap_batt_cap =
+ (uint32_t *)host_get_memmap(EC_MEMMAP_BATT_CAP);
+ ctx->memmap_batt_flags = host_get_memmap(EC_MEMMAP_BATT_FLAG);
+}
+DECLARE_HOOK(HOOK_INIT, charge_init, HOOK_PRIO_DEFAULT);
+
+
+static void charge_shutdown(void)
+{
+ /* Hibernate immediately if battery level is too low */
+ if (charge_want_shutdown()) {
+ CPRINTF("[%T charge force EC hibernate after"
+ " shutdown due to low battery]\n");
+ system_hibernate(0, 0);
+ }
+}
+/*
+ * Run the charge shutdown hook last, since when it hibernates no subsequent
+ * hooks would be run.
+ */
+DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, charge_shutdown, HOOK_PRIO_LAST);
+
+/*****************************************************************************/
+/* Host commands */
+
+static int charge_command_charge_control(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_charge_control *p = args->params;
+ int rv;
+
+ if (system_is_locked())
+ return EC_RES_ACCESS_DENIED;
+
+ rv = charge_force_idle(p->mode != CHARGE_CONTROL_NORMAL);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+#ifdef CONFIG_CHARGER_DISCHARGE_ON_AC
+ rv = board_discharge_on_ac(p->mode == CHARGE_CONTROL_DISCHARGE);
+ if (rv != EC_SUCCESS)
+ return rv;
+#endif
+
+ return EC_RES_SUCCESS;
+}
+/*
+ * TODO(crbug.com/239197) : Adding both versions to the version mask is a
+ * temporary workaround for a problem in the cros_ec driver. Drop
+ * EC_VER_MASK(0) once cros_ec driver can send the correct version.
+ */
+DECLARE_HOST_COMMAND(EC_CMD_CHARGE_CONTROL, charge_command_charge_control,
+ EC_VER_MASK(0) | EC_VER_MASK(1));
+
+static int charge_command_dump(struct host_cmd_handler_args *args)
+{
+ char *dest = (char *)args->response;
+
+ if (system_is_locked())
+ return EC_RES_ACCESS_DENIED;
+
+ ASSERT(sizeof(task_ctx) <= args->response_max);
+
+ memcpy(dest, &task_ctx, sizeof(task_ctx));
+ args->response_size = sizeof(task_ctx);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_CHARGE_DUMP, charge_command_dump,
+ EC_VER_MASK(0));
+
+static void reset_current_limit(void)
+{
+ user_current_limit = -1;
+}
+DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, reset_current_limit, HOOK_PRIO_DEFAULT);
+DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, reset_current_limit, HOOK_PRIO_DEFAULT);
+
+static int charge_command_current_limit(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_current_limit *p = args->params;
+
+ user_current_limit = p->limit;
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_CHARGE_CURRENT_LIMIT, charge_command_current_limit,
+ EC_VER_MASK(0));
+
+/*****************************************************************************/
+/* Console commands */
+
+static int command_battfake(int argc, char **argv)
+{
+ char *e;
+ int v;
+
+ if (argc == 2) {
+ v = strtoi(argv[1], &e, 0);
+ if (*e || v < -1 || v > 100)
+ return EC_ERROR_PARAM1;
+
+ fake_state_of_charge = v;
+ }
+
+ if (fake_state_of_charge < 0)
+ ccprintf("Reporting real battery level\n");
+ else
+ ccprintf("Reporting fake battery level %d%%\n",
+ fake_state_of_charge);
+
+ /* Wake charger task immediately to see new level */
+ task_wake(TASK_ID_CHARGER);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(battfake, command_battfake,
+ "percent (-1 = use real level)",
+ "Set fake battery level",
+ NULL);