summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 */