summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Glass <sjg@chromium.org>2023-05-05 10:24:03 -0600
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-05-09 22:29:39 +0000
commitf53ec2533fc46bb6ceb955715b4be8d1be4cbe37 (patch)
treeb69f6bf26b6f661cd2fcf729e9852450b7684180
parent81eeda15fbe097e92c36962e7f954c4677c9b22d (diff)
downloadchrome-ec-f53ec2533fc46bb6ceb955715b4be8d1be4cbe37.tar.gz
charger: Split base code out to its own file
With the essential refactoring complete, move the base code into a new charger_base.c file, with a corresponding charger_base.h header file. The base name 'charger' was chosen since it matches the charger.h header file, even though charge_state_v2.c uses the 'charge' base name. Three variables must be exported for use by the charge_state_v2.c code: charge_base, prev_charge_base and base_responsive. For now these are made global. Future work will tidy that up. The base_connected() function is defined in the new C file, with a stub in the header for when CONFIG_EC_EC_COMM_BATTERY_CLIENT is not enabled. The hook for board_base_reset() is placed at the bottom of the new charger_base.c file. With this change we have 8 #ifdefs for CONFIG_EC_EC_COMM_BATTERY_CLIENT in charge_state_v2.c - future work will tidy those up. This makes no functional change. BUG=b:218332694 TEST=zmake build dev-posix Check size on lux: *** 69552 bytes in flash and 1152 bytes in RAM lux RO **** *** 69448 bytes in flash and 1120 bytes in RAM lux RW **** Change-Id: I2d8d03e8b07ed1b281b8729cffdb8140239b5b2b Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/4510244 Commit-Queue: Simon Glass <sjg@chromium.org> Reviewed-by: Tristan Honscheid <honscheid@google.com> Tested-by: Simon Glass <sjg@chromium.org>
-rw-r--r--common/build.mk3
-rw-r--r--common/charge_state_v2.c671
-rw-r--r--common/charger_base.c646
-rw-r--r--include/charger.h13
-rw-r--r--include/charger_base.h45
5 files changed, 711 insertions, 667 deletions
diff --git a/common/build.mk b/common/build.mk
index 0c5250cf18..bbc0789f9b 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -68,6 +68,9 @@ common-$(CONFIG_CHARGER)+=charger.o
ifneq ($(CONFIG_CHARGER),)
common-$(CONFIG_BATTERY)+=charge_state_v2.o
endif
+ifneq ($(CONFIG_EC_EC_COMM_BATTERY_CLIENT),)
+common-$(CONFIG_BATTERY)+=charger_base.o
+endif
common-$(CONFIG_CHARGER_PROFILE_OVERRIDE_COMMON)+=charger_profile_override.o
common-$(CONFIG_CMD_I2CWEDGE)+=i2c_wedge.o
common-$(CONFIG_COMMON_GPIO)+=gpio.o gpio_commands.o
diff --git a/common/charge_state_v2.c b/common/charge_state_v2.c
index 41cb86753b..a570f00ab9 100644
--- a/common/charge_state_v2.c
+++ b/common/charge_state_v2.c
@@ -11,6 +11,7 @@
#include "charge_manager.h"
#include "charge_state.h"
#include "charger.h"
+#include "charger_base.h"
#include "charger_profile_override.h"
#include "chipset.h"
#include "common.h"
@@ -73,8 +74,6 @@
static timestamp_t uvp_throttle_start_time;
#endif /* CONFIG_THROTTLE_AP_ON_BAT_OLTAGE */
-static int charge_request(bool use_curr, bool is_full);
-
static uint8_t battery_level_shutdown;
/*
@@ -123,37 +122,6 @@ STATIC_IF(CONFIG_USB_PD_PREFER_MV) int stable_current;
STATIC_IF(CONFIG_USB_PD_PREFER_MV) int desired_mw;
STATIC_IF_NOT(CONFIG_USB_PD_PREFER_MV) struct pd_pref_config_t pd_pref_config;
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
-/* Base has responded to one of our commands already. */
-static int base_responsive;
-static int charge_base;
-static int prev_charge_base;
-static int prev_current_base;
-static int prev_allow_charge_base;
-static int prev_current_lid;
-
-/*
- * In debugging mode, with AC, input current to allocate to base. Negative
- * value disables manual mode.
- */
-static int manual_ac_current_base = -1;
-/*
- * In debugging mode, when discharging, current to transfer from lid to base
- * (negative to transfer from base to lid). Only valid when enabled is true.
- */
-static int manual_noac_enabled;
-static int manual_noac_current_base;
-#endif
-
-/* Check if there is a base and it is connected */
-static bool base_connected(void)
-{
- if (IS_ENABLED(CONFIG_EC_EC_COMM_BATTERY_CLIENT))
- return board_is_base_connected();
-
- return false;
-}
-
/* Is battery connected but unresponsive after precharge? */
static int battery_seems_dead;
@@ -260,543 +228,6 @@ static bool battery_sustainer_enabled(void)
return sustain_soc.lower != -1 && sustain_soc.upper != -1;
}
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
-/*
- * Parameters for dual-battery policy.
- * TODO(b:71881017): This should be made configurable by AP in the future.
- */
-struct dual_battery_policy {
- /*** Policies when AC is not connected. ***/
- /* Voltage to use when using OTG mode between lid and base (mV) */
- uint16_t otg_voltage;
- /* Maximum current to apply from base to lid (mA) */
- uint16_t max_base_to_lid_current;
- /*
- * Margin to apply between provided OTG output current and input current
- * limit, to make sure that input charger does not overcurrent output
- * charger. input_current = (1-margin) * output_current. (/128)
- */
- uint8_t margin_otg_current;
-
- /* Only do base to lid OTG when base battery above this value (%) */
- uint8_t min_charge_base_otg;
-
- /*
- * When base/lid battery percentage is below this value, do
- * battery-to-battery charging. (%)
- */
- uint8_t max_charge_base_batt_to_batt;
- uint8_t max_charge_lid_batt_to_batt;
-
- /*** Policies when AC is connected. ***/
- /* Minimum power to allocate to base (mW), includes some margin to allow
- * base to charge when critically low.
- */
- uint16_t min_base_system_power;
-
- /* Smoothing factor for lid power (/128) */
- uint8_t lid_system_power_smooth;
- /*
- * Smoothing factor for base/lid battery power, when the battery power
- * is decreasing only: we try to estimate the maximum power that the
- * battery is willing to take and always reset it when it draws more
- * than the estimate. (/128)
- */
- uint8_t battery_power_smooth;
-
- /*
- * Margin to add to requested base/lid battery power, to figure out how
- * much current to allocate. allocation = (1+margin) * request. (/128)
- */
- uint8_t margin_base_battery_power;
- uint8_t margin_lid_battery_power;
-
- /* Maximum current to apply from lid to base (mA) */
- uint16_t max_lid_to_base_current;
-};
-
-static const struct dual_battery_policy db_policy = {
- .otg_voltage = 12000, /* mV */
- .max_base_to_lid_current = 1800, /* mA, about 2000mA with margin. */
- .margin_otg_current = 13, /* /128 = 10.1% */
- .min_charge_base_otg = 5, /* % */
- .max_charge_base_batt_to_batt = 4, /* % */
- .max_charge_lid_batt_to_batt = 10, /* % */
- .min_base_system_power = 1300, /* mW */
- .lid_system_power_smooth = 32, /* 32/128 = 0.25 */
- .battery_power_smooth = 1, /* 1/128 = 0.008 */
- .margin_base_battery_power = 32, /* 32/128 = 0.25 */
- .margin_lid_battery_power = 32, /* 32/128 = 0.25 */
- .max_lid_to_base_current = 2000, /* mA */
-};
-
-/* Add at most "value" to power_var, subtracting from total_power budget. */
-#define CHG_ALLOCATE(power_var, total_power, value) \
- do { \
- int val_capped = MIN(value, total_power); \
- (power_var) += val_capped; \
- (total_power) -= val_capped; \
- } while (0)
-
-/**
- * Setup current settings for base, and record previous values, if the base
- * is responsive.
- *
- * @param current_base Current to be drawn by base (negative to provide power)
- * @param allow_charge_base Whether base battery should be charged (only makes
- * sense with positive current)
- */
-static int set_base_current(int current_base, int allow_charge_base)
-{
- /* "OTG" voltage from base to lid. */
- const int otg_voltage = db_policy.otg_voltage;
- int ret;
-
- ret = ec_ec_client_base_charge_control(current_base, otg_voltage,
- allow_charge_base);
- if (ret) {
- /* Ignore errors until the base is responsive. */
- if (base_responsive)
- return ret;
- } else {
- base_responsive = 1;
- prev_current_base = current_base;
- prev_allow_charge_base = allow_charge_base;
- }
-
- return EC_RES_SUCCESS;
-}
-
-/**
- * Setup current settings for lid and base, in a safe way.
- *
- * @param current_base Current to be drawn by base (negative to provide power)
- * @param allow_charge_base Whether base battery should be charged (only makes
- * sense with positive current)
- * @param current_lid Current to be drawn by lid (negative to provide power)
- * @param allow_charge_lid Whether lid battery should be charged
- * @param is_full Whether the lid battery is full
- */
-static void set_base_lid_current(int current_base, int allow_charge_base,
- int current_lid, int allow_charge_lid,
- bool is_full)
-{
- /* "OTG" voltage from lid to base. */
- const int otg_voltage = db_policy.otg_voltage;
-
- int lid_first;
- int ret;
- int chgnum = 0;
-
- /* TODO(b:71881017): This is still quite verbose during charging. */
- if (prev_current_base != current_base ||
- prev_allow_charge_base != allow_charge_base ||
- prev_current_lid != current_lid) {
- CPRINTS("Base/Lid: %d%s/%d%s mA", current_base,
- allow_charge_base ? "+" : "", current_lid,
- allow_charge_lid ? "+" : "");
- }
-
- /*
- * To decide whether to first control the lid or the base, we first
- * control the side that _reduces_ current that would be drawn, then
- * setup one that would start providing power, then increase current.
- */
- if (current_lid >= 0 && current_lid < prev_current_lid)
- lid_first = 1; /* Lid decreases current */
- else if (current_base >= 0 && current_base < prev_current_base)
- lid_first = 0; /* Base decreases current */
- else if (current_lid < 0)
- lid_first = 1; /* Lid provide power */
- else
- lid_first = 0; /* All other cases: control the base first */
-
- if (!lid_first && base_connected()) {
- ret = set_base_current(current_base, allow_charge_base);
- if (ret)
- return;
- }
-
- if (current_lid >= 0) {
- ret = charge_set_output_current_limit(CHARGER_SOLO, 0, 0);
- if (ret)
- return;
- ret = charger_set_input_current_limit(chgnum, current_lid);
- if (ret)
- return;
- ret = charge_request(allow_charge_lid, is_full);
- } else {
- ret = charge_set_output_current_limit(
- CHARGER_SOLO, -current_lid, otg_voltage);
- }
-
- if (ret)
- return;
-
- prev_current_lid = current_lid;
-
- if (lid_first && base_connected()) {
- ret = set_base_current(current_base, allow_charge_base);
- if (ret)
- return;
- }
-
- /*
- * Make sure cross-power is enabled (it might not be enabled right after
- * plugging the base, or when an adapter just got connected).
- */
- if (base_connected() && current_base != 0)
- board_enable_base_power(1);
-}
-
-/**
- * Smooth power value, covering some edge cases.
- * Compute s*curr+(1-s)*prev, where s is in 1/128 unit.
- */
-static int smooth_value(int prev, int curr, int s)
-{
- if (curr < 0)
- curr = 0;
- if (prev < 0)
- return curr;
-
- return prev + s * (curr - prev) / 128;
-}
-
-/**
- * Add margin m to value. Compute (1+m)*value, where m is in 1/128 unit.
- */
-static int add_margin(int value, int m)
-{
- return value + m * value / 128;
-}
-#endif /* CONFIG_EC_EC_COMM_BATTERY_CLIENT */
-
-/* allocate power between the base and the lid */
-static void
-base_charge_allocate_input_current_limit(const struct charge_state_data *curr,
- bool is_full, bool debugging)
-{
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
- /*
- * All the power numbers are in mW.
- *
- * Since we work with current and voltage in mA and mV, multiplying them
- * gives numbers in uW, which are dangerously close to overflowing when
- * doing intermediate computations (60W * 100 overflows a 32-bit int,
- * for example). We therefore divide the product by 1000 and re-multiply
- * the power numbers by 1000 when converting them back to current.
- */
- int total_power = 0;
-
- static int prev_base_battery_power = -1;
- int base_battery_power = 0;
- int base_battery_power_max = 0;
-
- static int prev_lid_system_power = -1;
- int lid_system_power;
-
- static int prev_lid_battery_power = -1;
- int lid_battery_power = 0;
- int lid_battery_power_max = 0;
-
- int power_base = 0;
- int power_lid = 0;
-
- int current_base = 0;
- int current_lid = 0;
-
- int charge_lid = charge_get_percent();
-
- const struct ec_response_battery_dynamic_info *const base_bd =
- &battery_dynamic[BATT_IDX_BASE];
- const struct batt_params *batt = &curr->batt;
-
- if (!base_connected()) {
- set_base_lid_current(0, 0, curr->desired_input_current, 1,
- is_full);
- prev_base_battery_power = -1;
- return;
- }
-
- /* Charging */
- if (curr->desired_input_current > 0 && curr->input_voltage > 0)
- total_power = curr->desired_input_current *
- curr->input_voltage / 1000;
-
- /*
- * TODO(b:71723024): We should be able to replace this test by curr.ac,
- * but the value is currently wrong, especially during transitions.
- */
- if (total_power <= 0) {
- int base_critical =
- charge_base >= 0 &&
- charge_base < db_policy.max_charge_base_batt_to_batt;
-
- /* Discharging */
- prev_base_battery_power = -1;
- prev_lid_system_power = -1;
- prev_lid_battery_power = -1;
-
- /* Manual control */
- if (manual_noac_enabled) {
- int lid_current, base_current;
-
- if (manual_noac_current_base > 0) {
- base_current = -manual_noac_current_base;
- lid_current = add_margin(
- manual_noac_current_base,
- db_policy.margin_otg_current);
- } else {
- lid_current = manual_noac_current_base;
- base_current = add_margin(
- -manual_noac_current_base,
- db_policy.margin_otg_current);
- }
-
- set_base_lid_current(base_current, 0, lid_current, 0,
- is_full);
- return;
- }
-
- /*
- * System is off, cut power to the base. We'll reset the base
- * when system restarts, or when AC is plugged.
- */
- if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
- set_base_lid_current(0, 0, 0, 0, is_full);
- if (base_responsive) {
- /* Base still responsive, put it to sleep. */
- CPRINTF("Hibernating base\n");
- ec_ec_client_hibernate();
- base_responsive = 0;
- board_enable_base_power(0);
- }
- return;
- }
-
- /*
- * System is suspended, let the lid and base run on their
- * own power. However, if the base battery is critically low, we
- * still want to provide power to the base, to make sure it
- * stays alive to be able to wake the system on keyboard or
- * touchpad events.
- */
- if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) &&
- !base_critical) {
- set_base_lid_current(0, 0, 0, 0, is_full);
- return;
- }
-
- if (charge_base > db_policy.min_charge_base_otg) {
- int lid_current = db_policy.max_base_to_lid_current;
- int base_current = add_margin(
- lid_current, db_policy.margin_otg_current);
- /* Draw current from base to lid */
- set_base_lid_current(
- -base_current, 0, lid_current,
- charge_lid <
- db_policy.max_charge_lid_batt_to_batt,
- is_full);
- } else {
- /*
- * Base battery is too low, apply power to it, and allow
- * it to charge if it is critically low.
- *
- * TODO(b:71881017): When suspended, this will make the
- * battery charge oscillate between 3 and 4 percent,
- * which might not be great for battery life. We need
- * some hysteresis.
- */
- /*
- * TODO(b:71881017): Precompute (ideally, at build time)
- * the base_current, so we do not need to do a division
- * here.
- */
- int base_current =
- (db_policy.min_base_system_power * 1000) /
- db_policy.otg_voltage;
- int lid_current = add_margin(
- base_current, db_policy.margin_otg_current);
-
- set_base_lid_current(base_current, base_critical,
- -lid_current, 0, is_full);
- }
-
- return;
- }
-
- /* Manual control */
- if (manual_ac_current_base >= 0) {
- int current_base = manual_ac_current_base;
- int current_lid =
- curr->desired_input_current - manual_ac_current_base;
-
- if (current_lid < 0) {
- current_base = curr->desired_input_current;
- current_lid = 0;
- }
-
- set_base_lid_current(current_base, 1, current_lid, 1, is_full);
- return;
- }
-
- /* Estimate system power. */
- lid_system_power = charger_get_system_power() / 1000;
-
- /* Smooth system power, as it is very spiky */
- lid_system_power = smooth_value(prev_lid_system_power, lid_system_power,
- db_policy.lid_system_power_smooth);
- prev_lid_system_power = lid_system_power;
-
- /*
- * TODO(b:71881017): Smoothing the battery power isn't necessarily a
- * good idea: if the system takes up too much power, we may reduce the
- * estimate power too quickly, leading to oscillations when the system
- * power goes down. Instead, we should probably estimate the current
- * based on remaining capacity.
- */
- /* Estimate lid battery power. */
- if (!(batt->flags & (BATT_FLAG_BAD_VOLTAGE | BATT_FLAG_BAD_CURRENT)))
- lid_battery_power = batt->current * batt->voltage / 1000;
- if (lid_battery_power < prev_lid_battery_power)
- lid_battery_power =
- smooth_value(prev_lid_battery_power, lid_battery_power,
- db_policy.battery_power_smooth);
- if (!(batt->flags &
- (BATT_FLAG_BAD_DESIRED_VOLTAGE | BATT_FLAG_BAD_DESIRED_CURRENT)))
- lid_battery_power_max =
- batt->desired_current * batt->desired_voltage / 1000;
-
- lid_battery_power = MIN(lid_battery_power, lid_battery_power_max);
-
- /* Estimate base battery power. */
- if (!(base_bd->flags & EC_BATT_FLAG_INVALID_DATA)) {
- base_battery_power = base_bd->actual_current *
- base_bd->actual_voltage / 1000;
- base_battery_power_max = base_bd->desired_current *
- base_bd->desired_voltage / 1000;
- }
- if (base_battery_power < prev_base_battery_power)
- base_battery_power = smooth_value(
- prev_base_battery_power, base_battery_power,
- db_policy.battery_power_smooth);
- base_battery_power = MIN(base_battery_power, base_battery_power_max);
-
- if (debugging()) {
- CPRINTF("%s:\n", __func__);
- CPRINTF("total power: %d\n", total_power);
- CPRINTF("base battery power: %d (%d)\n", base_battery_power,
- base_battery_power_max);
- CPRINTF("lid system power: %d\n", lid_system_power);
- CPRINTF("lid battery power: %d\n", lid_battery_power);
- CPRINTF("percent base/lid: %d%% %d%%\n", charge_base,
- charge_lid);
- }
-
- prev_lid_battery_power = lid_battery_power;
- prev_base_battery_power = base_battery_power;
-
- if (total_power > 0) { /* Charging */
- /* Allocate system power */
- CHG_ALLOCATE(power_base, total_power,
- db_policy.min_base_system_power);
- CHG_ALLOCATE(power_lid, total_power, lid_system_power);
-
- /* Allocate lid, then base battery power */
- lid_battery_power = add_margin(
- lid_battery_power, db_policy.margin_lid_battery_power);
- CHG_ALLOCATE(power_lid, total_power, lid_battery_power);
-
- base_battery_power =
- add_margin(base_battery_power,
- db_policy.margin_base_battery_power);
- CHG_ALLOCATE(power_base, total_power, base_battery_power);
-
- /* Give everything else to the lid. */
- CHG_ALLOCATE(power_lid, total_power, total_power);
- if (debugging())
- CPRINTF("power: base %d mW / lid %d mW\n", power_base,
- power_lid);
-
- current_base = 1000 * power_base / curr->input_voltage;
- current_lid = 1000 * power_lid / curr->input_voltage;
-
- if (current_base > db_policy.max_lid_to_base_current) {
- current_lid += (current_base -
- db_policy.max_lid_to_base_current);
- current_base = db_policy.max_lid_to_base_current;
- }
-
- if (debugging())
- CPRINTF("current: base %d mA / lid %d mA\n",
- current_base, current_lid);
-
- set_base_lid_current(current_base, 1, current_lid, 1, is_full);
- } else { /* Discharging */
- }
-
- if (debugging())
- CPRINTF("====\n");
-#endif /* CONFIG_EC_EC_COMM_BATTERY_CLIENT */
-}
-
-/* Update base battery information */
-static void base_update_battery_info(void)
-{
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
- struct ec_response_battery_dynamic_info *const bd =
- &battery_dynamic[BATT_IDX_BASE];
-
- if (!base_connected()) {
- const int invalid_flags = EC_BATT_FLAG_INVALID_DATA;
- /* Invalidate static/dynamic information */
- if (bd->flags != invalid_flags) {
- bd->flags = invalid_flags;
-
- host_set_single_event(EC_HOST_EVENT_BATTERY);
- host_set_single_event(EC_HOST_EVENT_BATTERY_STATUS);
- }
- charge_base = -1;
- base_responsive = 0;
- prev_current_base = 0;
- prev_allow_charge_base = 0;
- } else if (base_responsive) {
- int old_flags = bd->flags;
- int flags_changed;
- int old_full_capacity = bd->full_capacity;
-
- ec_ec_client_base_get_dynamic_info();
- flags_changed = (old_flags != bd->flags);
- /* Fetch static information when flags change. */
- if (flags_changed)
- ec_ec_client_base_get_static_info();
-
- battery_memmap_refresh(BATT_IDX_BASE);
-
- /* Newly connected battery, or change in capacity. */
- if (old_flags & EC_BATT_FLAG_INVALID_DATA ||
- ((old_flags & EC_BATT_FLAG_BATT_PRESENT) !=
- (bd->flags & EC_BATT_FLAG_BATT_PRESENT)) ||
- old_full_capacity != bd->full_capacity)
- host_set_single_event(EC_HOST_EVENT_BATTERY);
-
- if (flags_changed)
- host_set_single_event(EC_HOST_EVENT_BATTERY_STATUS);
-
- /* Update charge_base */
- if (bd->flags & (BATT_FLAG_BAD_FULL_CAPACITY |
- BATT_FLAG_BAD_REMAINING_CAPACITY))
- charge_base = -1;
- else if (bd->full_capacity > 0)
- charge_base = 100 * bd->remaining_capacity /
- bd->full_capacity;
- else
- charge_base = 0;
- }
-#endif /* CONFIG_EC_EC_COMM_BATTERY_CLIENT */
-}
-
static const char *const state_list[] = { "idle", "discharge", "charge",
"precharge" };
BUILD_ASSERT(ARRAY_SIZE(state_list) == NUM_STATES_V2);
@@ -991,15 +422,7 @@ __overridable int board_should_charger_bypass(void)
return false;
}
-/*
- * Ask the charger for some voltage and current. If either value is 0,
- * charging is disabled; otherwise it's enabled. Negative values are ignored.
- *
- * @param use_curr Use values from requested voltage and current (otherwise use
- * 0 for both)
- * @param is_full Battery is full
- */
-static int charge_request(bool use_curr, bool is_full)
+int charge_request(bool use_curr, bool is_full)
{
int r1 = EC_SUCCESS, r2 = EC_SUCCESS, r3 = EC_SUCCESS, r4 = EC_SUCCESS;
static int prev_volt, prev_curr;
@@ -1538,8 +961,7 @@ void charger_init(void)
}
DECLARE_HOOK(HOOK_INIT, charger_init, HOOK_PRIO_DEFAULT);
-/* Wake up the task when something important happens */
-static void charge_wakeup(void)
+void charge_wakeup(void)
{
task_wake(TASK_ID_CHARGER);
}
@@ -1547,11 +969,6 @@ DECLARE_HOOK(HOOK_CHIPSET_RESUME, charge_wakeup, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_AC_CHANGE, charge_wakeup, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_POWER_SUPPLY_CHANGE, charge_wakeup, HOOK_PRIO_DEFAULT);
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
-/* Reset the base on S5->S0 transition. */
-DECLARE_HOOK(HOOK_CHIPSET_STARTUP, board_base_reset, HOOK_PRIO_DEFAULT);
-#endif
-
#ifdef CONFIG_THROTTLE_AP_ON_BAT_VOLTAGE
static void bat_low_voltage_throttle_reset(void)
{
@@ -1724,36 +1141,6 @@ static void charger_setup(const struct charger_info *info)
battery_level_shutdown = board_set_battery_level_shutdown();
}
-/*
- * Check base external-power settings and react as needed
- *
- * @param ac Current ac value from struct charge_state_data
- * @param prev_ac Previous value of ac
- * Returns true if ac should be zeroed, false to leave it along
- */
-static bool base_check_extpower(int ac, int prev_ac)
-{
- /* This #ifdef protects access to vars that are otherwise unavailable */
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
- bool zero_ac = false;
-
- /*
- * When base is powering the system, make sure ac stays 0.
- * TODO(b:71723024): Fix extpower_is_present() in hardware instead.
- */
- if (base_responsive && prev_current_base < 0) {
- ac = 0;
- zero_ac = true;
- }
-
- /* System is off: if AC gets connected, reset the base. */
- if (chipset_in_state(CHIPSET_STATE_ANY_OFF) && !prev_ac && ac)
- board_base_reset();
-
- return zero_ac;
-#endif
-}
-
/* check for and handle any state-of-charge change with the battery */
void check_battery_change_soc(bool is_full, bool prev_full)
{
@@ -1995,7 +1382,7 @@ static void adjust_requested_vi(const struct charger_info *const info,
if (IS_ENABLED(CONFIG_EC_EC_COMM_BATTERY_CLIENT)) {
base_charge_allocate_input_current_limit(&curr, is_full,
- debugging);
+ debugging());
} else {
charge_request(true, is_full);
}
@@ -2962,53 +2349,3 @@ DECLARE_CONSOLE_COMMAND(chgstate, command_chgstate,
"[idle|discharge" CHGSTATE_DEBUG_HELP "]"
"\n[sustain <lower> <upper>]",
"Get/set charge state machine status");
-
-#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
-static int command_chgdualdebug(int argc, const char **argv)
-{
- int val;
- char *e;
-
- if (argc > 1) {
- if (argv[1][0] == 'c') {
- if (argc <= 2)
- return EC_ERROR_PARAM_COUNT;
-
- if (!strcasecmp(argv[2], "auto")) {
- val = -1;
- } else {
- val = strtoi(argv[2], &e, 0);
- if (*e || val < 0)
- return EC_ERROR_PARAM2;
- }
-
- manual_ac_current_base = val;
- charge_wakeup();
- } else if (argv[1][0] == 'd') {
- if (argc <= 2)
- return EC_ERROR_PARAM_COUNT;
-
- if (!strcasecmp(argv[2], "auto")) {
- manual_noac_enabled = 0;
- } else {
- val = strtoi(argv[2], &e, 0);
- if (*e)
- return EC_ERROR_PARAM2;
- manual_noac_current_base = val;
- manual_noac_enabled = 1;
- }
- charge_wakeup();
- } else {
- return EC_ERROR_PARAM1;
- }
- } else {
- ccprintf("Base/Lid: %d%s/%d mA\n", prev_current_base,
- prev_allow_charge_base ? "+" : "", prev_current_lid);
- }
-
- return EC_SUCCESS;
-}
-DECLARE_CONSOLE_COMMAND(chgdualdebug, command_chgdualdebug,
- "[charge (auto|<current>)|discharge (auto|<current>)]",
- "Manually control dual-battery charging algorithm.");
-#endif
diff --git a/common/charger_base.c b/common/charger_base.c
new file mode 100644
index 0000000000..af76b1f51c
--- /dev/null
+++ b/common/charger_base.c
@@ -0,0 +1,646 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Battery charging task and state machine.
+ */
+
+#include "charge_state.h"
+#include "charger.h"
+#include "charger_base.h"
+#include "common.h"
+#include "console.h"
+#include "ec_ec_comm_client.h"
+#include "hooks.h"
+#include "string.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHARGER, outstr)
+#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ##args)
+#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ##args)
+
+/* Base has responded to one of our commands already. */
+int base_responsive;
+int charge_base;
+int prev_charge_base;
+static int prev_current_base;
+static int prev_allow_charge_base;
+static int prev_current_lid;
+
+/*
+ * In debugging mode, with AC, input current to allocate to base. Negative
+ * value disables manual mode.
+ */
+static int manual_ac_current_base = -1;
+/*
+ * In debugging mode, when discharging, current to transfer from lid to base
+ * (negative to transfer from base to lid). Only valid when enabled is true.
+ */
+static int manual_noac_enabled;
+static int manual_noac_current_base;
+
+/*
+ * Parameters for dual-battery policy.
+ * TODO(b:71881017): This should be made configurable by AP in the future.
+ */
+struct dual_battery_policy {
+ /*** Policies when AC is not connected. ***/
+ /* Voltage to use when using OTG mode between lid and base (mV) */
+ uint16_t otg_voltage;
+ /* Maximum current to apply from base to lid (mA) */
+ uint16_t max_base_to_lid_current;
+ /*
+ * Margin to apply between provided OTG output current and input current
+ * limit, to make sure that input charger does not overcurrent output
+ * charger. input_current = (1-margin) * output_current. (/128)
+ */
+ uint8_t margin_otg_current;
+
+ /* Only do base to lid OTG when base battery above this value (%) */
+ uint8_t min_charge_base_otg;
+
+ /*
+ * When base/lid battery percentage is below this value, do
+ * battery-to-battery charging. (%)
+ */
+ uint8_t max_charge_base_batt_to_batt;
+ uint8_t max_charge_lid_batt_to_batt;
+
+ /*** Policies when AC is connected. ***/
+ /* Minimum power to allocate to base (mW), includes some margin to allow
+ * base to charge when critically low.
+ */
+ uint16_t min_base_system_power;
+
+ /* Smoothing factor for lid power (/128) */
+ uint8_t lid_system_power_smooth;
+ /*
+ * Smoothing factor for base/lid battery power, when the battery power
+ * is decreasing only: we try to estimate the maximum power that the
+ * battery is willing to take and always reset it when it draws more
+ * than the estimate. (/128)
+ */
+ uint8_t battery_power_smooth;
+
+ /*
+ * Margin to add to requested base/lid battery power, to figure out how
+ * much current to allocate. allocation = (1+margin) * request. (/128)
+ */
+ uint8_t margin_base_battery_power;
+ uint8_t margin_lid_battery_power;
+
+ /* Maximum current to apply from lid to base (mA) */
+ uint16_t max_lid_to_base_current;
+};
+
+static const struct dual_battery_policy db_policy = {
+ .otg_voltage = 12000, /* mV */
+ .max_base_to_lid_current = 1800, /* mA, about 2000mA with margin. */
+ .margin_otg_current = 13, /* /128 = 10.1% */
+ .min_charge_base_otg = 5, /* % */
+ .max_charge_base_batt_to_batt = 4, /* % */
+ .max_charge_lid_batt_to_batt = 10, /* % */
+ .min_base_system_power = 1300, /* mW */
+ .lid_system_power_smooth = 32, /* 32/128 = 0.25 */
+ .battery_power_smooth = 1, /* 1/128 = 0.008 */
+ .margin_base_battery_power = 32, /* 32/128 = 0.25 */
+ .margin_lid_battery_power = 32, /* 32/128 = 0.25 */
+ .max_lid_to_base_current = 2000, /* mA */
+};
+
+/* Add at most "value" to power_var, subtracting from total_power budget. */
+#define CHG_ALLOCATE(power_var, total_power, value) \
+ do { \
+ int val_capped = MIN(value, total_power); \
+ (power_var) += val_capped; \
+ (total_power) -= val_capped; \
+ } while (0)
+
+/* Check if a base is connected */
+bool base_connected(void)
+{
+ return board_is_base_connected();
+}
+
+/**
+ * Setup current settings for base, and record previous values, if the base
+ * is responsive.
+ *
+ * @param current_base Current to be drawn by base (negative to provide power)
+ * @param allow_charge_base Whether base battery should be charged (only makes
+ * sense with positive current)
+ */
+static int set_base_current(int current_base, int allow_charge_base)
+{
+ /* "OTG" voltage from base to lid. */
+ const int otg_voltage = db_policy.otg_voltage;
+ int ret;
+
+ ret = ec_ec_client_base_charge_control(current_base, otg_voltage,
+ allow_charge_base);
+ if (ret) {
+ /* Ignore errors until the base is responsive. */
+ if (base_responsive)
+ return ret;
+ } else {
+ base_responsive = 1;
+ prev_current_base = current_base;
+ prev_allow_charge_base = allow_charge_base;
+ }
+
+ return EC_RES_SUCCESS;
+}
+
+/**
+ * Setup current settings for lid and base, in a safe way.
+ *
+ * @param current_base Current to be drawn by base (negative to provide power)
+ * @param allow_charge_base Whether base battery should be charged (only makes
+ * sense with positive current)
+ * @param current_lid Current to be drawn by lid (negative to provide power)
+ * @param allow_charge_lid Whether lid battery should be charged
+ * @param is_full Whether the lid battery is full
+ */
+static void set_base_lid_current(int current_base, int allow_charge_base,
+ int current_lid, int allow_charge_lid,
+ bool is_full)
+{
+ /* "OTG" voltage from lid to base. */
+ const int otg_voltage = db_policy.otg_voltage;
+
+ int lid_first;
+ int ret;
+ int chgnum = 0;
+
+ /* TODO(b:71881017): This is still quite verbose during charging. */
+ if (prev_current_base != current_base ||
+ prev_allow_charge_base != allow_charge_base ||
+ prev_current_lid != current_lid) {
+ CPRINTS("Base/Lid: %d%s/%d%s mA", current_base,
+ allow_charge_base ? "+" : "", current_lid,
+ allow_charge_lid ? "+" : "");
+ }
+
+ /*
+ * To decide whether to first control the lid or the base, we first
+ * control the side that _reduces_ current that would be drawn, then
+ * setup one that would start providing power, then increase current.
+ */
+ if (current_lid >= 0 && current_lid < prev_current_lid)
+ lid_first = 1; /* Lid decreases current */
+ else if (current_base >= 0 && current_base < prev_current_base)
+ lid_first = 0; /* Base decreases current */
+ else if (current_lid < 0)
+ lid_first = 1; /* Lid provide power */
+ else
+ lid_first = 0; /* All other cases: control the base first */
+
+ if (!lid_first && base_connected()) {
+ ret = set_base_current(current_base, allow_charge_base);
+ if (ret)
+ return;
+ }
+
+ if (current_lid >= 0) {
+ ret = charge_set_output_current_limit(CHARGER_SOLO, 0, 0);
+ if (ret)
+ return;
+ ret = charger_set_input_current_limit(chgnum, current_lid);
+ if (ret)
+ return;
+ ret = charge_request(allow_charge_lid, is_full);
+ } else {
+ ret = charge_set_output_current_limit(
+ CHARGER_SOLO, -current_lid, otg_voltage);
+ }
+
+ if (ret)
+ return;
+
+ prev_current_lid = current_lid;
+
+ if (lid_first && base_connected()) {
+ ret = set_base_current(current_base, allow_charge_base);
+ if (ret)
+ return;
+ }
+
+ /*
+ * Make sure cross-power is enabled (it might not be enabled right after
+ * plugging the base, or when an adapter just got connected).
+ */
+ if (base_connected() && current_base != 0)
+ board_enable_base_power(1);
+}
+
+/**
+ * Smooth power value, covering some edge cases.
+ * Compute s*curr+(1-s)*prev, where s is in 1/128 unit.
+ */
+static int smooth_value(int prev, int curr, int s)
+{
+ if (curr < 0)
+ curr = 0;
+ if (prev < 0)
+ return curr;
+
+ return prev + s * (curr - prev) / 128;
+}
+
+/**
+ * Add margin m to value. Compute (1+m)*value, where m is in 1/128 unit.
+ */
+static int add_margin(int value, int m)
+{
+ return value + m * value / 128;
+}
+
+void base_charge_allocate_input_current_limit(
+ const struct charge_state_data *curr, bool is_full, bool debugging)
+{
+ /*
+ * All the power numbers are in mW.
+ *
+ * Since we work with current and voltage in mA and mV, multiplying them
+ * gives numbers in uW, which are dangerously close to overflowing when
+ * doing intermediate computations (60W * 100 overflows a 32-bit int,
+ * for example). We therefore divide the product by 1000 and re-multiply
+ * the power numbers by 1000 when converting them back to current.
+ */
+ int total_power = 0;
+
+ static int prev_base_battery_power = -1;
+ int base_battery_power = 0;
+ int base_battery_power_max = 0;
+
+ static int prev_lid_system_power = -1;
+ int lid_system_power;
+
+ static int prev_lid_battery_power = -1;
+ int lid_battery_power = 0;
+ int lid_battery_power_max = 0;
+
+ int power_base = 0;
+ int power_lid = 0;
+
+ int current_base = 0;
+ int current_lid = 0;
+
+ int charge_lid = charge_get_percent();
+
+ const struct ec_response_battery_dynamic_info *const base_bd =
+ &battery_dynamic[BATT_IDX_BASE];
+ const struct batt_params *batt = &curr->batt;
+
+ if (!base_connected()) {
+ set_base_lid_current(0, 0, curr->desired_input_current, 1,
+ is_full);
+ prev_base_battery_power = -1;
+ return;
+ }
+
+ /* Charging */
+ if (curr->desired_input_current > 0 && curr->input_voltage > 0)
+ total_power = curr->desired_input_current *
+ curr->input_voltage / 1000;
+
+ /*
+ * TODO(b:71723024): We should be able to replace this test by curr.ac,
+ * but the value is currently wrong, especially during transitions.
+ */
+ if (total_power <= 0) {
+ int base_critical =
+ charge_base >= 0 &&
+ charge_base < db_policy.max_charge_base_batt_to_batt;
+
+ /* Discharging */
+ prev_base_battery_power = -1;
+ prev_lid_system_power = -1;
+ prev_lid_battery_power = -1;
+
+ /* Manual control */
+ if (manual_noac_enabled) {
+ int lid_current, base_current;
+
+ if (manual_noac_current_base > 0) {
+ base_current = -manual_noac_current_base;
+ lid_current = add_margin(
+ manual_noac_current_base,
+ db_policy.margin_otg_current);
+ } else {
+ lid_current = manual_noac_current_base;
+ base_current = add_margin(
+ -manual_noac_current_base,
+ db_policy.margin_otg_current);
+ }
+
+ set_base_lid_current(base_current, 0, lid_current, 0,
+ is_full);
+ return;
+ }
+
+ /*
+ * System is off, cut power to the base. We'll reset the base
+ * when system restarts, or when AC is plugged.
+ */
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
+ set_base_lid_current(0, 0, 0, 0, is_full);
+ if (base_responsive) {
+ /* Base still responsive, put it to sleep. */
+ CPRINTF("Hibernating base\n");
+ ec_ec_client_hibernate();
+ base_responsive = 0;
+ board_enable_base_power(0);
+ }
+ return;
+ }
+
+ /*
+ * System is suspended, let the lid and base run on their
+ * own power. However, if the base battery is critically low, we
+ * still want to provide power to the base, to make sure it
+ * stays alive to be able to wake the system on keyboard or
+ * touchpad events.
+ */
+ if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) &&
+ !base_critical) {
+ set_base_lid_current(0, 0, 0, 0, is_full);
+ return;
+ }
+
+ if (charge_base > db_policy.min_charge_base_otg) {
+ int lid_current = db_policy.max_base_to_lid_current;
+ int base_current = add_margin(
+ lid_current, db_policy.margin_otg_current);
+ /* Draw current from base to lid */
+ set_base_lid_current(
+ -base_current, 0, lid_current,
+ charge_lid <
+ db_policy.max_charge_lid_batt_to_batt,
+ is_full);
+ } else {
+ /*
+ * Base battery is too low, apply power to it, and allow
+ * it to charge if it is critically low.
+ *
+ * TODO(b:71881017): When suspended, this will make the
+ * battery charge oscillate between 3 and 4 percent,
+ * which might not be great for battery life. We need
+ * some hysteresis.
+ */
+ /*
+ * TODO(b:71881017): Precompute (ideally, at build time)
+ * the base_current, so we do not need to do a division
+ * here.
+ */
+ int base_current =
+ (db_policy.min_base_system_power * 1000) /
+ db_policy.otg_voltage;
+ int lid_current = add_margin(
+ base_current, db_policy.margin_otg_current);
+
+ set_base_lid_current(base_current, base_critical,
+ -lid_current, 0, is_full);
+ }
+
+ return;
+ }
+
+ /* Manual control */
+ if (manual_ac_current_base >= 0) {
+ int current_base = manual_ac_current_base;
+ int current_lid =
+ curr->desired_input_current - manual_ac_current_base;
+
+ if (current_lid < 0) {
+ current_base = curr->desired_input_current;
+ current_lid = 0;
+ }
+
+ set_base_lid_current(current_base, 1, current_lid, 1, is_full);
+ return;
+ }
+
+ /* Estimate system power. */
+ lid_system_power = charger_get_system_power() / 1000;
+
+ /* Smooth system power, as it is very spiky */
+ lid_system_power = smooth_value(prev_lid_system_power, lid_system_power,
+ db_policy.lid_system_power_smooth);
+ prev_lid_system_power = lid_system_power;
+
+ /*
+ * TODO(b:71881017): Smoothing the battery power isn't necessarily a
+ * good idea: if the system takes up too much power, we may reduce the
+ * estimate power too quickly, leading to oscillations when the system
+ * power goes down. Instead, we should probably estimate the current
+ * based on remaining capacity.
+ */
+ /* Estimate lid battery power. */
+ if (!(batt->flags & (BATT_FLAG_BAD_VOLTAGE | BATT_FLAG_BAD_CURRENT)))
+ lid_battery_power = batt->current * batt->voltage / 1000;
+ if (lid_battery_power < prev_lid_battery_power)
+ lid_battery_power =
+ smooth_value(prev_lid_battery_power, lid_battery_power,
+ db_policy.battery_power_smooth);
+ if (!(batt->flags &
+ (BATT_FLAG_BAD_DESIRED_VOLTAGE | BATT_FLAG_BAD_DESIRED_CURRENT)))
+ lid_battery_power_max =
+ batt->desired_current * batt->desired_voltage / 1000;
+
+ lid_battery_power = MIN(lid_battery_power, lid_battery_power_max);
+
+ /* Estimate base battery power. */
+ if (!(base_bd->flags & EC_BATT_FLAG_INVALID_DATA)) {
+ base_battery_power = base_bd->actual_current *
+ base_bd->actual_voltage / 1000;
+ base_battery_power_max = base_bd->desired_current *
+ base_bd->desired_voltage / 1000;
+ }
+ if (base_battery_power < prev_base_battery_power)
+ base_battery_power = smooth_value(
+ prev_base_battery_power, base_battery_power,
+ db_policy.battery_power_smooth);
+ base_battery_power = MIN(base_battery_power, base_battery_power_max);
+
+ if (debugging) {
+ CPRINTF("%s:\n", __func__);
+ CPRINTF("total power: %d\n", total_power);
+ CPRINTF("base battery power: %d (%d)\n", base_battery_power,
+ base_battery_power_max);
+ CPRINTF("lid system power: %d\n", lid_system_power);
+ CPRINTF("lid battery power: %d\n", lid_battery_power);
+ CPRINTF("percent base/lid: %d%% %d%%\n", charge_base,
+ charge_lid);
+ }
+
+ prev_lid_battery_power = lid_battery_power;
+ prev_base_battery_power = base_battery_power;
+
+ if (total_power > 0) { /* Charging */
+ /* Allocate system power */
+ CHG_ALLOCATE(power_base, total_power,
+ db_policy.min_base_system_power);
+ CHG_ALLOCATE(power_lid, total_power, lid_system_power);
+
+ /* Allocate lid, then base battery power */
+ lid_battery_power = add_margin(
+ lid_battery_power, db_policy.margin_lid_battery_power);
+ CHG_ALLOCATE(power_lid, total_power, lid_battery_power);
+
+ base_battery_power =
+ add_margin(base_battery_power,
+ db_policy.margin_base_battery_power);
+ CHG_ALLOCATE(power_base, total_power, base_battery_power);
+
+ /* Give everything else to the lid. */
+ CHG_ALLOCATE(power_lid, total_power, total_power);
+ if (debugging)
+ CPRINTF("power: base %d mW / lid %d mW\n", power_base,
+ power_lid);
+
+ current_base = 1000 * power_base / curr->input_voltage;
+ current_lid = 1000 * power_lid / curr->input_voltage;
+
+ if (current_base > db_policy.max_lid_to_base_current) {
+ current_lid += (current_base -
+ db_policy.max_lid_to_base_current);
+ current_base = db_policy.max_lid_to_base_current;
+ }
+
+ if (debugging)
+ CPRINTF("current: base %d mA / lid %d mA\n",
+ current_base, current_lid);
+
+ set_base_lid_current(current_base, 1, current_lid, 1, is_full);
+ } else { /* Discharging */
+ }
+
+ if (debugging)
+ CPRINTF("====\n");
+}
+
+void base_update_battery_info(void)
+{
+ struct ec_response_battery_dynamic_info *const bd =
+ &battery_dynamic[BATT_IDX_BASE];
+
+ if (!base_connected()) {
+ const int invalid_flags = EC_BATT_FLAG_INVALID_DATA;
+ /* Invalidate static/dynamic information */
+ if (bd->flags != invalid_flags) {
+ bd->flags = invalid_flags;
+
+ host_set_single_event(EC_HOST_EVENT_BATTERY);
+ host_set_single_event(EC_HOST_EVENT_BATTERY_STATUS);
+ }
+ charge_base = -1;
+ base_responsive = 0;
+ prev_current_base = 0;
+ prev_allow_charge_base = 0;
+ } else if (base_responsive) {
+ int old_flags = bd->flags;
+ int flags_changed;
+ int old_full_capacity = bd->full_capacity;
+
+ ec_ec_client_base_get_dynamic_info();
+ flags_changed = (old_flags != bd->flags);
+ /* Fetch static information when flags change. */
+ if (flags_changed)
+ ec_ec_client_base_get_static_info();
+
+ battery_memmap_refresh(BATT_IDX_BASE);
+
+ /* Newly connected battery, or change in capacity. */
+ if (old_flags & EC_BATT_FLAG_INVALID_DATA ||
+ ((old_flags & EC_BATT_FLAG_BATT_PRESENT) !=
+ (bd->flags & EC_BATT_FLAG_BATT_PRESENT)) ||
+ old_full_capacity != bd->full_capacity)
+ host_set_single_event(EC_HOST_EVENT_BATTERY);
+
+ if (flags_changed)
+ host_set_single_event(EC_HOST_EVENT_BATTERY_STATUS);
+
+ /* Update charge_base */
+ if (bd->flags & (BATT_FLAG_BAD_FULL_CAPACITY |
+ BATT_FLAG_BAD_REMAINING_CAPACITY))
+ charge_base = -1;
+ else if (bd->full_capacity > 0)
+ charge_base = 100 * bd->remaining_capacity /
+ bd->full_capacity;
+ else
+ charge_base = 0;
+ }
+}
+
+bool base_check_extpower(int ac, int prev_ac)
+{
+ bool zero_ac = false;
+
+ /*
+ * When base is powering the system, make sure ac stays 0.
+ * TODO(b:71723024): Fix extpower_is_present() in hardware instead.
+ */
+ if (base_responsive && prev_current_base < 0) {
+ ac = 0;
+ zero_ac = true;
+ }
+
+ /* System is off: if AC gets connected, reset the base. */
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF) && !prev_ac && ac)
+ board_base_reset();
+
+ return zero_ac;
+}
+
+static int command_chgdualdebug(int argc, const char **argv)
+{
+ int val;
+ char *e;
+
+ if (argc > 1) {
+ if (argv[1][0] == 'c') {
+ if (argc <= 2)
+ return EC_ERROR_PARAM_COUNT;
+
+ if (!strcasecmp(argv[2], "auto")) {
+ val = -1;
+ } else {
+ val = strtoi(argv[2], &e, 0);
+ if (*e || val < 0)
+ return EC_ERROR_PARAM2;
+ }
+
+ manual_ac_current_base = val;
+ charge_wakeup();
+ } else if (argv[1][0] == 'd') {
+ if (argc <= 2)
+ return EC_ERROR_PARAM_COUNT;
+
+ if (!strcasecmp(argv[2], "auto")) {
+ manual_noac_enabled = 0;
+ } else {
+ val = strtoi(argv[2], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM2;
+ manual_noac_current_base = val;
+ manual_noac_enabled = 1;
+ }
+ charge_wakeup();
+ } else {
+ return EC_ERROR_PARAM1;
+ }
+ } else {
+ ccprintf("Base/Lid: %d%s/%d mA\n", prev_current_base,
+ prev_allow_charge_base ? "+" : "", prev_current_lid);
+ }
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(chgdualdebug, command_chgdualdebug,
+ "[charge (auto|<current>)|discharge (auto|<current>)]",
+ "Manually control dual-battery charging algorithm.");
+
+/* Reset the base on S5->S0 transition. */
+DECLARE_HOOK(HOOK_CHIPSET_STARTUP, board_base_reset, HOOK_PRIO_DEFAULT);
diff --git a/include/charger.h b/include/charger.h
index 468d267bb8..0f5a9b3e7c 100644
--- a/include/charger.h
+++ b/include/charger.h
@@ -435,4 +435,17 @@ void print_charger_prochot(int chgnum);
*/
int charger_get_min_bat_pct_for_power_on(void);
+/* Wake up the task when something important happens */
+void charge_wakeup(void);
+
+/*
+ * Ask the charger for some voltage and current. If either value is 0,
+ * charging is disabled; otherwise it's enabled. Negative values are ignored.
+ *
+ * @param use_curr Use values from requested voltage and current (otherwise use
+ * 0 for both)
+ * @param is_full Battery is full
+ */
+int charge_request(bool use_curr, bool is_full);
+
#endif /* __CROS_EC_CHARGER_H */
diff --git a/include/charger_base.h b/include/charger_base.h
new file mode 100644
index 0000000000..2f9a589e05
--- /dev/null
+++ b/include/charger_base.h
@@ -0,0 +1,45 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Charger functions related to a connected keyboard called a 'base' */
+
+#ifndef __CROS_EC_CHARGER_BASE_H
+#define __CROS_EC_CHARGER_BASE_H
+
+#include <stdbool.h>
+
+struct charge_state_data;
+
+extern int base_responsive;
+extern int charge_base;
+extern int prev_charge_base;
+
+/* allocate power between the base and the lid */
+void base_charge_allocate_input_current_limit(
+ const struct charge_state_data *curr, bool is_full, bool debugging);
+
+/*
+ * Check base external-power settings and react as needed
+ *
+ * @param ac Current ac value from struct charge_state_data
+ * @param prev_ac Previous value of ac
+ * Returns true if ac should be zeroed, false to leave it along
+ */
+bool base_check_extpower(int ac, int prev_ac);
+
+/* Update base battery information */
+void base_update_battery_info(void);
+
+#ifdef CONFIG_EC_EC_COMM_BATTERY_CLIENT
+/* Check if there is a base and it is connected */
+bool base_connected(void);
+#else
+bool base_connected(void)
+{
+ return false;
+}
+#endif
+
+#endif /* __CROS_EC_CHARGER_BASE_H */