| /* 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 "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 (board_is_ramp_allowed(active_sup)) { |
| min_icl = RAMP_CURR_START_MA; |
| max_icl = board_get_ramp_current_limit(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) |
| { |
| 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 (!board_is_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 (board_is_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 |