summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/build.mk1
-rw-r--r--common/charge_manager.c35
-rw-r--r--common/charge_ramp.c323
-rw-r--r--include/charge_ramp.h82
-rw-r--r--include/config.h9
-rw-r--r--test/build.mk2
-rw-r--r--test/charge_ramp.c502
-rw-r--r--test/charge_ramp.tasklist18
-rw-r--r--test/test_config.h4
9 files changed, 972 insertions, 4 deletions
diff --git a/common/build.mk b/common/build.mk
index 2f15725682..a6cb7bad8a 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -24,6 +24,7 @@ common-$(CONFIG_BUTTON_COUNT)+=button.o
common-$(CONFIG_CAPSENSE)+=capsense.o
common-$(CONFIG_CASE_CLOSED_DEBUG)+=case_closed_debug.o
common-$(CONFIG_CHARGE_MANAGER)+=charge_manager.o
+common-$(CONFIG_CHARGE_RAMP)+=charge_ramp.o
common-$(CONFIG_CHARGER)+=charger.o
common-$(CONFIG_CHARGER_V1)+=charge_state_v1.o
common-$(CONFIG_CHARGER_V2)+=charge_state_v2.o
diff --git a/common/charge_manager.c b/common/charge_manager.c
index bf93724cbd..da6b6c985c 100644
--- a/common/charge_manager.c
+++ b/common/charge_manager.c
@@ -5,6 +5,7 @@
#include "adc.h"
#include "charge_manager.h"
+#include "charge_ramp.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
@@ -26,6 +27,9 @@
static struct charge_port_info available_charge[CHARGE_SUPPLIER_COUNT]
[PD_PORT_COUNT];
+/* Keep track of when the supplier on each port is registered. */
+static timestamp_t registration_time[PD_PORT_COUNT];
+
/*
* Charge ceiling for ports. This can be set to temporarily limit the charge
* pulled from a port, without influencing the port selection logic.
@@ -152,6 +156,13 @@ static void charge_manager_fill_power_info(int port,
r->meas.current_max = 0;
r->max_power = 0;
} else {
+#ifdef HAS_TASK_CHG_RAMP
+ /* Read ramped current if active charging port */
+ int use_ramp_current = (charge_port == port);
+#else
+ const int use_ramp_current = 0;
+#endif
+
switch (sup) {
case CHARGE_SUPPLIER_PD:
r->type = USB_CHG_TYPE_PD;
@@ -175,8 +186,16 @@ static void charge_manager_fill_power_info(int port,
r->type = USB_CHG_TYPE_OTHER;
}
r->meas.voltage_max = available_charge[sup][port].voltage;
- r->meas.current_max = available_charge[sup][port].current;
- r->max_power = POWER(available_charge[sup][port]);
+
+ if (use_ramp_current) {
+ r->meas.current_max = chg_ramp_get_current_limit();
+ r->max_power =
+ r->meas.current_max * r->meas.voltage_max;
+ } else {
+ r->meas.current_max =
+ available_charge[sup][port].current;
+ r->max_power = POWER(available_charge[sup][port]);
+ }
/*
* If we are sourcing power, or sinking but not charging, then
@@ -385,9 +404,16 @@ static void charge_manager_refresh(void)
available_charge[new_supplier][new_port].voltage;
}
- /* Change the charge limit + charge port if modified. */
- if (new_port != charge_port || new_charge_current != charge_current) {
+ /* Change the charge limit + charge port/supplier if modified. */
+ if (new_port != charge_port || new_charge_current != charge_current ||
+ new_supplier != charge_supplier) {
+#ifdef HAS_TASK_CHG_RAMP
+ chg_ramp_charge_supplier_change(
+ new_port, new_supplier, new_charge_current,
+ registration_time[new_port]);
+#else
board_set_charge_limit(new_charge_current);
+#endif
CPRINTS("CL: p%d s%d i%d v%d", new_port, new_supplier,
new_charge_current, new_charge_voltage);
}
@@ -508,6 +534,7 @@ static void charge_manager_make_change(enum charge_manager_change_type change,
if (change == CHANGE_CHARGE) {
available_charge[supplier][port].current = charge->current;
available_charge[supplier][port].voltage = charge->voltage;
+ registration_time[port] = get_time();
/*
* If we have a charge on our delayed override port within
diff --git a/common/charge_ramp.c b/common/charge_ramp.c
new file mode 100644
index 0000000000..37bc45942d
--- /dev/null
+++ b/common/charge_ramp.c
@@ -0,0 +1,323 @@
+/* Copyright 2015 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.
+ */
+
+/* Charge input current limit ramp module for Chrome EC */
+
+#include "charge_manager.h"
+#include "charge_ramp.h"
+#include "common.h"
+#include "console.h"
+#include "task.h"
+#include "timer.h"
+#include "usb_pd_config.h"
+#include "util.h"
+
+#define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args)
+
+/* Number of times to ramp current searching for limit before stable charging */
+#define RAMP_COUNT 3
+
+/*
+ * Time to delay for detecting the charger type (must be long enough for BC1.2
+ * driver to get supplier information and notify charge manager).
+ */
+#define CHARGE_DETECT_DELAY (2*SECOND)
+
+/* Maximum allowable time charger can be unplugged to be considered an OCP */
+#define OC_RECOVER_MAX_TIME (SECOND)
+
+/* Delay for running state machine when board is not consuming full current */
+#define CURRENT_DRAW_DELAY (5*SECOND)
+
+/* Current ramp increment */
+#define RAMP_CURR_INCR_MA 64
+#define RAMP_CURR_DELAY (500*MSEC)
+
+/* How much to backoff the input current limit when limit has been found */
+#define RAMP_ICL_BACKOFF (2*RAMP_CURR_INCR_MA)
+
+/* Interval at which VBUS voltage is monitored in stable state */
+#define STABLE_VBUS_MONITOR_INTERVAL (SECOND)
+
+/* Time to delay for stablizing the charging current */
+#define STABLIZE_DELAY (5*SECOND)
+
+enum chg_ramp_state {
+ CHG_RAMP_DISCONNECTED,
+ CHG_RAMP_CHARGE_DETECT,
+ CHG_RAMP_OVERCURRENT_DETECT,
+ CHG_RAMP_RAMP,
+ CHG_RAMP_STABILIZE,
+ CHG_RAMP_STABLE,
+};
+static enum chg_ramp_state ramp_st;
+
+struct oc_info {
+ timestamp_t ts;
+ uint64_t recover;
+ int sup;
+ int icl;
+};
+
+/* OCP info for each over-current */
+static struct oc_info oc_info[PD_PORT_COUNT][RAMP_COUNT];
+static int oc_info_idx[PD_PORT_COUNT];
+#define ACTIVE_OC_INFO (oc_info[active_port][oc_info_idx[active_port]])
+
+/* Active charging information */
+static int active_port = CHARGE_PORT_NONE;
+static int active_sup;
+static int active_icl;
+static timestamp_t reg_time;
+
+static int stablize_port;
+static int stablize_sup;
+
+/* Maximum/minimum input current limit for active charger */
+static int max_icl;
+static int min_icl;
+
+
+void chg_ramp_charge_supplier_change(int port, int supplier, int current,
+ timestamp_t registration_time)
+{
+ /*
+ * If the last active port was a valid port and the port
+ * has changed, then this may have been an over-current.
+ */
+ if (active_port != CHARGE_PORT_NONE &&
+ port != active_port) {
+ if (oc_info_idx[active_port] == RAMP_COUNT - 1)
+ oc_info_idx[active_port] = 0;
+ else
+ oc_info_idx[active_port]++;
+ ACTIVE_OC_INFO.ts = get_time();
+ ACTIVE_OC_INFO.sup = active_sup;
+ ACTIVE_OC_INFO.icl = active_icl;
+ }
+
+ /* Set new active port, set ramp state, and wake ramp task */
+ active_port = port;
+ active_sup = supplier;
+ min_icl = current;
+ max_icl = board_get_ramp_current_limit(active_sup);
+ reg_time = registration_time;
+ if (ramp_st != CHG_RAMP_STABILIZE) {
+ ramp_st = (active_port == CHARGE_PORT_NONE) ?
+ CHG_RAMP_DISCONNECTED : CHG_RAMP_CHARGE_DETECT;
+ CPRINTS("Ramp reset: st%d\n", ramp_st);
+ task_wake(TASK_ID_CHG_RAMP);
+ }
+}
+
+int chg_ramp_get_current_limit(void)
+{
+ /*
+ * If we are ramping or stable, then use the active input
+ * current limit. Otherwise, use the minimum input current
+ * limit.
+ */
+ switch (ramp_st) {
+ case CHG_RAMP_RAMP:
+ case CHG_RAMP_STABILIZE:
+ case CHG_RAMP_STABLE:
+ return active_icl;
+ default:
+ return min_icl;
+ }
+}
+
+void chg_ramp_task(void)
+{
+ int task_wait_time = -1;
+ int i;
+ /*
+ * Static initializer so that we don't clobber early calls to this
+ * module.
+ */
+ static enum chg_ramp_state ramp_st_prev = CHG_RAMP_DISCONNECTED,
+ ramp_st_new = CHG_RAMP_DISCONNECTED;
+ int active_icl_new;
+
+ /* Clear last OCP supplier to guarantee we ramp on first connect */
+ for (i = 0; i < PD_PORT_COUNT; i++)
+ oc_info[i][0].sup = CHARGE_SUPPLIER_NONE;
+
+ while (1) {
+ ramp_st_new = ramp_st;
+ active_icl_new = active_icl;
+ switch (ramp_st) {
+ case CHG_RAMP_DISCONNECTED:
+ /* Do nothing */
+ task_wait_time = -1;
+ break;
+ case CHG_RAMP_CHARGE_DETECT:
+ /* Delay for charge_manager to determine supplier */
+ /* On entry to state, store the OC recovery time */
+ if (ramp_st_prev != ramp_st)
+ ACTIVE_OC_INFO.recover =
+ reg_time.val - ACTIVE_OC_INFO.ts.val;
+
+ /*
+ * If we are not drawing full charge, then don't ramp,
+ * just wait in this state, until we are.
+ */
+ if (!board_is_consuming_full_charge()) {
+ task_wait_time = CURRENT_DRAW_DELAY;
+ break;
+ }
+
+ ramp_st_new = CHG_RAMP_OVERCURRENT_DETECT;
+ task_wait_time = CHARGE_DETECT_DELAY;
+ break;
+ case CHG_RAMP_OVERCURRENT_DETECT:
+ /* Check if we should ramp or go straight to stable */
+ task_wait_time = SECOND;
+
+ /* Skip ramp for specific suppliers */
+ if (!board_is_ramp_allowed(active_sup)) {
+ active_icl_new = min_icl;
+ ramp_st_new = CHG_RAMP_STABLE;
+ break;
+ }
+
+ /*
+ * Compare recent OCP events, if all info matches,
+ * then we don't need to ramp anymore.
+ */
+ for (i = 0; i < RAMP_COUNT; i++) {
+ if (oc_info[active_port][i].sup != active_sup ||
+ oc_info[active_port][i].recover >
+ OC_RECOVER_MAX_TIME)
+ break;
+ }
+
+ if (i == RAMP_COUNT) {
+ /* Found OC threshold! */
+ active_icl_new = ACTIVE_OC_INFO.icl -
+ RAMP_ICL_BACKOFF;
+ ramp_st_new = CHG_RAMP_STABLE;
+ } else {
+ /*
+ * Need to ramp to find OC threshold, start
+ * at the minimum input current limit.
+ */
+ active_icl_new = min_icl;
+ ramp_st_new = CHG_RAMP_RAMP;
+ }
+ break;
+ case CHG_RAMP_RAMP:
+ /* Keep ramping until we find the limit */
+ task_wait_time = RAMP_CURR_DELAY;
+
+ /* Pause ramping if we are not drawing full current */
+ if (!board_is_consuming_full_charge()) {
+ task_wait_time = CURRENT_DRAW_DELAY;
+ break;
+ }
+
+ /* If VBUS is sagging a lot, then stop ramping */
+ if (board_is_vbus_too_low(CHG_RAMP_VBUS_RAMPING)) {
+ CPRINTS("VBUS low");
+ active_icl_new = MAX(min_icl, active_icl -
+ RAMP_ICL_BACKOFF);
+ ramp_st_new = CHG_RAMP_STABILIZE;
+ task_wait_time = STABLIZE_DELAY;
+ stablize_port = active_port;
+ stablize_sup = active_sup;
+ break;
+ }
+
+ /* Ramp the current limit if we haven't reached max */
+ if (active_icl == max_icl)
+ ramp_st_new = CHG_RAMP_STABLE;
+ else if (active_icl + RAMP_CURR_INCR_MA > max_icl)
+ active_icl_new = max_icl;
+ else
+ active_icl_new = active_icl + RAMP_CURR_INCR_MA;
+ break;
+ case CHG_RAMP_STABILIZE:
+ /* Wait for current to stabilize after ramp is done */
+ /* Use default delay for exiting this state */
+ task_wait_time = SECOND;
+ if (active_port == stablize_port &&
+ active_sup == stablize_sup) {
+ ramp_st_new = CHG_RAMP_STABLE;
+ break;
+ }
+
+ ramp_st_new = active_port == CHARGE_PORT_NONE ?
+ CHG_RAMP_DISCONNECTED :
+ CHG_RAMP_CHARGE_DETECT;
+ break;
+ case CHG_RAMP_STABLE:
+ /* Maintain input current limit */
+ /* On entry log charging stats */
+#ifdef CONFIG_USB_PD_LOGGING
+ if (ramp_st_prev != ramp_st)
+ charge_manager_save_log(active_port);
+#endif
+
+ /* Keep an eye on VBUS and restart ramping if it dips */
+ if (board_is_ramp_allowed(active_sup) &&
+ board_is_vbus_too_low(CHG_RAMP_VBUS_STABLE)) {
+ CPRINTS("VBUS low; Re-ramp");
+ active_icl_new = min_icl;
+ ramp_st_new = CHG_RAMP_RAMP;
+ }
+ task_wait_time = STABLE_VBUS_MONITOR_INTERVAL;
+ break;
+ }
+ if (ramp_st != ramp_st_new || active_icl != active_icl_new)
+ CPRINTS("Ramp p%d st%d %dmA %dmA",
+ active_port, ramp_st_new, min_icl,
+ active_icl_new);
+
+ ramp_st_prev = ramp_st;
+ ramp_st = ramp_st_new;
+ active_icl = active_icl_new;
+
+ /* Set the input current limit */
+ board_set_charge_limit(chg_ramp_get_current_limit());
+
+ if (ramp_st == CHG_RAMP_STABILIZE)
+ /*
+ * When in stabilize state, supplier/port may change
+ * and we don't want to wake up task until we have
+ * slept this amount of time.
+ */
+ usleep(task_wait_time);
+ else
+ task_wait_event(task_wait_time);
+ }
+}
+
+#ifdef CONFIG_CMD_CHGRAMP
+static int command_chgramp(int argc, char **argv)
+{
+ int i;
+ int port;
+
+ ccprintf("Chg Ramp:\nState: %d\nMin ICL: %d\nActive ICL: %d\n",
+ ramp_st, min_icl, active_icl);
+
+ for (port = 0; port < PD_PORT_COUNT; port++) {
+ ccprintf("Port %d:\n", port);
+ ccprintf(" OC idx:%d\n", oc_info_idx[port]);
+ for (i = 0; i < RAMP_COUNT; i++) {
+ ccprintf(" OC %d: s%d recover%lu icl%d\n", i,
+ oc_info[port][i].sup,
+ oc_info[port][i].recover,
+ oc_info[port][i].icl);
+ }
+ }
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(chgramp, command_chgramp,
+ "",
+ "Dump charge ramp state info",
+ NULL);
+#endif
diff --git a/include/charge_ramp.h b/include/charge_ramp.h
new file mode 100644
index 0000000000..629499e6ac
--- /dev/null
+++ b/include/charge_ramp.h
@@ -0,0 +1,82 @@
+/* Copyright 2015 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.
+ */
+
+/* Charge input current limit ramp header for Chrome EC */
+
+#ifndef __CROS_EC_CHG_RAMP_H
+#define __CROS_EC_CHG_RAMP_H
+
+#include "timer.h"
+
+/* Charge ramp state used for checking VBUS */
+enum chg_ramp_vbus_state {
+ CHG_RAMP_VBUS_RAMPING,
+ CHG_RAMP_VBUS_STABLE
+};
+
+/**
+ * Check if ramping is allowed for given supplier
+ *
+ * @supplier Supplier to check
+ *
+ * @return Ramping is allowed for given supplier
+ */
+int board_is_ramp_allowed(int supplier);
+
+/**
+ * Get the maximum current limit that we are allowed to ramp to
+ *
+ * @supplier Active supplier type
+ *
+ * @return Maximum current in mA
+ */
+int board_get_ramp_current_limit(int supplier);
+
+/**
+ * Check if board is consuming full input current
+ *
+ * @return Board is consuming full input current
+ */
+int board_is_consuming_full_charge(void);
+
+/**
+ * Check if VBUS is too low
+ *
+ * @param ramp_state Current ramp state
+ *
+ * @return VBUS is sagging low
+ */
+int board_is_vbus_too_low(enum chg_ramp_vbus_state ramp_state);
+
+/**
+ * Get the input current limit set by ramp module
+ *
+ * Active input current limit (mA)
+ */
+int chg_ramp_get_current_limit(void);
+
+#ifdef HAS_TASK_CHG_RAMP
+/**
+ * Notify charge ramp module of supplier type change on a port. If port
+ * is CHARGE_PORT_NONE, the call indicates the last charge supplier went
+ * away.
+ *
+ * @port Active charging port
+ * @supplier Active charging supplier
+ * @current Minimum input current limit
+ * @registration_time Timestamp of when the supplier is registered
+ */
+void chg_ramp_charge_supplier_change(int port, int supplier, int current,
+ timestamp_t registration_time);
+
+#else
+static inline void chg_ramp_charge_supplier_change(
+ int port, int supplier, timestamp_t registration_time) { }
+
+/* Point directly to board function to set charge limit */
+#define chg_ramp_set_min_current board_set_charge_limit
+#endif
+
+#endif /* __CROS_EC_CHG_RAMP_H */
diff --git a/include/config.h b/include/config.h
index af14aeae19..9e4e1999f7 100644
--- a/include/config.h
+++ b/include/config.h
@@ -215,6 +215,14 @@
#undef CONFIG_CAPSENSE
/*****************************************************************************/
+
+/* Compile charge manager */
+#undef CONFIG_CHARGE_MANAGER
+
+/* Compile input current ramping support */
+#undef CONFIG_CHARGE_RAMP
+
+/*****************************************************************************/
/* Charger config */
/* Compile common charge state code. You must pick an implementation. */
@@ -335,6 +343,7 @@
#undef CONFIG_CMD_ACCELS
#undef CONFIG_CMD_ACCEL_INFO
#undef CONFIG_CMD_BATDEBUG
+#undef CONFIG_CMD_CHGRAMP
#undef CONFIG_CMD_CLOCKGATES
#undef CONFIG_CMD_COMXTEST
#undef CONFIG_CMD_ECTEMP
diff --git a/test/build.mk b/test/build.mk
index 787e99a320..115fb515ee 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -36,6 +36,7 @@ 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_lid math_util sbs_charging_v2 battery_get_params_smart
test-list-host+=lightbar inductive_charging usb_pd fan charge_manager
+test-list-host+=charge_ramp
adapter-y=adapter.o
battery_get_params_smart-y=battery_get_params_smart.o
@@ -43,6 +44,7 @@ bklight_lid-y=bklight_lid.o
bklight_passthru-y=bklight_passthru.o
button-y=button.o
charge_manager-y=charge_manager.o
+charge_ramp-y+=charge_ramp.o
console_edit-y=console_edit.o
extpwr_gpio-y=extpwr_gpio.o
flash-y=flash.o
diff --git a/test/charge_ramp.c b/test/charge_ramp.c
new file mode 100644
index 0000000000..e853b46e93
--- /dev/null
+++ b/test/charge_ramp.c
@@ -0,0 +1,502 @@
+/* Copyright 2015 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 AC input current ramp.
+ */
+
+#include "charge_manager.h"
+#include "charge_ramp.h"
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "task.h"
+#include "test_util.h"
+#include "timer.h"
+#include "util.h"
+
+#define TASK_EVENT_OVERCURRENT (1 << 0)
+
+#define RAMP_STABLE_DELAY (120*SECOND)
+
+/*
+ * Time to delay for detecting the charger type. This value follows
+ * the value in common/charge_ramp.c.
+ */
+#define CHARGE_DETECT_DELAY (2*SECOND)
+
+static int system_load_current_ma;
+static int vbus_low_current_ma = 500;
+static int overcurrent_current_ma = 3000;
+
+static int charge_limit_ma;
+
+/* Mock functions */
+
+int board_is_ramp_allowed(int supplier)
+{
+ /* Ramp for TEST4-TEST9 */
+ return supplier > CHARGE_SUPPLIER_TEST3;
+}
+
+int board_is_consuming_full_charge(void)
+{
+ return charge_limit_ma <= system_load_current_ma;
+}
+
+int board_is_vbus_too_low(enum chg_ramp_vbus_state ramp_state)
+{
+ return MIN(system_load_current_ma, charge_limit_ma) >
+ vbus_low_current_ma;
+}
+
+void board_set_charge_limit(int limit_ma)
+{
+ charge_limit_ma = limit_ma;
+ if (charge_limit_ma > overcurrent_current_ma)
+ task_set_event(TASK_ID_TEST_RUNNER, TASK_EVENT_OVERCURRENT, 0);
+}
+
+int board_get_ramp_current_limit(int supplier)
+{
+ if (supplier == CHARGE_SUPPLIER_TEST9)
+ return 1600;
+ else if (supplier == CHARGE_SUPPLIER_TEST8)
+ return 2400;
+ else
+ return 3000;
+}
+
+/* Test utilities */
+
+static void plug_charger_with_ts(int supplier_type, int port, int min_current,
+ int vbus_low_current, int overcurrent_current,
+ timestamp_t reg_time)
+{
+ vbus_low_current_ma = vbus_low_current;
+ overcurrent_current_ma = overcurrent_current;
+ chg_ramp_charge_supplier_change(port, supplier_type, min_current,
+ reg_time);
+}
+
+static void plug_charger(int supplier_type, int port, int min_current,
+ int vbus_low_current, int overcurrent_current)
+{
+ plug_charger_with_ts(supplier_type, port, min_current,
+ vbus_low_current, overcurrent_current,
+ get_time());
+}
+
+static void unplug_charger(void)
+{
+ chg_ramp_charge_supplier_change(CHARGE_PORT_NONE, CHARGE_SUPPLIER_NONE,
+ 0, get_time());
+}
+
+static int unplug_charger_and_check(void)
+{
+ unplug_charger();
+ usleep(CHARGE_DETECT_DELAY);
+ return charge_limit_ma == 0;
+}
+
+static int wait_stable_no_overcurrent(void)
+{
+ return task_wait_event(RAMP_STABLE_DELAY) != TASK_EVENT_OVERCURRENT;
+}
+
+static int is_in_range(int x, int min, int max)
+{
+ return x >= min && x <= max;
+}
+
+/* Tests */
+
+static int test_no_ramp(void)
+{
+ system_load_current_ma = 3000;
+ /* A powerful charger, but hey, you're not allowed to ramp! */
+ plug_charger(CHARGE_SUPPLIER_TEST1, 0, 500, 3000, 3000);
+ usleep(CHARGE_DETECT_DELAY);
+ /* That's right. Start at 500 mA */
+ TEST_ASSERT(charge_limit_ma == 500);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ /* ... and stays at 500 mA */
+ TEST_ASSERT(charge_limit_ma == 500);
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_full_ramp(void)
+{
+ system_load_current_ma = 3000;
+ /* Now you get to ramp with this 3A charger */
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 3000, 3000);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Start with something around 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 800));
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ /* And ramp up to 3A */
+ TEST_ASSERT(charge_limit_ma == 3000);
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_vbus_dip(void)
+{
+ system_load_current_ma = 3000;
+ /* VBUS dips too low right before the charger shuts down */
+ plug_charger(CHARGE_SUPPLIER_TEST5, 0, 1000, 1500, 1600);
+
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1300, 1500));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_overcurrent(void)
+{
+ system_load_current_ma = 3000;
+ /* Huh...VBUS doesn't dip before the charger shuts down */
+ plug_charger(CHARGE_SUPPLIER_TEST6, 0, 500, 3000, 1500);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Ramp starts at 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+
+ while (task_wait_event(RAMP_STABLE_DELAY) == TASK_EVENT_OVERCURRENT) {
+ /* Charger goes away but comes back after 0.6 seconds */
+ unplug_charger();
+ usleep(MSEC * 600);
+ plug_charger(CHARGE_SUPPLIER_TEST6, 0, 500, 3000, 1500);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Ramp restarts at 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ }
+
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1300, 1500));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_switch_outlet(void)
+{
+ int i;
+
+ system_load_current_ma = 3000;
+ /* Here's a nice powerful charger */
+ plug_charger(CHARGE_SUPPLIER_TEST7, 0, 500, 3000, 3000);
+
+ /*
+ * Now the user decides to move it to a nearby outlet...actually
+ * he decides to move it 5 times!
+ */
+ for (i = 0; i < 5; ++i) {
+ usleep(SECOND * 20);
+ unplug_charger();
+ usleep(SECOND * 1.5);
+ plug_charger(CHARGE_SUPPLIER_TEST7, 0, 500, 3000, 3000);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Ramp restarts at 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ }
+
+ /* Should still ramp up to 3000 mA */
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 3000);
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_fast_switch(void)
+{
+ int i;
+
+ system_load_current_ma = 3000;
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 3000, 3000);
+
+ /*
+ * Here comes that naughty user again, and this time he's switching
+ * outlet really quickly. Fortunately this time he only does it twice.
+ */
+ for (i = 0; i < 2; ++i) {
+ usleep(SECOND * 20);
+ unplug_charger();
+ usleep(600 * MSEC);
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 3000, 3000);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Ramp restarts at 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ }
+
+ /* Should still ramp up to 3000 mA */
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 3000);
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_overcurrent_after_switch_outlet(void)
+{
+ system_load_current_ma = 3000;
+ /* Here's a less powerful charger */
+ plug_charger(CHARGE_SUPPLIER_TEST5, 0, 500, 3000, 1500);
+ usleep(SECOND * 5);
+
+ /* Now the user decides to move it to a nearby outlet */
+ unplug_charger();
+ usleep(SECOND * 1.5);
+ plug_charger(CHARGE_SUPPLIER_TEST5, 0, 500, 3000, 1500);
+
+ /* Okay the user is satisified */
+ while (task_wait_event(RAMP_STABLE_DELAY) == TASK_EVENT_OVERCURRENT) {
+ /* Charger goes away but comes back after 0.6 seconds */
+ unplug_charger();
+ usleep(MSEC * 600);
+ plug_charger(CHARGE_SUPPLIER_TEST5, 0, 500, 3000, 1500);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Ramp restarts at 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ }
+
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1300, 1500));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_partial_load(void)
+{
+ /* We have a 3A charger, but we just want 1.5A */
+ system_load_current_ma = 1500;
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 3000, 2500);
+
+ /* We should end up with a little bit more than 1.5A */
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1500, 1600));
+
+ /* Ok someone just started watching YouTube */
+ system_load_current_ma = 2000;
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 2000, 2100));
+
+ /* Somehow the system load increases again */
+ system_load_current_ma = 2600;
+ while (task_wait_event(RAMP_STABLE_DELAY) == TASK_EVENT_OVERCURRENT) {
+ /* Charger goes away but comes back after 0.6 seconds */
+ unplug_charger();
+ usleep(MSEC * 600);
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 3000, 2500);
+ usleep(CHARGE_DETECT_DELAY);
+ /* Ramp restarts at 500 mA */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ }
+
+ /* Alright the charger isn't powerful enough, so we'll stop at 2.5A */
+ TEST_ASSERT(is_in_range(charge_limit_ma, 2300, 2500));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_charge_supplier_stable(void)
+{
+ system_load_current_ma = 3000;
+ /* The charger says it's of type TEST4 initially */
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 1500, 1600);
+ /*
+ * And then it decides it's actually TEST2 after 0.5 seconds,
+ * why? Well, this charger is just evil.
+ */
+ usleep(500 * MSEC);
+ plug_charger(CHARGE_SUPPLIER_TEST2, 0, 3000, 3000, 3000);
+ /* We should get 3A right away. */
+ usleep(SECOND);
+ TEST_ASSERT(charge_limit_ma == 3000);
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_charge_supplier_stable_ramp(void)
+{
+ system_load_current_ma = 3000;
+ /* This time we start with a non-ramp charge supplier */
+ plug_charger(CHARGE_SUPPLIER_TEST3, 0, 500, 3000, 3000);
+ /*
+ * After 0.5 seconds, it's decided that the supplier is actually
+ * a 1.5A ramp supplier.
+ */
+ usleep(500 * MSEC);
+ plug_charger(CHARGE_SUPPLIER_TEST5, 0, 500, 1400, 1500);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1200, 1400));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_charge_supplier_change(void)
+{
+ system_load_current_ma = 3000;
+ /* Start with a 3A ramp charge supplier */
+ plug_charger(CHARGE_SUPPLIER_TEST4, 0, 500, 3000, 3000);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 3000);
+
+ /* The charger decides to change type to a 1.5A non-ramp supplier */
+ plug_charger(CHARGE_SUPPLIER_TEST1, 0, 1500, 3000, 3000);
+ usleep(500 * MSEC);
+ TEST_ASSERT(charge_limit_ma == 1500);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 1500);
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_charge_port_change(void)
+{
+ system_load_current_ma = 3000;
+ /* Start with a 1.5A ramp charge supplier on port 0 */
+ plug_charger(CHARGE_SUPPLIER_TEST5, 0, 500, 1400, 1500);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1200, 1400));
+
+ /* Here comes a 2.1A ramp charge supplier on port 1 */
+ plug_charger(CHARGE_SUPPLIER_TEST6, 0, 500, 2000, 2100);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1800, 2000));
+
+ /* Now we have a 2.5A non-ramp charge supplier on port 0 */
+ plug_charger(CHARGE_SUPPLIER_TEST1, 0, 2500, 3000, 3000);
+ usleep(SECOND);
+ TEST_ASSERT(charge_limit_ma == 2500);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 2500);
+
+ /* Unplug on port 0 */
+ plug_charger(CHARGE_SUPPLIER_TEST6, 0, 500, 2000, 2100);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1800, 2000));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_vbus_shift(void)
+{
+ system_load_current_ma = 3000;
+ /*
+ * At first, the charger is able to supply up to 1900 mA before
+ * the VBUS voltage starts to drop.
+ */
+ plug_charger(CHARGE_SUPPLIER_TEST7, 0, 500, 1900, 2000);
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1700, 1900));
+
+ /* The charger heats up and VBUS voltage drops by 100mV */
+ vbus_low_current_ma = 1800;
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1600, 1800));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_equal_priority_overcurrent(void)
+{
+ int overcurrent_count = 0;
+ timestamp_t oc_time = get_time();
+
+ system_load_current_ma = 3000;
+
+ /*
+ * Now we have two charge suppliers of equal priorties plugged into
+ * port 0 and port 1. If the active one browns out, charge manager
+ * switches to the other one.
+ */
+ while (1) {
+ plug_charger_with_ts(CHARGE_SUPPLIER_TEST4, 0, 500, 3000,
+ 2000, oc_time);
+ oc_time = get_time();
+ oc_time.val += 600 * MSEC;
+ if (wait_stable_no_overcurrent())
+ break;
+ plug_charger_with_ts(CHARGE_SUPPLIER_TEST4, 1, 500, 3000,
+ 2000, oc_time);
+ oc_time = get_time();
+ oc_time.val += 600 * MSEC;
+ if (wait_stable_no_overcurrent())
+ break;
+ if (overcurrent_count++ >= 10) {
+ /*
+ * Apparently we are in a loop and can never reach
+ * stable state.
+ */
+ unplug_charger();
+ return EC_ERROR_UNKNOWN;
+ }
+ }
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+static int test_ramp_limit(void)
+{
+ system_load_current_ma = 3000;
+
+ /* Plug in supplier that is limited to 1.6A */
+ plug_charger(CHARGE_SUPPLIER_TEST9, 0, 500, 3000, 3000);
+ usleep(SECOND);
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 1600);
+
+ /* Switch to supplier that is limited to 2.4A */
+ plug_charger(CHARGE_SUPPLIER_TEST8, 1, 500, 3000, 3000);
+ usleep(SECOND);
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(charge_limit_ma == 2400);
+
+ /* Go back to 1.6A limited, but VBUS goes low before that point */
+ plug_charger(CHARGE_SUPPLIER_TEST9, 0, 500, 1200, 1300);
+ usleep(SECOND);
+ TEST_ASSERT(is_in_range(charge_limit_ma, 500, 700));
+ TEST_ASSERT(wait_stable_no_overcurrent());
+ TEST_ASSERT(is_in_range(charge_limit_ma, 1000, 1200));
+
+ TEST_ASSERT(unplug_charger_and_check());
+ return EC_SUCCESS;
+}
+
+void run_test(void)
+{
+ test_reset();
+
+ RUN_TEST(test_no_ramp);
+ RUN_TEST(test_full_ramp);
+ RUN_TEST(test_vbus_dip);
+ RUN_TEST(test_overcurrent);
+ RUN_TEST(test_switch_outlet);
+ RUN_TEST(test_fast_switch);
+ RUN_TEST(test_overcurrent_after_switch_outlet);
+ RUN_TEST(test_partial_load);
+ RUN_TEST(test_charge_supplier_stable);
+ RUN_TEST(test_charge_supplier_stable_ramp);
+ RUN_TEST(test_charge_supplier_change);
+ RUN_TEST(test_charge_port_change);
+ RUN_TEST(test_vbus_shift);
+ RUN_TEST(test_equal_priority_overcurrent);
+ RUN_TEST(test_ramp_limit);
+
+ test_print_result();
+}
diff --git a/test/charge_ramp.tasklist b/test/charge_ramp.tasklist
new file mode 100644
index 0000000000..97a94fef6c
--- /dev/null
+++ b/test/charge_ramp.tasklist
@@ -0,0 +1,18 @@
+/* Copyright 2015 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(CHG_RAMP, chg_ramp_task, NULL, SMALLER_TASK_STACK_SIZE)
diff --git a/test/test_config.h b/test/test_config.h
index 56fdf87772..c4bf3f57fa 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -139,5 +139,9 @@ int board_discharge_on_ac(int enabled);
#define CONFIG_USB_PD_DUAL_ROLE
#endif
+#ifdef TEST_CHARGE_RAMP
+#define CONFIG_CHARGE_RAMP
+#endif
+
#endif /* TEST_BUILD */
#endif /* __CROS_EC_TEST_CONFIG_H */