summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 */