384 lines
10 KiB
C
384 lines
10 KiB
C
/* 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
|