diff options
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/charge_state_v2.c | 59 | ||||
-rw-r--r-- | common/ocpc.c | 298 | ||||
-rw-r--r-- | include/config.h | 8 | ||||
-rw-r--r-- | include/ocpc.h | 38 |
5 files changed, 399 insertions, 5 deletions
diff --git a/common/build.mk b/common/build.mk index 1bae3bca41..1218c24326 100644 --- a/common/build.mk +++ b/common/build.mk @@ -102,6 +102,7 @@ common-$(CONFIG_HOSTCMD_X86)+=acpi.o port80.o ec_features.o common-$(CONFIG_MAG_CALIBRATE)+= mag_cal.o math_util.o vec3.o mat33.o mat44.o \ kasa.o common-$(CONFIG_MKBP_EVENT)+=mkbp_event.o +common-$(CONFIG_OCPC)+=ocpc.o common-$(CONFIG_ONEWIRE)+=onewire.o common-$(CONFIG_PECI_COMMON)+=peci.o common-$(CONFIG_POWER_BUTTON)+=power_button.o diff --git a/common/charge_state_v2.c b/common/charge_state_v2.c index 6354cf0a40..ce5b826c8f 100644 --- a/common/charge_state_v2.c +++ b/common/charge_state_v2.c @@ -164,6 +164,7 @@ enum problem_type { PR_CHG_FLAGS, PR_BATT_FLAGS, PR_CUSTOM, + PR_CFG_SEC_CHG, NUM_PROBLEM_TYPES }; @@ -177,6 +178,7 @@ static const char * const prob_text[] = { "chg params", "batt params", "custom profile", + "cfg secondary chg" }; BUILD_ASSERT(ARRAY_SIZE(prob_text) == NUM_PROBLEM_TYPES); @@ -1080,6 +1082,15 @@ static void dump_charge_state(void) #ifdef CONFIG_OCPC ccprintf("ocpc.*:\n"); DUMP_OCPC(active_chg_chip, "%d"); + DUMP_OCPC(combined_rsys_rbatt_mo, "%dmOhm"); + DUMP_OCPC(primary_vbus_mv, "%dmV"); + DUMP_OCPC(primary_ibus_ma, "%dmA"); + DUMP_OCPC(secondary_vbus_mv, "%dmV"); + DUMP_OCPC(secondary_ibus_ma, "%dmA"); + DUMP_OCPC(last_error, "%d"); + DUMP_OCPC(integral, "%d"); + DUMP_OCPC(last_vsys, "%dmV"); + cflush(); #endif /* CONFIG_OCPC */ DUMP(requested_voltage, "%dmV"); DUMP(requested_current, "%dmA"); @@ -1195,7 +1206,7 @@ static int calc_is_full(void) */ static int charge_request(int voltage, int current) { - int r1 = EC_SUCCESS, r2 = EC_SUCCESS, r3 = EC_SUCCESS; + int r1 = EC_SUCCESS, r2 = EC_SUCCESS, r3 = EC_SUCCESS, r4 = EC_SUCCESS; static int __bss_slow prev_volt, prev_curr; if (!voltage || !current) { @@ -1240,22 +1251,43 @@ static int charge_request(int voltage, int current) if (r1 != EC_SUCCESS) problem(PR_SET_VOLTAGE, r1); +#ifdef CONFIG_OCPC + /* + * For OCPC systems, if the secondary charger is active, we need to + * configure that charge IC as well. Note that if OCPC ever supports + * more than 2 charger ICs, we'll need to refactor things a bit. The + * following check should be comparing against PRIMARY_CHARGER and + * config_secondary_charger should probably be config_auxiliary_charger + * and take the active chgnum as a parameter. + */ + if (curr.ocpc.active_chg_chip == SECONDARY_CHARGER) { + if ((current >= 0) || (voltage >= 0)) + r3 = ocpc_config_secondary_charger(&curr.desired_input_current, + &curr.ocpc, + voltage, current); + if (r3 != EC_SUCCESS) + problem(PR_CFG_SEC_CHG, r3); + } +#endif /* CONFIG_OCPC */ + /* * Set the charge inhibit bit when possible as it appears to save * power in some cases (e.g. Nyan with BQ24735). */ if (voltage > 0 || current > 0) - r3 = charger_set_mode(0); + r4 = charger_set_mode(0); else - r3 = charger_set_mode(CHARGE_FLAG_INHIBIT_CHARGE); - if (r3 != EC_SUCCESS) - problem(PR_SET_MODE, r3); + r4 = charger_set_mode(CHARGE_FLAG_INHIBIT_CHARGE); + if (r4 != EC_SUCCESS) + problem(PR_SET_MODE, r4); /* * Only update if the request worked, so we'll keep trying on failures. */ if (r1 || r2) return r1 ? r1 : r2; + if (IS_ENABLED(CONFIG_OCPC) && r3) + return r3; if (IS_ENABLED(CONFIG_USB_PD_PREFER_MV) && (prev_volt != voltage || prev_curr != current)) @@ -1630,6 +1662,15 @@ void charger_task(void *u) battery_dynamic[BATT_IDX_BASE].flags = EC_BATT_FLAG_INVALID_DATA; charge_base = -1; #endif +#ifdef CONFIG_OCPC + /* + * We can start off assuming that the board resistance is 0 ohms + * and later on, we can update this value if we charge the + * system in suspend or off. + */ + curr.ocpc.combined_rsys_rbatt_mo = CONFIG_OCPC_DEF_RBATT_MOHMS; + charge_set_active_chg_chip(CHARGE_PORT_NONE); +#endif /* CONFIG_OCPC */ /* * If system is not locked and we don't have a battery to live on, @@ -1713,6 +1754,9 @@ void charger_task(void *u) charger_get_params(&curr.chg); battery_get_params(&curr.batt); +#ifdef CONFIG_OCPC + ocpc_get_adcs(&curr.ocpc); +#endif /* CONFIG_OCPC */ if (prev_bp != curr.batt.is_present) { prev_bp = curr.batt.is_present; @@ -2435,6 +2479,11 @@ void charge_set_active_chg_chip(int idx) CPRINTS("Act Chg: %d", idx); curr.ocpc.active_chg_chip = idx; + if (idx == CHARGE_PORT_NONE) { + curr.ocpc.last_error = 0; + curr.ocpc.integral = 0; + curr.ocpc.last_vsys = OCPC_UNINIT; + } } #endif /* CONFIG_OCPC */ diff --git a/common/ocpc.c b/common/ocpc.c new file mode 100644 index 0000000000..0d0a7dc24a --- /dev/null +++ b/common/ocpc.c @@ -0,0 +1,298 @@ +/* Copyright 2020 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. + */ + +/* OCPC - One Charger IC Per Type-C module */ + +#include "battery.h" +#include "charge_state_v2.h" +#include "charger.h" +#include "common.h" +#include "console.h" +#include "hooks.h" +#include "math_util.h" +#include "ocpc.h" +#include "util.h" + +/* + * These constants were chosen by tuning the PID loop to reduce oscillations and + * minimize overshoot. + */ +#define KP 1 +#define KP_DIV 4 +#define KI 1 +#define KI_DIV 15 +#define KD 1 +#define KD_DIV 10 + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_CHARGER, outstr) +#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) +#define CPRINTS_DBG(format, args...) \ +do { \ + if (debug_output) \ + cprints(CC_CHARGER, format, ## args); \ +} while (0) + +static int k_p = KP; +static int k_i = KI; +static int k_d = KD; +static int k_p_div = KP_DIV; +static int k_i_div = KI_DIV; +static int k_d_div = KD_DIV; +static int debug_output; + +enum phase { + PHASE_UNKNOWN = -1, + PHASE_CC, + PHASE_CV_TRIP, + PHASE_CV_COMPLETE, +}; + +int ocpc_config_secondary_charger(int *desired_input_current, + struct ocpc_data *ocpc, + int voltage_mv, int current_ma) +{ + int rv = EC_SUCCESS; + struct batt_params batt; + const struct battery_info *batt_info; + struct charger_params charger; + int vsys_target = 0; + int drive = 0; + int i_ma; + int min_vsys_target; + int error = 0; + int derivative = 0; + static enum phase ph; + static int prev_limited; + + /* + * There's nothing to do if we're not using this charger. Should + * there be more than two charger ICs in the future, the following check + * should change to ensure that only the active charger IC is acted + * upon. + */ + if (charge_get_active_chg_chip() != SECONDARY_CHARGER) + return EC_ERROR_INVAL; + + if (ocpc->last_vsys == OCPC_UNINIT) + ph = PHASE_UNKNOWN; + + if (current_ma == 0) { + vsys_target = voltage_mv; + goto set_vsys; + } + + /* + * We need to induce a current flow that matches the requested current + * by raising VSYS. Let's start by getting the latest data that we + * know of. + */ + batt_info = battery_get_info(); + battery_get_params(&batt); + ocpc_get_adcs(ocpc); + charger_get_params(&charger); + + /* Set our current target accordingly. */ + if (batt.voltage < batt.desired_voltage) { + if (ph < PHASE_CV_TRIP) + ph = PHASE_CC; + i_ma = batt.desired_current; + } else{ + /* + * Once the battery voltage reaches the desired voltage, we + * should note that we've reached the CV step and set VSYS to + * the desired CV + offset. + */ + i_ma = batt.current; + ph = ph == PHASE_CC ? PHASE_CV_TRIP : PHASE_CV_COMPLETE; + + } + + /* Ensure our target is not negative. */ + i_ma = MAX(i_ma, 0); + + /* + * We'll use our current target and our combined Rsys+Rbatt to seed our + * VSYS target. However, we'll use a PID loop to correct the error and + * help drive VSYS to what it _should_ be in order to reach our current + * target. The first time through this function, we won't make any + * corrections in order to determine our initial error. + */ + if (ocpc->last_vsys != OCPC_UNINIT) { + error = i_ma - batt.current; + /* Add some hysteresis. */ + if (ABS(error) < 4) + error = 0; + + derivative = error - ocpc->last_error; + ocpc->last_error = error; + ocpc->integral += error; + if (ocpc->integral > 500) + ocpc->integral = 500; + } + + CPRINTS_DBG("phase = %d", ph); + CPRINTS_DBG("error = %dmA", error); + CPRINTS_DBG("derivative = %d", derivative); + CPRINTS_DBG("integral = %d", ocpc->integral); + CPRINTS_DBG("batt.voltage = %dmV", batt.voltage); + CPRINTS_DBG("batt.desired_voltage = %dmV", batt.desired_voltage); + CPRINTS_DBG("batt.desired_current = %dmA", batt.desired_current); + CPRINTS_DBG("batt.current = %dmA", batt.current); + CPRINTS_DBG("i_ma = %dmA", i_ma); + + /* + * Assuming that our combined Rsys + Rbatt resistance is correct, this + * should be enough to reach our desired i_ma. If it's not, our PID + * loop will help us get there. + */ + min_vsys_target = (i_ma * ocpc->combined_rsys_rbatt_mo) / 1000; + min_vsys_target += MIN(batt.voltage, batt.desired_voltage); + CPRINTS_DBG("min_vsys_target = %d", min_vsys_target); + + /* Obtain the drive from our PID controller. */ + if (ocpc->last_vsys != OCPC_UNINIT) { + drive = (k_p * error / k_p_div) + + (k_i * ocpc->integral / k_i_div) + + (k_d * derivative / k_d_div); + /* + * Let's limit upward transitions to 500mV. It's okay to reduce + * VSYS rather quickly, but we'll be conservative on + * increasing VSYS. + */ + if (drive > 500) + drive = 500; + CPRINTS_DBG("drive = %d", drive); + } + + /* + * Adjust our VSYS target by applying the calculated drive. Note that + * we won't apply our drive the first time through this function such + * that we can determine our initial error. + */ + if (ocpc->last_vsys != OCPC_UNINIT) + vsys_target = ocpc->last_vsys + drive; + + /* + * Once we're in the CV region, all we need to do is keep VSYS at the + * desired voltage. + */ + if (ph >= PHASE_CV_TRIP) + vsys_target = batt.desired_voltage; + + /* + * Ensure VSYS is no higher than 1V over the max battery voltage, but + * greater than or equal to our minimum VSYS target. + */ + vsys_target = CLAMP(vsys_target, min_vsys_target, + batt_info->voltage_max+1000); + + /* If we're input current limited, we cannot increase VSYS any more. */ + CPRINTS_DBG("OCPC: Inst. Input Current: %dmA (Limit: %dmA)", + ocpc->secondary_ibus_ma, *desired_input_current); + if ((ocpc->secondary_ibus_ma >= (*desired_input_current * 95 / 100)) && + (vsys_target > ocpc->last_vsys) && + (ocpc->last_vsys != OCPC_UNINIT)) { + if (!prev_limited) + CPRINTS("Input limited! Not increasing VSYS"); + prev_limited = 1; + return rv; + } + prev_limited = 0; + +set_vsys: + /* To reduce spam, only print when we change VSYS significantly. */ + if ((ABS(vsys_target - ocpc->last_vsys) > 10) || debug_output) + CPRINTS("OCPC: Target VSYS: %dmV", vsys_target); + charger_set_voltage(SECONDARY_CHARGER, vsys_target); + ocpc->last_vsys = vsys_target; + + return rv; +} + +void ocpc_get_adcs(struct ocpc_data *ocpc) +{ + int val; + + val = 0; + if (!charger_get_vbus_voltage(PRIMARY_CHARGER, &val)) + ocpc->primary_vbus_mv = val; + + val = 0; + if (!charger_get_vbus_voltage(SECONDARY_CHARGER, &val)) + ocpc->secondary_vbus_mv = val; + + val = 0; + if (!charger_get_input_current(PRIMARY_CHARGER, &val)) + ocpc->primary_ibus_ma = val; + + val = 0; + if (!charger_get_input_current(SECONDARY_CHARGER, &val)) + ocpc->secondary_ibus_ma = val; +} + +__overridable void ocpc_get_pid_constants(int *kp, int *kp_div, + int *ki, int *ki_div, + int *kd, int *kd_div) +{ +} + +static void ocpc_set_pid_constants(void) +{ + ocpc_get_pid_constants(&k_p, &k_p_div, &k_i, &k_i_div, &k_d, &k_d_div); +} +DECLARE_HOOK(HOOK_INIT, ocpc_set_pid_constants, HOOK_PRIO_DEFAULT); + +static int command_ocpcdebug(int argc, char **argv) +{ + if (argc < 2) + return EC_ERROR_PARAM_COUNT; + + if (!parse_bool(argv[1], &debug_output)) + return EC_ERROR_PARAM1; + + return EC_SUCCESS; +} +DECLARE_SAFE_CONSOLE_COMMAND(ocpcdebug, command_ocpcdebug, + "<enable/disable>", + "enable/disable debug prints for OCPC data"); + +static int command_ocpcpid(int argc, char **argv) +{ + int *num, *denom; + + if (argc == 4) { + switch (argv[1][0]) { + case 'p': + num = &k_p; + denom = &k_p_div; + break; + + case 'i': + num = &k_i; + denom = &k_i_div; + break; + + case 'd': + num = &k_d; + denom = &k_d_div; + break; + default: + return EC_ERROR_PARAM1; + } + + *num = atoi(argv[2]); + *denom = atoi(argv[3]); + } + + /* Print the current constants */ + ccprintf("Kp = %d / %d\n", k_p, k_p_div); + ccprintf("Ki = %d / %d\n", k_i, k_i_div); + ccprintf("Kd = %d / %d\n", k_d, k_d_div); + return EC_SUCCESS; +} +DECLARE_SAFE_CONSOLE_COMMAND(ocpcpid, command_ocpcpid, + "[<k/p/d> <numerator> <denominator>]", + "Show/Set PID constants for OCPC PID loop"); diff --git a/include/config.h b/include/config.h index 336b3dade6..108edc89a5 100644 --- a/include/config.h +++ b/include/config.h @@ -1024,6 +1024,14 @@ */ #undef CONFIG_OCPC +/* + * Boards using OCPC must define this value in order to seed the starting board + * battery and system resistance between the secondary charger IC and the + * battery. This should be at a minimum the Rds(on) resistance of the BFET plus + * the series sense resistor. + */ +#undef CONFIG_OCPC_DEF_RBATT_MOHMS + /* Enable trickle charging */ #undef CONFIG_TRICKLE_CHARGING diff --git a/include/ocpc.h b/include/ocpc.h index c5c41470cb..9dbd372ae5 100644 --- a/include/ocpc.h +++ b/include/ocpc.h @@ -13,9 +13,47 @@ #define PRIMARY_CHARGER 0 #define SECONDARY_CHARGER 1 +#define OCPC_UNINIT 0xdededede + struct ocpc_data { /* Index into chg_chips[] table for the charger IC that is switching. */ int active_chg_chip; + + int combined_rsys_rbatt_mo; /* System resistance b/w output and Vbatt */ + + /* ADC values */ + int primary_vbus_mv; /* VBUS measured by the primary charger IC */ + int primary_ibus_ma; /* IBUS measrued by the primary charger IC */ + int secondary_vbus_mv; /* VBUS measured by the secondary charger IC */ + int secondary_ibus_ma; /* IBUS measure by the secondary charger IC */ + + /* PID values */ + int last_error; + int integral; + int last_vsys; }; +/** Set the VSYS target for the secondary charger IC. + * + * @param curr: Pointer to desired_input_current + * @param ocpc: Pointer to OCPC data + * @param voltage_mv: The desired voltage + * @param current_ma: The desired current + * @return EC_SUCCESS on success, error otherwise. + */ +int ocpc_config_secondary_charger(int *desired_input_current, + struct ocpc_data *ocpc, + int voltage_mv, int current_ma); + +/** Get the runtime data from the various ADCs. + * + * @param ocpc: Pointer to OCPC data + */ +void ocpc_get_adcs(struct ocpc_data *ocpc); + +/* Set the PID constants for the charging loop */ +__overridable void ocpc_get_pid_constants(int *kp, int *kp_div, + int *ki, int *ki_div, + int *kd, int *kd_div); + #endif /* __CROS_EC_OCPC_H */ |