diff options
Diffstat (limited to 'common/charge_ramp_sw.c')
-rw-r--r-- | common/charge_ramp_sw.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/common/charge_ramp_sw.c b/common/charge_ramp_sw.c new file mode 100644 index 0000000000..cfdfa50c21 --- /dev/null +++ b/common/charge_ramp_sw.c @@ -0,0 +1,385 @@ +/* Copyright 2017 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 "ec_commands.h" +#include "task.h" +#include "timer.h" +#include "usb_pd.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 + +/* 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) +#define RAMP_CURR_START_MA 500 + +/* 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_DELAY, + 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; + int oc_detected; + int sup; + int icl; +}; + +/* OCP info for each over-current */ +static struct oc_info oc_info[CONFIG_USB_PD_PORT_COUNT][RAMP_COUNT]; +static int oc_info_idx[CONFIG_USB_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 int active_vtg; +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, int voltage) +{ + /* + * 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; + active_vtg = voltage; + + /* Set min and max input current limit based on if ramp is allowed */ + if (chg_ramp_allowed(active_sup)) { + min_icl = RAMP_CURR_START_MA; + max_icl = chg_ramp_max(active_sup, current); + } else { + min_icl = max_icl = current; + } + + reg_time = registration_time; + if (ramp_st != CHG_RAMP_STABILIZE) { + ramp_st = (active_port == CHARGE_PORT_NONE) ? + CHG_RAMP_DISCONNECTED : CHG_RAMP_CHARGE_DETECT_DELAY; + CPRINTS("Ramp reset: st%d", 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; + } +} + +int chg_ramp_is_detected(void) +{ + /* Charger detected (charge detect delay has passed) */ + return ramp_st > CHG_RAMP_CHARGE_DETECT_DELAY; +} + +int chg_ramp_is_stable(void) +{ + return ramp_st == CHG_RAMP_STABLE; +} + +void chg_ramp_task(void *u) +{ + int task_wait_time = -1; + int i, lim; + uint64_t detect_end_time_us = 0, time_us; + int last_active_port = CHARGE_PORT_NONE; + + /* + * 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; + static uint8_t values_have_changed_at_least_once; + + /* Clear last OCP supplier to guarantee we ramp on first connect */ + for (i = 0; i < CONFIG_USB_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: + /* Delay for charge_manager to determine supplier */ + /* + * On entry to state, or if port changes, check + * timestamps to determine if this was likely an + * OC event (check if we lost VBUS and it came back + * within OC_RECOVER_MAX_TIME). + */ + if (ramp_st_prev != ramp_st || + active_port != last_active_port) { + last_active_port = active_port; + if (reg_time.val < + ACTIVE_OC_INFO.ts.val + + OC_RECOVER_MAX_TIME) { + ACTIVE_OC_INFO.oc_detected = 1; + } else { + for (i = 0; i < RAMP_COUNT; ++i) + oc_info[active_port][i]. + oc_detected = 0; + } + detect_end_time_us = get_time().val + + CHARGE_DETECT_DELAY; + task_wait_time = CHARGE_DETECT_DELAY; + break; + } + + /* If detect delay has not passed, set wait time */ + time_us = get_time().val; + if (time_us < detect_end_time_us) { + task_wait_time = detect_end_time_us - time_us; + break; + } + + /* Detect delay is over, fall through to next state */ + ramp_st_new = CHG_RAMP_OVERCURRENT_DETECT; + /* notify host of power info change */ + pd_send_host_event(PD_EVENT_POWER_CHANGE); + 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 (!chg_ramp_allowed(active_sup)) { + active_icl_new = min_icl; + ramp_st_new = CHG_RAMP_STABLE; + break; + } + + /* + * 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; + } + + /* + * 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].oc_detected) + 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(active_port, + 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_DELAY; + break; + case CHG_RAMP_STABLE: + /* Maintain input current limit */ + /* On entry log charging stats */ + if (ramp_st_prev != ramp_st) { +#ifdef CONFIG_USB_PD_LOGGING + charge_manager_save_log(active_port); +#endif + /* notify host of power info change */ + pd_send_host_event(PD_EVENT_POWER_CHANGE); + } + + /* Keep an eye on VBUS and restart ramping if it dips */ + if (chg_ramp_allowed(active_sup) && + board_is_vbus_too_low(active_port, + CHG_RAMP_VBUS_STABLE)) { + CPRINTS("VBUS low; Re-ramp"); + max_icl = MAX(min_icl, + max_icl - RAMP_ICL_BACKOFF); + 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); + values_have_changed_at_least_once = 1; + } + + ramp_st_prev = ramp_st; + ramp_st = ramp_st_new; + active_icl = active_icl_new; + + /* + * Don't perform any action unless something has changed. + * Otherwise, when the task starts, we may try and set a current + * limit that's invalid/uninitialized. + */ + if (values_have_changed_at_least_once) { + /* Set the input current limit */ + lim = chg_ramp_get_current_limit(); + board_set_charge_limit(active_port, active_sup, lim, + lim, active_vtg); + } + + 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 < CONFIG_USB_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 oc_det%d icl%d\n", i, + oc_info[port][i].sup, + oc_info[port][i].oc_detected, + oc_info[port][i].icl); + } + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(chgramp, command_chgramp, + "", + "Dump charge ramp state info"); +#endif |