/* 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 "charge_state.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; 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 < CONFIG_USB_PD_PORT_COUNT; i++) oc_info[i][0].sup = CHARGE_SUPPLIER_NONE; /* * Sleep until chg_ramp_charge_supplier_change is called to avoid * setting input current limit to zero. chg_ramp_charge_supplier_change * won't be called until charge_manager is ready to call * board_set_charge_limit by itself (if there is no chg_ramp_task). */ if (!IS_ENABLED(TEST_BUILD)) task_wait_event(-1); 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 (!charge_is_consuming_full_input_current()) { 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 (!charge_is_consuming_full_input_current()) { 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; } ramp_st_prev = ramp_st; ramp_st = ramp_st_new; active_icl = active_icl_new; /* Skip setting limit if status is stable twice in a row */ if (ramp_st_prev != CHG_RAMP_STABLE || ramp_st != CHG_RAMP_STABLE) { CPRINTS("Ramp p%d st%d %dmA %dmA", active_port, ramp_st, min_icl, active_icl); /* 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