diff options
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/charge_manager.c | 35 | ||||
-rw-r--r-- | common/charge_ramp.c | 323 | ||||
-rw-r--r-- | include/charge_ramp.h | 82 | ||||
-rw-r--r-- | include/config.h | 9 | ||||
-rw-r--r-- | test/build.mk | 2 | ||||
-rw-r--r-- | test/charge_ramp.c | 502 | ||||
-rw-r--r-- | test/charge_ramp.tasklist | 18 | ||||
-rw-r--r-- | test/test_config.h | 4 |
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 */ |