summaryrefslogtreecommitdiff
path: root/common/ocpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/ocpc.c')
-rw-r--r--common/ocpc.c298
1 files changed, 298 insertions, 0 deletions
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");