summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAseda Aboagye <aaboagye@google.com>2020-04-03 17:18:53 -0700
committerJustin TerAvest <teravest@chromium.org>2020-06-02 17:25:42 +0000
commit7229fab3bb2d4c1983dab388210eb894855aec7d (patch)
tree2e7dae00b9ed961f5929cc982f8aa0057f918c94
parentdba753130cd341c047afd652ab28a2b099dde9ee (diff)
downloadchrome-ec-7229fab3bb2d4c1983dab388210eb894855aec7d.tar.gz
OCPC: Configure VSYS via secondary charger IC
This commit adds the bulk of the work in getting OCPC functional. Since the secondary charger IC cannot directly sense the current entering the battery, with OCPC, we recruit the EC to do this work instead. Essentially, VSYS needs to be chosen such that we induce the desired current in the battery while also accounting for losses in the system between the output of the secondary charger IC and the battery. To start, a board needs to define the following CONFIG_* option: CONFIG_OCPC_DEF_RBATT_MOHMS This should be at least the R_ds(on) resistance of the BFET and the series sense resistance. The board should also define CONFIG_OCPC. With the combined system resistance, we can calculate the VSYS required to induce the desired current. However, we will also use a PID control loop to help drive our VSYS target to what it should actually be accounting for our error. The PID constants were found by tuning on a waddledoo board. It remains to be seen whether or not these will differ on a board to board basis. BUG=b:148980016,b:147440290 BRANCH=None TEST=Enable on waddledoo, verify that we can charge the battery from the sub-board. Signed-off-by: Aseda Aboagye <aaboagye@google.com> Change-Id: Icd323546836fe41fa1fcc7c3b6071d822663ed05 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2135964 Tested-by: Aseda Aboagye <aaboagye@chromium.org> Reviewed-by: Diana Z <dzigterman@chromium.org> Commit-Queue: Aseda Aboagye <aaboagye@chromium.org>
-rw-r--r--common/build.mk1
-rw-r--r--common/charge_state_v2.c59
-rw-r--r--common/ocpc.c298
-rw-r--r--include/config.h8
-rw-r--r--include/ocpc.h38
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 */