summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2014-03-26 16:56:38 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-04-01 21:37:32 +0000
commit9f40d3f90e7373b96256033b3ec7b4b8e5140881 (patch)
treebb2f4dc8742f869667938f8ee7a2ad4fb9cb38f8
parent71766a39262b6ed4fec51cba19f10c6bec7e9f19 (diff)
downloadchrome-ec-9f40d3f90e7373b96256033b3ec7b4b8e5140881.tar.gz
Add charge_state_v2 algorithm for use by Samus
This is a complete rewrite of the charge_state task used by x86 platforms. Rather than having a bunch of state-specific functions, each with their own error handling and special cases, this is organized like so: Forever: 1. Read everything we can from the battery and charger. 2. Figure out what we'd like to do (including error handling). 3. Allow for customization to override that. 4. Do it. Things I need to file bugs for are marked with "TODO(wfrichar)". I'll file the bugs after this CL goes in, so that they'll have something relevant to refer to. BUG=chrome-os-partner:20881 BRANCH=ToT TEST=manual make buildall -j Try it on Samus, watch it charge from nearly empty to full, both with and without fastcharge enabled. Also undefine CONFIG_BATTERY_PRESENT_CUSTOM, plug and unplug the battery to be sure the trickle charging logic is correct when it can't tell if the battery is present. Change-Id: I3935cd3b87f322eb52178f8a675a886c16b75d58 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/191767 Reviewed-by: Randall Spangler <rspangler@chromium.org>
-rw-r--r--board/samus/board.c9
-rw-r--r--board/samus/board.h9
-rw-r--r--common/charge_state_v2.c717
-rw-r--r--driver/battery/samus.c129
-rw-r--r--driver/battery/smart.c4
-rw-r--r--driver/build.mk1
-rw-r--r--include/charge_state_v2.h38
-rw-r--r--include/config.h5
-rw-r--r--test/battery_get_params_smart.c87
-rw-r--r--test/battery_get_params_smart.tasklist17
-rw-r--r--test/build.mk4
-rw-r--r--test/sbs_charging_v2.c221
-rw-r--r--test/sbs_charging_v2.tasklist19
-rw-r--r--test/test_config.h22
14 files changed, 1266 insertions, 16 deletions
diff --git a/board/samus/board.c b/board/samus/board.c
index cfab3e9f16..9e203c19da 100644
--- a/board/samus/board.c
+++ b/board/samus/board.c
@@ -10,6 +10,7 @@
#include "backlight.h"
#include "battery.h"
#include "capsense.h"
+#include "charger.h"
#include "common.h"
#include "driver/temp_sensor/tmp006.h"
#include "driver/als_isl29035.h"
@@ -332,3 +333,11 @@ enum battery_present battery_is_present(void)
return analog_val < (9 * ADC_READ_MAX / 10) ? BP_YES : BP_NO;
}
#endif
+
+/**
+ * Discharge battery when on AC power for factory test.
+ */
+int board_discharge_on_ac(int enable)
+{
+ return charger_discharge_on_ac(enable);
+}
diff --git a/board/samus/board.h b/board/samus/board.h
index ed0aa4005a..c69ff0959b 100644
--- a/board/samus/board.h
+++ b/board/samus/board.h
@@ -30,12 +30,14 @@
#define CONFIG_POWER_BUTTON_X86
#define CONFIG_BACKLIGHT_REQ_GPIO GPIO_PCH_BL_EN
-#define CONFIG_BATTERY_LINK
+#define CONFIG_BATTERY_SAMUS
+#define CONFIG_CHARGER_PROFILE_OVERRIDE
#define CONFIG_BATTERY_PRESENT_CUSTOM
#define CONFIG_BATTERY_SMART
#define CONFIG_CHARGER
-#define CONFIG_CHARGER_V1
+#define CONFIG_CHARGER_V2
#define CONFIG_CHARGER_BQ24715
+#define CONFIG_CHARGER_DISCHARGE_ON_AC
/* 10mOhm sense resitors. */
#define CONFIG_CHARGER_SENSE_RESISTOR 10
#define CONFIG_CHARGER_SENSE_RESISTOR_AC 10
@@ -245,6 +247,9 @@ enum board_version {
#define WIRELESS_GPIO_WWAN GPIO_PP3300_LTE_EN
#define WIRELESS_GPIO_WLAN_POWER GPIO_PP3300_WLAN_EN
+/* Discharge battery when on AC power for factory test. */
+int board_discharge_on_ac(int enable);
+
#endif /* !__ASSEMBLER__ */
#endif /* __BOARD_H */
diff --git a/common/charge_state_v2.c b/common/charge_state_v2.c
index 1f0d4d6350..e28a31c3ad 100644
--- a/common/charge_state_v2.c
+++ b/common/charge_state_v2.c
@@ -15,6 +15,7 @@
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
+#include "math_util.h"
#include "printf.h"
#include "system.h"
#include "task.h"
@@ -25,42 +26,736 @@
#define CPUTS(outstr) cputs(CC_CHARGER, outstr)
#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args)
+/* Delay after AP battery shutdown warning before we kill the AP */
+#define LOW_BATTERY_SHUTDOWN_TIMEOUT_US (30 * SECOND)
+/* Time to spend trying to wake a non-responsive battery */
+#define PRECHARGE_TIMEOUT_US (30 * SECOND)
+/* Power state task polling periods in usec */
+#define POLL_PERIOD_VERY_LONG MINUTE
+#define POLL_PERIOD_LONG (MSEC * 500)
+#define POLL_PERIOD_CHARGE (MSEC * 250)
+#define POLL_PERIOD_SHORT (MSEC * 100)
+#define MIN_SLEEP_USEC (MSEC * 50)
+#define MAX_SLEEP_USEC MINUTE
-void charger_task(void)
+/*
+ * State for charger_task(). Here so we can reset it on a HOOK_INIT, and
+ * because stack space is more limited than .bss
+ */
+static const struct battery_info *batt_info;
+static struct charge_state_data curr;
+static int prev_ac, prev_volt, prev_curr, prev_charge;
+static int state_machine_force_idle;
+static unsigned int user_current_limit = -1U;
+static timestamp_t shutdown_warning_time, precharge_start_time;
+static int battery_seems_to_be_dead;
+static int problems_exist;
+
+/* Track problems in communicating with the battery or charger */
+enum problem_type {
+ PR_STATIC_UPDATE,
+ PR_SET_VOLTAGE,
+ PR_SET_CURRENT,
+ PR_POST_INIT,
+ PR_CHG_FLAGS,
+ PR_BATT_FLAGS,
+ PR_CUSTOM,
+
+ NUM_PROBLEM_TYPES
+};
+
+/*
+ * TODO(wfrichar): When do we decide a problem is real and not just
+ * intermittent? And what do we do about it?
+ */
+static void problem(enum problem_type p, int v)
+{
+ ccprintf("[%T %s: problem %d, value 0x%x]\n", __FILE__, p, v);
+ problems_exist = 1;
+}
+
+/* Returns zero if every item was updated. */
+static int update_static_battery_info(void)
+{
+ char *batt_str;
+ int batt_serial;
+ /*
+ * The return values have type enum ec_error_list, but EC_SUCCESS is
+ * zero. We'll just look for any failures so we can try them all again.
+ */
+ int rv;
+
+ /* 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);
+ rv = battery_serial_number(&batt_serial);
+ if (!rv)
+ snprintf(batt_str, EC_MEMMAP_TEXT_MAX, "%04X", batt_serial);
+
+ /* Design Capacity of Full */
+ rv |= battery_design_capacity(
+ (int *)host_get_memmap(EC_MEMMAP_BATT_DCAP));
+
+ /* Design Voltage */
+ rv |= battery_design_voltage(
+ (int *)host_get_memmap(EC_MEMMAP_BATT_DVLT));
+
+ /* Last Full Charge Capacity (this is only mostly static) */
+ rv |= battery_full_charge_capacity(
+ (int *)host_get_memmap(EC_MEMMAP_BATT_LFCC));
+
+ /* Cycle Count */
+ rv |= 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);
+ rv |= 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);
+ rv |= battery_device_name(batt_str, EC_MEMMAP_TEXT_MAX);
+
+ /* Battery Type string */
+ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_TYPE);
+ rv |= battery_device_chemistry(batt_str, EC_MEMMAP_TEXT_MAX);
+
+ /* Zero the dynamic entries. They'll come next. */
+ *(int *)host_get_memmap(EC_MEMMAP_BATT_VOLT) = 0;
+ *(int *)host_get_memmap(EC_MEMMAP_BATT_RATE) = 0;
+ *(int *)host_get_memmap(EC_MEMMAP_BATT_CAP) = 0;
+ *(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC) = 0;
+ *host_get_memmap(EC_MEMMAP_BATT_FLAG) = 0;
+
+ if (rv)
+ problem(PR_STATIC_UPDATE, 0);
+ else
+ /* No errors seen. Battery data is now present */
+ *host_get_memmap(EC_MEMMAP_BATTERY_VERSION) = 1;
+
+ return rv;
+}
+
+static void update_dynamic_battery_info(void)
+{
+ /* The memmap address is constant. We should fix these calls somehow. */
+ int *memmap_volt = (int *)host_get_memmap(EC_MEMMAP_BATT_VOLT);
+ int *memmap_rate = (int *)host_get_memmap(EC_MEMMAP_BATT_RATE);
+ int *memmap_cap = (int *)host_get_memmap(EC_MEMMAP_BATT_CAP);
+ int *memmap_lfcc = (int *)host_get_memmap(EC_MEMMAP_BATT_LFCC);
+ uint8_t *memmap_flags = host_get_memmap(EC_MEMMAP_BATT_FLAG);
+ uint8_t tmp;
+ int cap_changed;
+
+ tmp = 0;
+ if (curr.ac)
+ tmp |= EC_BATT_FLAG_AC_PRESENT;
+
+ if (curr.batt.is_present == BP_YES)
+ tmp |= EC_BATT_FLAG_BATT_PRESENT;
+
+ if (!(curr.batt.flags & BATT_FLAG_BAD_VOLTAGE))
+ *memmap_volt = curr.batt.voltage;
+
+ if (!(curr.batt.flags & BATT_FLAG_BAD_CURRENT))
+ *memmap_rate = ABS(curr.batt.current);
+
+ if (!(curr.batt.flags & BATT_FLAG_BAD_REMAINING_CAPACITY))
+ *memmap_cap = curr.batt.remaining_capacity;
+
+ cap_changed = 0;
+ if (!(curr.batt.flags & BATT_FLAG_BAD_FULL_CAPACITY) &&
+ curr.batt.full_capacity != *memmap_lfcc) {
+ *memmap_lfcc = curr.batt.full_capacity;
+ cap_changed = 1;
+ }
+
+ if (curr.batt.is_present == BP_YES &&
+ !(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
+ curr.batt.state_of_charge <= BATTERY_LEVEL_CRITICAL)
+ tmp |= EC_BATT_FLAG_LEVEL_CRITICAL;
+
+ switch (curr.state) {
+ case ST_DISCHARGE:
+ tmp |= EC_BATT_FLAG_DISCHARGING;
+ break;
+ case ST_CHARGE:
+ tmp |= EC_BATT_FLAG_CHARGING;
+ break;
+ default:
+ /* neither charging nor discharging */
+ break;
+ }
+
+ *memmap_flags = tmp;
+
+ /* Poke the AP if the full_capacity changes. */
+ if (cap_changed)
+ host_set_single_event(EC_HOST_EVENT_BATTERY);
+}
+
+static const char * const state_list[] = {
+ "idle", "discharge", "charge", "precharge"
+};
+BUILD_ASSERT(ARRAY_SIZE(state_list) == NUM_STATES_V2);
+static const char * const batt_pres[] = {
+ "NO", "YES", "NOT_SURE",
+};
+
+static void dump_charge_state(void)
+{
+#define DUMP(FLD, FMT) ccprintf(" " #FLD " = " FMT "\n", curr.FLD)
+ ccprintf(" state = %s\n", state_list[curr.state]);
+ DUMP(ac, "%d");
+ DUMP(chg.voltage, "%dmV");
+ DUMP(chg.current, "%dmA");
+ DUMP(chg.input_current, "%dmA");
+ DUMP(chg.status, "0x%x");
+ DUMP(chg.option, "0x%x");
+ DUMP(chg.flags, "0x%x");
+ ccprintf(" batt.temperature = %dC\n",
+ DECI_KELVIN_TO_CELSIUS(curr.batt.temperature));
+ DUMP(batt.state_of_charge, "%d%%");
+ DUMP(batt.voltage, "%dmV");
+ DUMP(batt.current, "%dmA");
+ DUMP(batt.desired_voltage, "%dmV");
+ DUMP(batt.desired_current, "%dmA");
+ DUMP(batt.flags, "0x%x");
+ DUMP(batt.remaining_capacity, "%dmAh");
+ DUMP(batt.full_capacity, "%dmAh");
+ ccprintf(" batt.is_present = %s\n", batt_pres[curr.batt.is_present]);
+ DUMP(requested_voltage, "%dmV");
+ DUMP(requested_current, "%dmA");
+ ccprintf(" force_idle = %d\n", state_machine_force_idle);
+ ccprintf(" user_current_limit = %dmA\n", user_current_limit);
+ ccprintf(" battery_seems_to_be_dead = %d\n", battery_seems_to_be_dead);
+#undef DUMP
+}
+
+static void show_charging_progress(void)
+{
+ int rv, minutes, to_full;
+
+ if (curr.state == ST_IDLE ||
+ curr.state == ST_DISCHARGE) {
+ rv = battery_time_to_empty(&minutes);
+ to_full = 0;
+ } else {
+ rv = battery_time_to_full(&minutes);
+ to_full = 1;
+ }
+
+ if (rv)
+ CPRINTF("[%T Battery %d%% / ??h:?? %s]\n",
+ curr.batt.state_of_charge,
+ to_full ? "to full" : "to empty");
+ else
+ CPRINTF("[%T Battery %d%% / %dh:%d %s]\n",
+ curr.batt.state_of_charge,
+ minutes / 60, minutes % 60,
+ to_full ? "to full" : "to empty");
+}
+
+/*
+ * Ask the charger for some voltage and current. If either value is 0,
+ * charging is disabled; otherwise it's enabled.
+ */
+static int charge_request(int voltage, int current)
+{
+ int r1, r2;
+
+ if (!voltage || !current)
+ /* TODO(wfrichar): should we call charger_set_mode() too? */
+ voltage = current = 0;
+
+ CPRINTF("[%T %s(%dmV, %dmA)]\n", __func__, voltage, current);
+
+ r1 = charger_set_voltage(voltage);
+ if (r1 != EC_SUCCESS)
+ problem(PR_SET_VOLTAGE, r1);
+
+ r2 = charger_set_current(current);
+ if (r2 != EC_SUCCESS)
+ problem(PR_SET_CURRENT, r2);
+
+ return r1 ? r1 : r2;
+}
+
+
+/* Force charging off before the battery is full. */
+static int charge_force_idle(int enable)
+{
+ /*
+ * Force idle is only meaningful if external power is
+ * present. If it's not present we can't charge anyway.
+ */
+ if (enable && !curr.ac)
+ return EC_ERROR_NOT_POWERED;
+
+ state_machine_force_idle = enable;
+ return EC_SUCCESS;
+}
+
+static void prevent_hot_discharge(void)
+{
+ int batt_temp_c;
+
+ /* If the AP is off already, the thermal task should handle it. */
+ if (!chipset_in_state(CHIPSET_STATE_ON))
+ return;
+
+ /* Same if we can't read the battery temp. */
+ if (curr.batt.flags & BATT_FLAG_BAD_TEMPERATURE)
+ return;
+
+ /*
+ * TODO(wfrichar): Shouldn't we do this in stages, like
+ * prevent_deep_discharge()? Send an event, give the AP time to react,
+ * maybe even hibernate the EC if things are really bad?
+ *
+ * TODO(wfrichar): The thermal loop should watch the battery temp
+ * anyway, so it can turn fans on. It could also force an AP shutdown
+ * if it's too hot, but AFAIK we don't have anything in place to do a
+ * battery shutdown if it's really really hot. We probably should, just
+ * in case.
+ *
+ * TODO(wfrichar): It'd also be nice if we had a persistent error log
+ * for that somewhere too. It would have to be in the EC's eeprom and
+ * not in the nvram if we're going to cut the battery.
+ */
+ batt_temp_c = DECI_KELVIN_TO_CELSIUS(curr.batt.temperature);
+ if (batt_temp_c > batt_info->discharging_max_c ||
+ batt_temp_c < batt_info->discharging_min_c) {
+ CPRINTF("[%T charge force shutdown due to battery temp %dC]\n",
+ batt_temp_c);
+ chipset_force_shutdown();
+ host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN);
+ }
+}
+
+/* True if we know the charge is too low, or we know the voltage is too low. */
+static inline int battery_too_low(void)
{
- while (1)
- task_wait_event(-1);
+ return ((!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
+ curr.batt.state_of_charge < BATTERY_LEVEL_SHUTDOWN) ||
+ (!(curr.batt.flags & BATT_FLAG_BAD_VOLTAGE) &&
+ curr.batt.voltage <= batt_info->voltage_min));
}
+/* Shut everything down before the battery completely dies. */
+static void prevent_deep_discharge(void)
+{
+ if (!battery_too_low())
+ return;
+
+ 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 (!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");
+ shutdown_warning_time = get_time();
+ host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN);
+ } else if (get_time().val > 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)
+/*
+ * Send host events as the battery charge drops below certain thresholds.
+ * We handle forced shutdown and other actions elsewhere; this is just for the
+ * host events. We send these even if the AP is off, since the AP will read and
+ * discard any events it doesn't care about the next time it wakes up.
+ */
+static void notify_host_of_low_battery(void)
{
- return 0;
+ /* We can't tell what the current charge is. Assume it's okay. */
+ if (curr.batt.flags && BATT_FLAG_BAD_STATE_OF_CHARGE)
+ return;
+
+ if (curr.batt.state_of_charge <= BATTERY_LEVEL_LOW &&
+ prev_charge > BATTERY_LEVEL_LOW)
+ host_set_single_event(EC_HOST_EVENT_BATTERY_LOW);
+
+ if (curr.batt.state_of_charge <= BATTERY_LEVEL_CRITICAL &&
+ prev_charge > BATTERY_LEVEL_CRITICAL)
+ host_set_single_event(EC_HOST_EVENT_BATTERY_CRITICAL);
+}
+
+/* Main loop */
+void charger_task(void)
+{
+ int sleep_usec;
+ int need_static = 1;
+
+ /* Get the battery-specific values */
+ batt_info = battery_get_info();
+
+ /* Initialize all the state */
+ memset(&curr, 0, sizeof(curr));
+ curr.batt.is_present = BP_NOT_SURE;
+ prev_ac = prev_volt = prev_curr = prev_charge = -1;
+ state_machine_force_idle = 0;
+ shutdown_warning_time.val = 0UL;
+ battery_seems_to_be_dead = 0;
+
+ while (1) {
+
+ /* Let's see what's going on... */
+ curr.ts = get_time();
+ sleep_usec = 0;
+ problems_exist = 0;
+ curr.ac = extpower_is_present();
+ if (curr.ac != prev_ac) {
+ if (curr.ac) {
+ /*
+ * Some chargers are unpowered when the AC is
+ * off, so we'll reinitialize it when AC
+ * comes back. Try again if it fails.
+ */
+ int rv = charger_post_init();
+ if (rv != EC_SUCCESS)
+ problem(PR_POST_INIT, rv);
+ else
+ prev_ac = curr.ac;
+ } else {
+ /* Some things are only meaningful on AC */
+ state_machine_force_idle = 0;
+ battery_seems_to_be_dead = 0;
+ prev_ac = curr.ac;
+ }
+ }
+ charger_get_params(&curr.chg);
+ battery_get_params(&curr.batt);
+
+ /*
+ * Now decide what we want to do about it. We'll normally just
+ * pass along whatever the battery wants to the charger. Note
+ * that if battery_get_params() can't get valid values from the
+ * battery it uses (0, 0), which is probably safer than blindly
+ * applying power to a battery we can't talk to.
+ */
+ curr.requested_voltage = curr.batt.desired_voltage;
+ curr.requested_current = curr.batt.desired_current;
+
+ /* If we *know* there's no battery, wait for one to appear. */
+ if (curr.batt.is_present == BP_NO) {
+ ASSERT(curr.ac); /* How are we running? */
+ curr.state = ST_IDLE;
+ goto wait_for_it;
+ }
+
+ /*
+ * If we had trouble talking to the battery or the charger, we
+ * should probably do nothing for a bit, and if it doesn't get
+ * better then flag it as an error.
+ */
+ if (curr.chg.flags & CHG_FLAG_BAD_ANY)
+ problem(PR_CHG_FLAGS, curr.chg.flags);
+ if (curr.batt.flags & BATT_FLAG_BAD_ANY)
+ problem(PR_BATT_FLAGS, curr.batt.flags);
+
+ if (!curr.ac) {
+ curr.state = ST_DISCHARGE;
+ /* Don't let the battery hurt itself. */
+ prevent_hot_discharge();
+ prevent_deep_discharge();
+ goto wait_for_it;
+ }
+
+ /* Okay, we're on AC and we should have a battery. */
+
+ /* Needed for factory tests.
+ *
+ * TODO(wfrichar): But see chrome-os-partner:26418. Can we do
+ * away with state_machine_force_idle if DPTF current=0 does
+ * the same thing?
+ */
+ if (state_machine_force_idle) {
+ curr.state = ST_IDLE;
+ goto wait_for_it;
+ }
+
+ /* If the battery is not responsive, try to wake it up. */
+ if (!(curr.batt.flags & BATT_FLAG_RESPONSIVE)) {
+ /* If we've already tried (or for too long) */
+ if (battery_seems_to_be_dead ||
+ (curr.state == ST_PRECHARGE &&
+ (get_time().val > precharge_start_time.val +
+ PRECHARGE_TIMEOUT_US))) {
+ /* Then do nothing */
+ battery_seems_to_be_dead = 1;
+ curr.state = ST_IDLE;
+ curr.requested_voltage = 0;
+ curr.requested_current = 0;
+ } else {
+ /* See if we can wake it up */
+ if (curr.state != ST_PRECHARGE)
+ precharge_start_time = get_time();
+ curr.state = ST_PRECHARGE;
+ curr.requested_voltage =
+ batt_info->voltage_max;
+ curr.requested_current =
+ batt_info->precharge_current;
+ }
+ goto wait_for_it;
+ } else {
+ /* The battery is responding. Yay. Try to use it. */
+ battery_seems_to_be_dead = 0;
+ curr.state = ST_CHARGE;
+ }
+
+ if (curr.batt.state_of_charge >= BATTERY_LEVEL_FULL) {
+ /* Full up. Stop charging */
+ curr.state = ST_IDLE;
+ goto wait_for_it;
+ }
+
+ /*
+ * TODO(wfrichar): Quit trying if charging too long without
+ * getting full. See CONFIG_CHARGER_TIMEOUT_HOURS,
+ * chrome-os-partner:20145
+ */
+
+#ifdef CONFIG_CHARGER_PROFILE_OVERRIDE
+ sleep_usec = charger_profile_override(&curr);
+ if (sleep_usec < 0)
+ problem(PR_CUSTOM, sleep_usec);
+#endif
+
+wait_for_it:
+ /* Keep the AP informed */
+ if (need_static)
+ need_static = update_static_battery_info();
+ /* Wait on the dynamic info until the static info is good. */
+ if (!need_static)
+ update_dynamic_battery_info();
+ notify_host_of_low_battery();
+
+ /* And the EC console */
+ if (!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
+ curr.batt.state_of_charge != prev_charge) {
+ show_charging_progress();
+ prev_charge = curr.batt.state_of_charge;
+ }
+
+ /* Turn charger off if it's not needed */
+ if (curr.state == ST_IDLE || curr.state == ST_DISCHARGE) {
+ curr.requested_voltage = 0;
+ curr.requested_current = 0;
+ }
+
+ /* Apply external limits */
+ if (curr.requested_current > user_current_limit)
+ curr.requested_current = user_current_limit;
+
+ /* Round to valid values */
+ curr.requested_voltage =
+ charger_closest_voltage(curr.requested_voltage);
+ curr.requested_current =
+ charger_closest_current(curr.requested_current);
+
+ /*
+ * Only update the charger when something changes so that
+ * temporary overrides are possible through console commands.
+ */
+ if ((prev_volt != curr.requested_voltage ||
+ prev_curr != curr.requested_current) &&
+ EC_SUCCESS == charge_request(curr.requested_voltage,
+ curr.requested_current)) {
+ /*
+ * Only update if the request worked, so we'll keep
+ * trying on failures.
+ */
+ prev_volt = curr.requested_voltage;
+ prev_curr = curr.requested_current;
+ }
+
+ /* How long to sleep? */
+ if (problems_exist)
+ /* If there are errors, don't wait very long. */
+ sleep_usec = POLL_PERIOD_SHORT;
+ else if (sleep_usec <= 0) {
+ /* default values depend on the state */
+ if (curr.state == ST_IDLE ||
+ curr.state == ST_DISCHARGE) {
+ /* If AP is off, we can sleep a long time */
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF |
+ CHIPSET_STATE_SUSPEND))
+ sleep_usec = POLL_PERIOD_VERY_LONG;
+ else
+ /* Discharging, not too urgent */
+ sleep_usec = POLL_PERIOD_LONG;
+ } else {
+ /* Charging, so pay closer attention */
+ sleep_usec = POLL_PERIOD_CHARGE;
+ }
+ }
+
+ /* Adjust for time spent in this loop */
+ sleep_usec -= (int)(get_time().val - curr.ts.val);
+ if (sleep_usec < MIN_SLEEP_USEC)
+ sleep_usec = MIN_SLEEP_USEC;
+ else if (sleep_usec > MAX_SLEEP_USEC)
+ sleep_usec = MAX_SLEEP_USEC;
+
+ task_wait_event(sleep_usec);
+ }
}
+/*****************************************************************************/
+/* Exported functions */
+
+int charge_want_shutdown(void)
+{
+ return (curr.state == ST_DISCHARGE) &&
+ !(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
+ (curr.batt.state_of_charge < BATTERY_LEVEL_SHUTDOWN);
+}
+
enum charge_state charge_get_state(void)
{
- return PWR_STATE_INIT;
+ switch (curr.state) {
+ case ST_IDLE:
+ if (battery_seems_to_be_dead)
+ return PWR_STATE_ERROR;
+ return PWR_STATE_IDLE;
+ case ST_DISCHARGE:
+ return PWR_STATE_DISCHARGE;
+ case ST_CHARGE:
+ /* The only difference here is what the LEDs display. */
+ if (curr.batt.state_of_charge >= BATTERY_LEVEL_NEAR_FULL)
+ return PWR_STATE_CHARGE_NEAR_FULL;
+ else
+ return PWR_STATE_CHARGE;
+ default:
+ /* Anything else can be considered an error for LED purposes */
+ return PWR_STATE_ERROR;
+ }
}
uint32_t charge_get_flags(void)
{
- return 0;
+ uint32_t flags = 0;
+
+ if (state_machine_force_idle)
+ flags |= CHARGE_FLAG_FORCE_IDLE;
+ if (curr.ac)
+ flags |= CHARGE_FLAG_EXTERNAL_POWER;
+
+ return flags;
}
int charge_get_percent(void)
{
- return 0;
+ /*
+ * Since there's no way to indicate an error to the caller, we'll just
+ * return the last known value. Even if we've never been able to talk
+ * to the battery, that'll be zero, which is probably as good as
+ * anything.
+ */
+ return curr.batt.state_of_charge;
}
int charge_temp_sensor_get_val(int idx, int *temp_ptr)
{
- return EC_ERROR_UNKNOWN;
+ if (curr.batt.flags & BATT_FLAG_BAD_TEMPERATURE)
+ return EC_ERROR_UNKNOWN;
+
+ /* Battery temp is 10ths of degrees K, temp wants degrees K */
+ *temp_ptr = curr.batt.temperature / 10;
+ return EC_SUCCESS;
+}
+
+/*****************************************************************************/
+/* Hooks */
+
+/* Wake up the task when something important happens */
+static void charge_wakeup(void)
+{
+ task_wake(TASK_ID_CHARGER);
}
+DECLARE_HOOK(HOOK_CHIPSET_RESUME, charge_wakeup, HOOK_PRIO_DEFAULT);
+DECLARE_HOOK(HOOK_AC_CHANGE, charge_wakeup, HOOK_PRIO_DEFAULT);
-int charge_want_shutdown(void)
+/*****************************************************************************/
+/* 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;
+}
+DECLARE_HOST_COMMAND(EC_CMD_CHARGE_CONTROL, charge_command_charge_control,
+ EC_VER_MASK(1));
+
+static void reset_current_limit(void)
+{
+ user_current_limit = -1U;
+}
+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)
{
- return 0;
+ 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_chgstate(int argc, char **argv)
+{
+ int rv;
+ int val;
+
+ if (argc > 1) {
+ if (!strcasecmp(argv[1], "idle")) {
+ if (argc <= 2)
+ return EC_ERROR_PARAM_COUNT;
+ if (!parse_bool(argv[2], &val))
+ return EC_ERROR_PARAM2;
+ rv = charge_force_idle(val);
+ if (rv)
+ return rv;
+ } else {
+ /* maybe handle board_discharge_on_ac() too? */
+ return EC_ERROR_PARAM1;
+ }
+ }
+
+ dump_charge_state();
+ return EC_SUCCESS;
}
+DECLARE_CONSOLE_COMMAND(chgstate, command_chgstate,
+ "[idle on|off]",
+ "Get/set charge state machine status",
+ NULL);
diff --git a/driver/battery/samus.c b/driver/battery/samus.c
new file mode 100644
index 0000000000..d99a2397a6
--- /dev/null
+++ b/driver/battery/samus.c
@@ -0,0 +1,129 @@
+/* Copyright (c) 2012 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 pack vendor provided charging profile
+ */
+
+#include "charge_state.h"
+#include "console.h"
+#include "util.h"
+
+static const struct battery_info info = {
+ /*
+ * Design voltage
+ * max = 8.4V
+ * normal = 7.4V
+ * min = 6.0V
+ */
+ .voltage_max = 8400,
+ .voltage_normal = 7400,
+ .voltage_min = 6000,
+
+ /* Pre-charge current: I <= 0.01C */
+ .precharge_current = 64, /* mA */
+
+ /*
+ * Operational temperature range
+ * 0 <= T_charge <= 50 deg C
+ * -20 <= T_discharge <= 60 deg C
+ */
+ .start_charging_min_c = 0,
+ .start_charging_max_c = 50,
+ .charging_min_c = 0,
+ .charging_max_c = 50,
+ .discharging_min_c = -20,
+ .discharging_max_c = 60,
+};
+
+const struct battery_info *battery_get_info(void)
+{
+ return &info;
+}
+
+#ifdef CONFIG_CHARGER_PROFILE_OVERRIDE
+
+/*
+ * For Samus, we'd like to set the CONFIG_CHARGER_INPUT_CURRENT to a higher
+ * value, but the AC adapters freak out if we do. So instead we set it to a
+ * low value, and it gets reset to that point every time AC is applied. Then we
+ * bump it up a little bit every time through the loop until it's where we
+ * wanted it in the first place.
+ */
+#define MAX_INPUT_CURRENT 3200
+#define INPUT_CURRENT_INCR 64
+
+static int fast_charging_allowed;
+
+/*
+ * This can override the smart battery's charging profile. To make a change,
+ * modify one or more of requested_voltage, requested_current, or state.
+ * Leave everything else unchanged.
+ *
+ * Return the next poll period in usec, or zero to use the default (which is
+ * state dependent).
+ */
+int charger_profile_override(struct charge_state_data *curr)
+{
+ int rv;
+
+ /* We only want to override how we charge, nothing else. */
+ if (curr->state != ST_CHARGE)
+ return 0;
+
+ /* Bump the input current up a little at a time if needed. */
+ if (curr->chg.input_current < MAX_INPUT_CURRENT) {
+ rv = charger_set_input_current(curr->chg.input_current +
+ INPUT_CURRENT_INCR);
+ /*
+ * If we can't set the input current, indicate the error
+ * (negative, since positive changes the poll period) and
+ * don't override the default behavior.
+ */
+ if (rv)
+ return -rv;
+ }
+
+ /* Do we want to mess with the charge profile too? */
+ if (!fast_charging_allowed)
+ return 0;
+
+ /* Okay, impose our custom will */
+ curr->requested_current = 9000;
+ curr->requested_voltage = 8300;
+ if (curr->batt.current <= 6300) {
+ curr->requested_current = 6300;
+ curr->requested_voltage = 8400;
+ } else if (curr->batt.current <= 4500) {
+ curr->requested_current = 4500;
+ curr->requested_voltage = 8500;
+ } else if (curr->batt.current <= 2700) {
+ curr->requested_current = 2700;
+ curr->requested_voltage = 8700;
+ } else if (curr->batt.current <= 475) {
+ /*
+ * Should we stop? If so, how do we start again?
+ * For now, just use the battery's profile.
+ */
+ curr->requested_current = curr->batt.desired_current;
+ curr->requested_voltage = curr->batt.desired_voltage;
+ }
+
+ return 0;
+}
+
+static int command_fastcharge(int argc, char **argv)
+{
+ if (argc > 1 && !parse_bool(argv[1], &fast_charging_allowed))
+ return EC_ERROR_PARAM1;
+
+ ccprintf("fastcharge %s\n", fast_charging_allowed ? "on" : "off");
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(fastcharge, command_fastcharge,
+ "[on|off]",
+ "Get or set fast charging profile",
+ NULL);
+
+#endif /* CONFIG_CHARGER_PROFILE_OVERRIDE */
diff --git a/driver/battery/smart.c b/driver/battery/smart.c
index 90b566009b..976b7b7af5 100644
--- a/driver/battery/smart.c
+++ b/driver/battery/smart.c
@@ -22,12 +22,12 @@ test_mockable int sbc_write(int cmd, int param)
return i2c_write16(I2C_PORT_CHARGER, CHARGER_ADDR, cmd, param);
}
-int sb_read(int cmd, int *param)
+test_mockable int sb_read(int cmd, int *param)
{
return i2c_read16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
}
-int sb_write(int cmd, int param)
+test_mockable int sb_write(int cmd, int param)
{
return i2c_write16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
}
diff --git a/driver/build.mk b/driver/build.mk
index ab637f03e2..b8e3c92929 100644
--- a/driver/build.mk
+++ b/driver/build.mk
@@ -16,6 +16,7 @@ driver-$(CONFIG_ALS_ISL29035)+=als_isl29035.o
driver-$(CONFIG_BATTERY_BQ20Z453)+=battery/bq20z453.o
driver-$(CONFIG_BATTERY_BQ27541)+=battery/bq27541.o
driver-$(CONFIG_BATTERY_LINK)+=battery/link.o
+driver-$(CONFIG_BATTERY_SAMUS)+=battery/samus.o
driver-$(CONFIG_BATTERY_SMART)+=battery/smart.o
# Battery charger ICs
diff --git a/include/charge_state_v2.h b/include/charge_state_v2.h
index 085f2a2047..16d8ee61ad 100644
--- a/include/charge_state_v2.h
+++ b/include/charge_state_v2.h
@@ -4,11 +4,49 @@
*/
#include "battery.h"
+#include "charger.h"
#include "timer.h"
#ifndef __CROS_EC_CHARGE_STATE_V2_H
#define __CROS_EC_CHARGE_STATE_V2_H
+/*
+ * The values exported by charge_get_state() and charge_get_flags() are used
+ * only to control the LEDs (with one not-quite-correct exception). For V2
+ * we use a different set of states internally.
+ */
+enum charge_state_v2 {
+ ST_IDLE = 0,
+ ST_DISCHARGE,
+ ST_CHARGE,
+ ST_PRECHARGE,
+
+ NUM_STATES_V2
+};
+
+struct charge_state_data {
+ timestamp_t ts;
+ int ac;
+ struct charger_params chg;
+ struct batt_params batt;
+ enum charge_state_v2 state;
+ int requested_voltage;
+ int requested_current;
+};
+
+/*
+ * Optional customization.
+ *
+ * On input, the struct reflects the default behavior. The function can make
+ * changes to the state, requested_voltage, or requested_current.
+ *
+ * Return value:
+ * >0 Desired time in usec for this poll period.
+ * 0 Use the default poll period (which varies with the state).
+ * <0 An error occurred. The poll time will be shorter than usual. Too
+ * many errors in a row may trigger some corrective action.
+ */
+int charger_profile_override(struct charge_state_data *);
#endif /* __CROS_EC_CHARGE_STATE_V2_H */
diff --git a/include/config.h b/include/config.h
index 9b8d5e362c..e89be9ca53 100644
--- a/include/config.h
+++ b/include/config.h
@@ -123,6 +123,8 @@
/*
* Charger should call battery_vendor_params() to limit/correct the voltage and
* current requested by the battery pack before acting on the request.
+ *
+ * This is valid with CONFIG_CHARGER_V1 only.
*/
#undef CONFIG_BATTERY_VENDOR_PARAMS
@@ -195,6 +197,9 @@
*/
#undef CONFIG_CHARGER_INPUT_CURRENT
+/* Equivalent of CONFIG_BATTERY_VENDOR_PARAMS for use with CONFIG_CHARGER_V2 */
+#undef CONFIG_CHARGER_PROFILE_OVERRIDE
+
/* Value of the charge sense resistor, in mOhms */
#undef CONFIG_CHARGER_SENSE_RESISTOR
diff --git a/test/battery_get_params_smart.c b/test/battery_get_params_smart.c
new file mode 100644
index 0000000000..7c86252b23
--- /dev/null
+++ b/test/battery_get_params_smart.c
@@ -0,0 +1,87 @@
+/* Copyright (c) 2014 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.
+ *
+ * Test the logic of battery_get_params() to be sure it sets the correct flags
+ * when i2c reads fail.
+ */
+
+#include "battery.h"
+#include "battery_smart.h"
+#include "common.h"
+#include "console.h"
+#include "i2c.h"
+#include "test_util.h"
+#include "util.h"
+
+/* Test state */
+static int fail_on_first, fail_on_last;
+static int read_count, write_count;
+struct batt_params batt;
+
+static void reset_and_fail_on(int first, int last)
+{
+ /* We're not initializing the fake battery, so everything reads zero */
+ memset(&batt, 0, sizeof(typeof(batt)));
+ read_count = write_count = 0;
+ fail_on_first = first;
+ fail_on_last = last;
+}
+
+/* Mocked functions */
+int sb_read(int cmd, int *param)
+{
+ read_count++;
+ if (read_count >= fail_on_first && read_count <= fail_on_last)
+ return EC_ERROR_UNKNOWN;
+
+ return i2c_read16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
+}
+int sb_write(int cmd, int param)
+{
+ write_count++;
+ return i2c_write16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
+}
+
+
+/* Tests */
+static int test_param_failures(void)
+{
+ int i, num_reads;
+
+ /* No failures */
+ reset_and_fail_on(0, 0);
+ battery_get_params(&batt);
+ TEST_ASSERT(batt.flags & BATT_FLAG_RESPONSIVE);
+ TEST_ASSERT(!(batt.flags & BATT_FLAG_BAD_ANY));
+ num_reads = read_count;
+
+ /* Just a single failure */
+ for (i = 1; i <= num_reads; i++) {
+ reset_and_fail_on(i, i);
+ battery_get_params(&batt);
+ TEST_ASSERT(batt.flags & BATT_FLAG_BAD_ANY);
+ TEST_ASSERT(batt.flags & BATT_FLAG_RESPONSIVE);
+ }
+
+ /* Once it fails, it keeps failing */
+ for (i = 1; i <= num_reads; i++) {
+ reset_and_fail_on(i, num_reads);
+ battery_get_params(&batt);
+ TEST_ASSERT(batt.flags & BATT_FLAG_BAD_ANY);
+ if (i == 1)
+ /* If every read fails, it's not responsive */
+ TEST_ASSERT(!(batt.flags & BATT_FLAG_RESPONSIVE));
+ else
+ TEST_ASSERT(batt.flags & BATT_FLAG_RESPONSIVE);
+ }
+
+ return EC_SUCCESS;
+}
+
+void run_test(void)
+{
+ RUN_TEST(test_param_failures);
+
+ test_print_result();
+}
diff --git a/test/battery_get_params_smart.tasklist b/test/battery_get_params_smart.tasklist
new file mode 100644
index 0000000000..c5d4a2a4b3
--- /dev/null
+++ b/test/battery_get_params_smart.tasklist
@@ -0,0 +1,17 @@
+/* Copyright (c) 2014 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.
+ */
+
+/**
+ * List of enabled tasks in the priority order
+ *
+ * The first one has the lowest priority.
+ *
+ * For each task, use the macro TASK_TEST(n, r, d, s) where :
+ * 'n' in the name of the task
+ * 'r' in the main routine of the task
+ * 'd' in an opaque parameter passed to the routine at startup
+ * 's' is the stack size in bytes; must be a multiple of 8
+ */
+#define CONFIG_TEST_TASK_LIST /* No test task */
diff --git a/test/build.mk b/test/build.mk
index f93b5897aa..d21469ac98 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -23,7 +23,7 @@ test-list-host=mutex pingpong utils kb_scan kb_mkbp lid_sw power_button hooks
test-list-host+=thermal flash queue kb_8042 extpwr_gpio console_edit system
test-list-host+=sbs_charging adapter host_command thermal_falco led_spring
test-list-host+=bklight_lid bklight_passthru interrupt timer_dos button
-test-list-host+=motion_sense math_util
+test-list-host+=motion_sense math_util sbs_charging_v2 battery_get_params_smart
adapter-y=adapter.o
button-y=button.o
@@ -48,6 +48,7 @@ power_button-y=power_button.o
powerdemo-y=powerdemo.o
queue-y=queue.o
sbs_charging-y=sbs_charging.o
+sbs_charging_v2-y=sbs_charging_v2.o
stress-y=stress.o
system-y=system.o
thermal-y=thermal.o
@@ -55,3 +56,4 @@ thermal_falco-y=thermal_falco.o
timer_calib-y=timer_calib.o
timer_dos-y=timer_dos.o
utils-y=utils.o
+battery_get_params_smart-y=battery_get_params_smart.o
diff --git a/test/sbs_charging_v2.c b/test/sbs_charging_v2.c
new file mode 100644
index 0000000000..feedcb117b
--- /dev/null
+++ b/test/sbs_charging_v2.c
@@ -0,0 +1,221 @@
+/* Copyright (c) 2014 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.
+ *
+ * Test lid switch.
+ */
+
+#include "battery_smart.h"
+#include "charge_state.h"
+#include "chipset.h"
+#include "common.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "task.h"
+#include "test_util.h"
+#include "util.h"
+
+#define WAIT_CHARGER_TASK 500
+#define BATTERY_DETACH_DELAY 35000
+
+static int mock_chipset_state = CHIPSET_STATE_ON;
+static int is_shutdown;
+static int is_force_discharge;
+static int is_hibernated;
+
+void chipset_force_shutdown(void)
+{
+ is_shutdown = 1;
+}
+
+int chipset_in_state(int state_mask)
+{
+ return state_mask & mock_chipset_state;
+}
+
+int board_discharge_on_ac(int enabled)
+{
+ is_force_discharge = enabled;
+ return EC_SUCCESS;
+}
+
+void system_hibernate(int sec, int usec)
+{
+ is_hibernated = 1;
+}
+
+/* Setup init condition */
+static void test_setup(void)
+{
+ const struct battery_info *bat_info = battery_get_info();
+
+ /* 50% of charge */
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 50);
+ sb_write(SB_ABSOLUTE_STATE_OF_CHARGE, 50);
+ /* 25 degree Celsius */
+ sb_write(SB_TEMPERATURE, 250 + 2731);
+ /* Normal voltage */
+ sb_write(SB_VOLTAGE, bat_info->voltage_normal);
+ sb_write(SB_CHARGING_VOLTAGE, bat_info->voltage_max);
+ sb_write(SB_CHARGING_CURRENT, 4000);
+ /* Discharging at 100mAh */
+ sb_write(SB_CURRENT, -100);
+ /* Unplug AC */
+ gpio_set_level(GPIO_AC_PRESENT, 0);
+}
+
+static int wait_charging_state(void)
+{
+ enum charge_state state;
+ task_wake(TASK_ID_CHARGER);
+ msleep(WAIT_CHARGER_TASK);
+ state = charge_get_state();
+ ccprintf("[CHARGING TEST] state = %d\n", state);
+ return state;
+}
+
+static int charge_control(enum ec_charge_control_mode mode)
+{
+ struct ec_params_charge_control params;
+ params.mode = mode;
+ return test_send_host_command(EC_CMD_CHARGE_CONTROL, 1, &params,
+ sizeof(params), NULL, 0);
+}
+
+static int test_charge_state(void)
+{
+ enum charge_state state;
+
+ state = wait_charging_state();
+ /* Plug AC, charging at 1000mAh */
+ ccprintf("[CHARGING TEST] AC on\n");
+ gpio_set_level(GPIO_AC_PRESENT, 1);
+ sb_write(SB_CURRENT, 1000);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_CHARGE);
+
+ /* Detach battery, charging error */
+ ccprintf("[CHARGING TEST] Detach battery\n");
+ TEST_ASSERT(test_detach_i2c(I2C_PORT_BATTERY, BATTERY_ADDR) ==
+ EC_SUCCESS);
+ msleep(BATTERY_DETACH_DELAY);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_ERROR);
+
+ /* Attach battery again, charging */
+ ccprintf("[CHARGING TEST] Attach battery\n");
+ test_attach_i2c(I2C_PORT_BATTERY, BATTERY_ADDR);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_CHARGE);
+
+ /* Unplug AC, discharging at 1000mAh */
+ ccprintf("[CHARGING TEST] AC off\n");
+ gpio_set_level(GPIO_AC_PRESENT, 0);
+ sb_write(SB_CURRENT, -1000);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_DISCHARGE);
+
+ /* Discharging overtemp */
+ ccprintf("[CHARGING TEST] AC off, batt temp = 90 C\n");
+ gpio_set_level(GPIO_AC_PRESENT, 0);
+ sb_write(SB_CURRENT, -1000);
+
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_DISCHARGE);
+ sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(90));
+ state = wait_charging_state();
+ TEST_ASSERT(is_shutdown);
+ TEST_ASSERT(state == PWR_STATE_DISCHARGE);
+ sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(40));
+
+ /* Force idle */
+ ccprintf("[CHARGING TEST] AC on, force idle\n");
+ gpio_set_level(GPIO_AC_PRESENT, 1);
+ sb_write(SB_CURRENT, 1000);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_CHARGE);
+ charge_control(CHARGE_CONTROL_IDLE);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_IDLE);
+ charge_control(CHARGE_CONTROL_NORMAL);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_CHARGE);
+
+ /* Force discharge */
+ ccprintf("[CHARGING TEST] AC on, force discharge\n");
+ gpio_set_level(GPIO_AC_PRESENT, 1);
+ sb_write(SB_CURRENT, 1000);
+ charge_control(CHARGE_CONTROL_DISCHARGE);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_IDLE);
+ TEST_ASSERT(is_force_discharge);
+ charge_control(CHARGE_CONTROL_NORMAL);
+ state = wait_charging_state();
+ TEST_ASSERT(state == PWR_STATE_CHARGE);
+ TEST_ASSERT(!is_force_discharge);
+
+ return EC_SUCCESS;
+}
+
+static int test_low_battery(void)
+{
+ ccprintf("[CHARGING TEST] Low battery with AC\n");
+ gpio_set_level(GPIO_AC_PRESENT, 1);
+ is_hibernated = 0;
+ sb_write(SB_CURRENT, 1000);
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2);
+ wait_charging_state();
+ mock_chipset_state = CHIPSET_STATE_SOFT_OFF;
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+ TEST_ASSERT(!is_hibernated);
+
+ ccprintf("[CHARGING TEST] Low battery shutdown S0->S5\n");
+ mock_chipset_state = CHIPSET_STATE_ON;
+ hook_notify(HOOK_CHIPSET_PRE_INIT);
+ hook_notify(HOOK_CHIPSET_STARTUP);
+ gpio_set_level(GPIO_AC_PRESENT, 0);
+ is_hibernated = 0;
+ sb_write(SB_CURRENT, -1000);
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2);
+ wait_charging_state();
+ mock_chipset_state = CHIPSET_STATE_SOFT_OFF;
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+ wait_charging_state();
+ TEST_ASSERT(is_hibernated);
+
+ ccprintf("[CHARGING TEST] Low battery shutdown S5\n");
+ is_hibernated = 0;
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 10);
+ wait_charging_state();
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2);
+ wait_charging_state();
+ TEST_ASSERT(is_hibernated);
+
+ ccprintf("[CHARGING TEST] Low battery AP shutdown\n");
+ is_shutdown = 0;
+ mock_chipset_state = CHIPSET_STATE_ON;
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 10);
+ gpio_set_level(GPIO_AC_PRESENT, 1);
+ sb_write(SB_CURRENT, 1000);
+ wait_charging_state();
+ gpio_set_level(GPIO_AC_PRESENT, 0);
+ sb_write(SB_CURRENT, -1000);
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2);
+ wait_charging_state();
+ usleep(32 * SECOND);
+ wait_charging_state();
+ TEST_ASSERT(is_shutdown);
+
+ return EC_SUCCESS;
+}
+
+void run_test(void)
+{
+ test_setup();
+
+ RUN_TEST(test_charge_state);
+ RUN_TEST(test_low_battery);
+
+ test_print_result();
+}
diff --git a/test/sbs_charging_v2.tasklist b/test/sbs_charging_v2.tasklist
new file mode 100644
index 0000000000..a87856a123
--- /dev/null
+++ b/test/sbs_charging_v2.tasklist
@@ -0,0 +1,19 @@
+/* Copyright (c) 2014 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.
+ */
+
+/**
+ * List of enabled tasks in the priority order
+ *
+ * The first one has the lowest priority.
+ *
+ * For each task, use the macro TASK_TEST(n, r, d, s) where :
+ * 'n' in the name of the task
+ * 'r' in the main routine of the task
+ * 'd' in an opaque parameter passed to the routine at startup
+ * 's' is the stack size in bytes; must be a multiple of 8
+ */
+#define CONFIG_TEST_TASK_LIST \
+ TASK_TEST(CHARGER, charger_task, NULL, TASK_STACK_SIZE) \
+ TASK_TEST(CHIPSET, chipset_task, NULL, TASK_STACK_SIZE)
diff --git a/test/test_config.h b/test/test_config.h
index 9a005645f8..1ac42ae9d9 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -63,6 +63,19 @@ int board_discharge_on_ac(int enabled);
#define I2C_PORT_CHARGER 1
#endif
+#ifdef TEST_SBS_CHARGING_V2
+#define CONFIG_BATTERY_MOCK
+#define CONFIG_BATTERY_SMART
+#define CONFIG_CHARGER
+#define CONFIG_CHARGER_V2
+#define CONFIG_CHARGER_INPUT_CURRENT 4032
+#define CONFIG_CHARGER_DISCHARGE_ON_AC
+int board_discharge_on_ac(int enabled);
+#define I2C_PORT_MASTER 1
+#define I2C_PORT_BATTERY 1
+#define I2C_PORT_CHARGER 1
+#endif
+
#ifdef TEST_THERMAL
#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_FANS 1
@@ -89,5 +102,14 @@ int board_discharge_on_ac(int enabled);
#define CONFIG_KEYBOARD_PROTOCOL_8042
#endif
+#ifdef TEST_BATTERY_GET_PARAMS_SMART
+#define CONFIG_BATTERY_MOCK
+#define CONFIG_BATTERY_SMART
+#define CONFIG_CHARGER_INPUT_CURRENT 4032
+#define I2C_PORT_MASTER 1
+#define I2C_PORT_BATTERY 1
+#define I2C_PORT_CHARGER 1
+#endif
+
#endif /* TEST_BUILD */
#endif /* __CROS_EC_TEST_CONFIG_H */