mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-30 18:41:11 +00:00
To avoid issues where adapter would drive against OTG of lid or
base, and to make sure that we do not over-current the adapter,
we disconnect the base/lid power transfer whenever a new
adapter is connected.
We reenable power transfer as needed.
We also separate out base current control as a new function,
that allows us to record the previous base current only when
the base charge control command is successful, and ignore
errors until the base is responsive for the first time.
Finally, we make sure that
charge_allocate_input_current_limit is only called from a
single location in charger_task.
BRANCH=none
BUG=b:71881017
TEST=Plug/unplug base, reset lux EC, connect charger.
Base is detected, power allocation works as expected.
Change-Id: I8b206d5b0fbcf0fe868b56a0336745aebe2a6dc2
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/880021
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2165 lines
62 KiB
C
2165 lines
62 KiB
C
/* Copyright (c) 2014 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.
|
|
*
|
|
* Battery charging task and state machine.
|
|
*/
|
|
|
|
#include "battery.h"
|
|
#include "battery_smart.h"
|
|
#include "charge_manager.h"
|
|
#include "charger_profile_override.h"
|
|
#include "charge_state.h"
|
|
#include "charger.h"
|
|
#include "chipset.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "ec_ec_comm_master.h"
|
|
#include "ec_ec_comm_slave.h"
|
|
#include "extpower.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "i2c.h"
|
|
#include "math_util.h"
|
|
#include "printf.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.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)
|
|
|
|
/* Extra debugging prints when allocating power between lid and base. */
|
|
#undef CHARGE_ALLOCATE_EXTRA_DEBUG
|
|
|
|
#define CRITICAL_BATTERY_SHUTDOWN_TIMEOUT_US \
|
|
(CONFIG_BATTERY_CRITICAL_SHUTDOWN_TIMEOUT * SECOND)
|
|
#define PRECHARGE_TIMEOUT_US (PRECHARGE_TIMEOUT * SECOND)
|
|
#define LFCC_EVENT_THRESH 5 /* Full-capacity change reqd for host event */
|
|
|
|
/* Prior to negotiating PD, most PD chargers advertise 15W */
|
|
#define LIKELY_PD_USBC_POWER_MW 15000
|
|
|
|
static int charge_request(int voltage, int current);
|
|
|
|
/*
|
|
* State for charger_task(). Here so we can reset it on a HOOK_INIT, and
|
|
* because stack space is more limited than .bss
|
|
*/
|
|
static const struct battery_info *batt_info;
|
|
static struct charge_state_data curr;
|
|
static int prev_ac, prev_charge, prev_full;
|
|
static enum battery_present prev_bp;
|
|
static int is_full; /* battery not accepting current */
|
|
static enum ec_charge_control_mode chg_ctl_mode;
|
|
static int manual_mode; /* volt/curr are no longer maintained by charger */
|
|
static unsigned int user_current_limit = -1U;
|
|
test_export_static timestamp_t shutdown_warning_time;
|
|
static timestamp_t precharge_start_time;
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
static int base_connected;
|
|
/* 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;
|
|
#else
|
|
static const int base_connected;
|
|
#endif
|
|
|
|
/* Is battery connected but unresponsive after precharge? */
|
|
static int battery_seems_to_be_dead;
|
|
|
|
static int battery_seems_to_be_disconnected;
|
|
|
|
/*
|
|
* Was battery removed? Set when we see BP_NO, cleared after the battery is
|
|
* reattached and becomes responsive. Used to indicate an error state after
|
|
* removal and trigger re-reading the battery static info when battery is
|
|
* reattached and responsive.
|
|
*/
|
|
static int battery_was_removed;
|
|
|
|
static int problems_exist;
|
|
static int debugging;
|
|
|
|
|
|
/* Track problems in communicating with the battery or charger */
|
|
enum problem_type {
|
|
PR_STATIC_UPDATE,
|
|
PR_SET_VOLTAGE,
|
|
PR_SET_CURRENT,
|
|
PR_SET_MODE,
|
|
PR_SET_INPUT_CURR,
|
|
PR_POST_INIT,
|
|
PR_CHG_FLAGS,
|
|
PR_BATT_FLAGS,
|
|
PR_CUSTOM,
|
|
|
|
NUM_PROBLEM_TYPES
|
|
};
|
|
static const char * const prob_text[] = {
|
|
"static update",
|
|
"set voltage",
|
|
"set current",
|
|
"set mode",
|
|
"set input current",
|
|
"post init",
|
|
"chg params",
|
|
"batt params",
|
|
"custom profile",
|
|
};
|
|
BUILD_ASSERT(ARRAY_SIZE(prob_text) == NUM_PROBLEM_TYPES);
|
|
|
|
/*
|
|
* TODO(crosbug.com/p/27639): When do we decide a problem is real and not
|
|
* just intermittent? And what do we do about it?
|
|
*/
|
|
static void problem(enum problem_type p, int v)
|
|
{
|
|
static int __bss_slow last_prob_val[NUM_PROBLEM_TYPES];
|
|
static timestamp_t __bss_slow last_prob_time[NUM_PROBLEM_TYPES];
|
|
timestamp_t t_now, t_diff;
|
|
|
|
if (last_prob_val[p] != v) {
|
|
t_now = get_time();
|
|
t_diff.val = t_now.val - last_prob_time[p].val;
|
|
CPRINTS("charge problem: %s, 0x%x -> 0x%x after %.6lds",
|
|
prob_text[p], last_prob_val[p], v, t_diff.val);
|
|
last_prob_val[p] = v;
|
|
last_prob_time[p] = t_now;
|
|
}
|
|
problems_exist = 1;
|
|
}
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
/*
|
|
* 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;
|
|
/* When base battery is low, current to provide from lid to base (mA) */
|
|
uint16_t lid_to_base_current_charge_base_low;
|
|
/*
|
|
* 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) */
|
|
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 = 15000, /* mV */
|
|
.max_base_to_lid_current = 1800, /* mA, about 2000mA with margin. */
|
|
.lid_to_base_current_charge_base_low = 200, /* mA, so about 3W. */
|
|
.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 = 1100, /* 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)
|
|
|
|
static int charge_get_base_percent(void)
|
|
{
|
|
if (base_battery_dynamic.flags & (BATT_FLAG_BAD_FULL_CAPACITY |
|
|
BATT_FLAG_BAD_REMAINING_CAPACITY))
|
|
return -1;
|
|
|
|
if (base_battery_dynamic.full_capacity > 0)
|
|
return 100 * base_battery_dynamic.remaining_capacity
|
|
/ base_battery_dynamic.full_capacity;
|
|
|
|
return 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_master_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
|
|
*/
|
|
static void set_base_lid_current(int current_base, int allow_charge_base,
|
|
int current_lid, int allow_charge_lid)
|
|
{
|
|
/* "OTG" voltage from lid to base. */
|
|
const int otg_voltage = db_policy.otg_voltage;
|
|
|
|
int lid_first;
|
|
int ret;
|
|
|
|
/* 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(0, 0);
|
|
if (ret)
|
|
return;
|
|
ret = charger_set_input_current(current_lid);
|
|
if (ret)
|
|
return;
|
|
if (allow_charge_lid)
|
|
ret = charge_request(curr.requested_voltage,
|
|
curr.requested_current);
|
|
else
|
|
ret = charge_request(0, 0);
|
|
} else {
|
|
ret = charge_set_output_current_limit(
|
|
-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)
|
|
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;
|
|
}
|
|
|
|
static void charge_allocate_input_current_limit(void)
|
|
{
|
|
/*
|
|
* 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();
|
|
|
|
if (!base_connected) {
|
|
set_base_lid_current(0, 0, curr.desired_input_current, 1);
|
|
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) {
|
|
/* Discharging */
|
|
prev_base_battery_power = -1;
|
|
prev_lid_system_power = -1;
|
|
prev_lid_battery_power = -1;
|
|
|
|
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);
|
|
} else {
|
|
/*
|
|
* Base battery is too low, apply power to it, and allow
|
|
* it to charge if it connected, and it is critically
|
|
* low.
|
|
*
|
|
* TODO(b:71881017): This will make the battery charge
|
|
* oscillate between 3 and 4 percent, which might not be
|
|
* great for battery life. We need some hysteresis.
|
|
*/
|
|
int base_current = db_policy.min_base_system_power;
|
|
int lid_current = add_margin(base_current,
|
|
db_policy.margin_otg_current);
|
|
|
|
int allow_charge = charge_base >= 0 &&
|
|
charge_base < db_policy.max_charge_base_batt_to_batt;
|
|
|
|
set_base_lid_current(base_current, allow_charge,
|
|
-lid_current, 0);
|
|
}
|
|
|
|
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 (!(curr.batt.flags &
|
|
(BATT_FLAG_BAD_VOLTAGE | BATT_FLAG_BAD_CURRENT)))
|
|
lid_battery_power = curr.batt.current *
|
|
curr.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 (!(curr.batt.flags &
|
|
(BATT_FLAG_BAD_DESIRED_VOLTAGE |
|
|
BATT_FLAG_BAD_DESIRED_CURRENT)))
|
|
lid_battery_power_max = curr.batt.desired_current *
|
|
curr.batt.desired_voltage / 1000;
|
|
|
|
lid_battery_power = MIN(lid_battery_power, lid_battery_power_max);
|
|
|
|
/* Estimate base battery power. */
|
|
if (!(base_battery_dynamic.flags & EC_BATT_FLAG_INVALID_DATA)) {
|
|
base_battery_power = base_battery_dynamic.actual_current *
|
|
base_battery_dynamic.actual_voltage / 1000;
|
|
base_battery_power_max = base_battery_dynamic.desired_current *
|
|
base_battery_dynamic.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);
|
|
} else { /* Discharging */
|
|
}
|
|
|
|
if (debugging)
|
|
CPRINTF("====\n");
|
|
}
|
|
#endif /* CONFIG_EC_EC_COMM_BATTERY_MASTER */
|
|
|
|
#ifdef HAS_TASK_HOSTCMD
|
|
/* Returns zero if every item was updated. */
|
|
static int update_static_battery_info(void)
|
|
{
|
|
char *batt_str;
|
|
int batt_serial;
|
|
/*
|
|
* The return values have type enum ec_error_list, but EC_SUCCESS is
|
|
* zero. We'll just look for any failures so we can try them all again.
|
|
*/
|
|
int rv;
|
|
|
|
/* Smart battery serial number is 16 bits */
|
|
batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_SERIAL);
|
|
memset(batt_str, 0, EC_MEMMAP_TEXT_MAX);
|
|
rv = battery_serial_number(&batt_serial);
|
|
if (!rv)
|
|
snprintf(batt_str, EC_MEMMAP_TEXT_MAX, "%04X", batt_serial);
|
|
|
|
/* Design Capacity of Full */
|
|
rv |= battery_design_capacity(
|
|
(int *)host_get_memmap(EC_MEMMAP_BATT_DCAP));
|
|
|
|
/* Design Voltage */
|
|
rv |= battery_design_voltage(
|
|
(int *)host_get_memmap(EC_MEMMAP_BATT_DVLT));
|
|
|
|
/* Last Full Charge Capacity (this is only mostly static) */
|
|
rv |= battery_full_charge_capacity(
|
|
(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC));
|
|
|
|
/* Cycle Count */
|
|
rv |= battery_cycle_count((int *)host_get_memmap(EC_MEMMAP_BATT_CCNT));
|
|
|
|
/* Battery Manufacturer string */
|
|
batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MFGR);
|
|
memset(batt_str, 0, EC_MEMMAP_TEXT_MAX);
|
|
rv |= battery_manufacturer_name(batt_str, EC_MEMMAP_TEXT_MAX);
|
|
|
|
/* Battery Model string */
|
|
batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MODEL);
|
|
memset(batt_str, 0, EC_MEMMAP_TEXT_MAX);
|
|
rv |= battery_device_name(batt_str, EC_MEMMAP_TEXT_MAX);
|
|
|
|
/* Battery Type string */
|
|
batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_TYPE);
|
|
rv |= battery_device_chemistry(batt_str, EC_MEMMAP_TEXT_MAX);
|
|
|
|
/* Zero the dynamic entries. They'll come next. */
|
|
*(int *)host_get_memmap(EC_MEMMAP_BATT_VOLT) = 0;
|
|
*(int *)host_get_memmap(EC_MEMMAP_BATT_RATE) = 0;
|
|
*(int *)host_get_memmap(EC_MEMMAP_BATT_CAP) = 0;
|
|
*(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC) = 0;
|
|
*host_get_memmap(EC_MEMMAP_BATT_FLAG) = 0;
|
|
|
|
if (rv)
|
|
problem(PR_STATIC_UPDATE, rv);
|
|
else
|
|
/* No errors seen. Battery data is now present */
|
|
*host_get_memmap(EC_MEMMAP_BATTERY_VERSION) = 1;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void update_dynamic_battery_info(void)
|
|
{
|
|
/* The memmap address is constant. We should fix these calls somehow. */
|
|
int *memmap_volt = (int *)host_get_memmap(EC_MEMMAP_BATT_VOLT);
|
|
int *memmap_rate = (int *)host_get_memmap(EC_MEMMAP_BATT_RATE);
|
|
int *memmap_cap = (int *)host_get_memmap(EC_MEMMAP_BATT_CAP);
|
|
int *memmap_lfcc = (int *)host_get_memmap(EC_MEMMAP_BATT_LFCC);
|
|
uint8_t *memmap_flags = host_get_memmap(EC_MEMMAP_BATT_FLAG);
|
|
uint8_t tmp;
|
|
int send_batt_status_event = 0;
|
|
int send_batt_info_event = 0;
|
|
static int __bss_slow batt_present;
|
|
|
|
tmp = 0;
|
|
if (curr.ac)
|
|
tmp |= EC_BATT_FLAG_AC_PRESENT;
|
|
|
|
if (curr.batt.is_present == BP_YES) {
|
|
tmp |= EC_BATT_FLAG_BATT_PRESENT;
|
|
batt_present = 1;
|
|
/* Tell the AP to read battery info if it is newly present. */
|
|
if (!(*memmap_flags & EC_BATT_FLAG_BATT_PRESENT))
|
|
send_batt_info_event++;
|
|
} else {
|
|
/*
|
|
* Require two consecutive updates with BP_NOT_SURE
|
|
* before reporting it gone to the host.
|
|
*/
|
|
if (batt_present)
|
|
tmp |= EC_BATT_FLAG_BATT_PRESENT;
|
|
else if (*memmap_flags & EC_BATT_FLAG_BATT_PRESENT)
|
|
send_batt_info_event++;
|
|
batt_present = 0;
|
|
}
|
|
|
|
if (curr.batt.flags & EC_BATT_FLAG_INVALID_DATA)
|
|
tmp |= EC_BATT_FLAG_INVALID_DATA;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_VOLTAGE))
|
|
*memmap_volt = curr.batt.voltage;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_CURRENT))
|
|
*memmap_rate = ABS(curr.batt.current);
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_REMAINING_CAPACITY)) {
|
|
/*
|
|
* If we're running off the battery, it must have some charge.
|
|
* Don't report zero charge, as that has special meaning
|
|
* to Chrome OS powerd.
|
|
*/
|
|
if (curr.batt.remaining_capacity == 0 && !curr.batt_is_charging)
|
|
*memmap_cap = 1;
|
|
else
|
|
*memmap_cap = curr.batt.remaining_capacity;
|
|
}
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_FULL_CAPACITY) &&
|
|
(curr.batt.full_capacity <= (*memmap_lfcc - LFCC_EVENT_THRESH) ||
|
|
curr.batt.full_capacity >= (*memmap_lfcc + LFCC_EVENT_THRESH))) {
|
|
*memmap_lfcc = curr.batt.full_capacity;
|
|
/* Poke the AP if the full_capacity changes. */
|
|
send_batt_info_event++;
|
|
}
|
|
|
|
if (curr.batt.is_present == BP_YES &&
|
|
!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
|
|
curr.batt.state_of_charge <= BATTERY_LEVEL_CRITICAL)
|
|
tmp |= EC_BATT_FLAG_LEVEL_CRITICAL;
|
|
|
|
tmp |= curr.batt_is_charging ? EC_BATT_FLAG_CHARGING :
|
|
EC_BATT_FLAG_DISCHARGING;
|
|
|
|
/* Tell the AP to re-read battery status if charge state changes */
|
|
if (*memmap_flags != tmp)
|
|
send_batt_status_event++;
|
|
|
|
/* Update flags before sending host events. */
|
|
*memmap_flags = tmp;
|
|
|
|
if (send_batt_info_event)
|
|
host_set_single_event(EC_HOST_EVENT_BATTERY);
|
|
if (send_batt_status_event)
|
|
host_set_single_event(EC_HOST_EVENT_BATTERY_STATUS);
|
|
}
|
|
#elif defined(CONFIG_EC_EC_COMM_BATTERY_SLAVE)
|
|
/*
|
|
* TODO(b:65697620): Make this depend on a separate config option instead, so
|
|
* that, even on systems that would typically use memmap, we can decide to use
|
|
* these structures, and pass battery information via a host command instead.
|
|
*/
|
|
static int update_static_battery_info(void)
|
|
{
|
|
int batt_serial;
|
|
int val;
|
|
/*
|
|
* The return values have type enum ec_error_list, but EC_SUCCESS is
|
|
* zero. We'll just look for any failures so we can try them all again.
|
|
*/
|
|
int rv, ret;
|
|
|
|
/* Clear all static information. */
|
|
memset(&base_battery_static, 0, sizeof(base_battery_static));
|
|
|
|
/* Smart battery serial number is 16 bits */
|
|
rv = battery_serial_number(&batt_serial);
|
|
if (!rv)
|
|
snprintf(base_battery_static.serial,
|
|
sizeof(base_battery_static.serial),
|
|
"%04X", batt_serial);
|
|
|
|
/* Design Capacity of Full */
|
|
ret = battery_design_capacity(&val);
|
|
if (!ret)
|
|
base_battery_static.design_capacity = val;
|
|
rv |= ret;
|
|
|
|
/* Design Voltage */
|
|
ret = battery_design_voltage(&val);
|
|
if (!ret)
|
|
base_battery_static.design_voltage = val;
|
|
rv |= ret;
|
|
|
|
/* Cycle Count */
|
|
ret = battery_cycle_count(&val);
|
|
if (!ret)
|
|
base_battery_static.cycle_count = val;
|
|
rv |= ret;
|
|
|
|
/* Battery Manufacturer string */
|
|
rv |= battery_manufacturer_name(base_battery_static.manufacturer,
|
|
sizeof(base_battery_static.manufacturer));
|
|
|
|
/* Battery Model string */
|
|
rv |= battery_device_name(base_battery_static.model,
|
|
sizeof(base_battery_static.model));
|
|
|
|
/* Battery Type string */
|
|
rv |= battery_device_chemistry(base_battery_static.type,
|
|
sizeof(base_battery_static.type));
|
|
|
|
/* Zero the dynamic entries. They'll come next. */
|
|
memset(&base_battery_dynamic, 0, sizeof(base_battery_dynamic));
|
|
|
|
if (rv)
|
|
problem(PR_STATIC_UPDATE, rv);
|
|
else
|
|
; /*
|
|
* TODO(b:65697620): Do we need to set a flag to indicate that
|
|
* the information is now valid?
|
|
*/
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void update_dynamic_battery_info(void)
|
|
{
|
|
static int __bss_slow batt_present;
|
|
uint8_t tmp;
|
|
|
|
tmp = 0;
|
|
if (curr.ac)
|
|
tmp |= EC_BATT_FLAG_AC_PRESENT;
|
|
|
|
if (curr.batt.is_present == BP_YES) {
|
|
tmp |= EC_BATT_FLAG_BATT_PRESENT;
|
|
batt_present = 1;
|
|
} else {
|
|
/*
|
|
* Require two consecutive updates with BP_NOT_SURE
|
|
* before reporting it gone to the host.
|
|
*/
|
|
if (batt_present)
|
|
tmp |= EC_BATT_FLAG_BATT_PRESENT;
|
|
batt_present = 0;
|
|
}
|
|
|
|
if (curr.batt.flags & EC_BATT_FLAG_INVALID_DATA)
|
|
tmp |= EC_BATT_FLAG_INVALID_DATA;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_VOLTAGE))
|
|
base_battery_dynamic.actual_voltage = curr.batt.voltage;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_CURRENT))
|
|
base_battery_dynamic.actual_current = curr.batt.current;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_DESIRED_VOLTAGE))
|
|
base_battery_dynamic.desired_voltage =
|
|
curr.batt.desired_voltage;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_DESIRED_CURRENT))
|
|
base_battery_dynamic.desired_current =
|
|
curr.batt.desired_current;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_REMAINING_CAPACITY)) {
|
|
/*
|
|
* If we're running off the battery, it must have some charge.
|
|
* Don't report zero charge, as that has special meaning
|
|
* to Chrome OS powerd.
|
|
*/
|
|
if (curr.batt.remaining_capacity == 0 && !curr.batt_is_charging)
|
|
base_battery_dynamic.remaining_capacity = 1;
|
|
else
|
|
base_battery_dynamic.remaining_capacity =
|
|
curr.batt.remaining_capacity;
|
|
}
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_FULL_CAPACITY) &&
|
|
(curr.batt.full_capacity <=
|
|
(base_battery_dynamic.full_capacity - LFCC_EVENT_THRESH) ||
|
|
curr.batt.full_capacity >=
|
|
(base_battery_dynamic.full_capacity + LFCC_EVENT_THRESH))) {
|
|
base_battery_dynamic.full_capacity = curr.batt.full_capacity;
|
|
}
|
|
|
|
if (curr.batt.is_present == BP_YES &&
|
|
!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
|
|
curr.batt.state_of_charge <= BATTERY_LEVEL_CRITICAL)
|
|
tmp |= EC_BATT_FLAG_LEVEL_CRITICAL;
|
|
|
|
tmp |= curr.batt_is_charging ? EC_BATT_FLAG_CHARGING :
|
|
EC_BATT_FLAG_DISCHARGING;
|
|
|
|
base_battery_dynamic.flags = tmp;
|
|
}
|
|
#endif
|
|
|
|
static const char * const state_list[] = {
|
|
"idle", "discharge", "charge", "precharge"
|
|
};
|
|
BUILD_ASSERT(ARRAY_SIZE(state_list) == NUM_STATES_V2);
|
|
static const char * const batt_pres[] = {
|
|
"NO", "YES", "NOT_SURE",
|
|
};
|
|
|
|
static void dump_charge_state(void)
|
|
{
|
|
#define DUMP(FLD, FMT) ccprintf(#FLD " = " FMT "\n", curr.FLD)
|
|
#define DUMP_CHG(FLD, FMT) ccprintf("\t" #FLD " = " FMT "\n", curr.chg. FLD)
|
|
#define DUMP_BATT(FLD, FMT) ccprintf("\t" #FLD " = " FMT "\n", curr.batt. FLD)
|
|
ccprintf("state = %s\n", state_list[curr.state]);
|
|
DUMP(ac, "%d");
|
|
DUMP(batt_is_charging, "%d");
|
|
ccprintf("chg.*:\n");
|
|
DUMP_CHG(voltage, "%dmV");
|
|
DUMP_CHG(current, "%dmA");
|
|
DUMP_CHG(input_current, "%dmA");
|
|
DUMP_CHG(status, "0x%x");
|
|
DUMP_CHG(option, "0x%x");
|
|
DUMP_CHG(flags, "0x%x");
|
|
cflush();
|
|
ccprintf("batt.*:\n");
|
|
ccprintf("\ttemperature = %dC\n",
|
|
DECI_KELVIN_TO_CELSIUS(curr.batt.temperature));
|
|
DUMP_BATT(state_of_charge, "%d%%");
|
|
DUMP_BATT(voltage, "%dmV");
|
|
DUMP_BATT(current, "%dmA");
|
|
DUMP_BATT(desired_voltage, "%dmV");
|
|
DUMP_BATT(desired_current, "%dmA");
|
|
DUMP_BATT(flags, "0x%x");
|
|
DUMP_BATT(remaining_capacity, "%dmAh");
|
|
DUMP_BATT(full_capacity, "%dmAh");
|
|
ccprintf("\tis_present = %s\n", batt_pres[curr.batt.is_present]);
|
|
cflush();
|
|
DUMP(requested_voltage, "%dmV");
|
|
DUMP(requested_current, "%dmA");
|
|
#ifdef CONFIG_CHARGER_OTG
|
|
DUMP(output_current, "%dmA");
|
|
#endif
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
DUMP(input_voltage, "%dmV");
|
|
#endif
|
|
ccprintf("chg_ctl_mode = %d\n", chg_ctl_mode);
|
|
ccprintf("manual_mode = %d\n", manual_mode);
|
|
ccprintf("user_current_limit = %dmA\n", user_current_limit);
|
|
ccprintf("battery_seems_to_be_dead = %d\n", battery_seems_to_be_dead);
|
|
ccprintf("battery_seems_to_be_disconnected = %d\n",
|
|
battery_seems_to_be_disconnected);
|
|
ccprintf("battery_was_removed = %d\n", battery_was_removed);
|
|
ccprintf("debug output = %s\n", debugging ? "on" : "off");
|
|
#undef DUMP
|
|
}
|
|
|
|
static void show_charging_progress(void)
|
|
{
|
|
int rv = 0, minutes, to_full;
|
|
|
|
#ifdef CONFIG_BATTERY_SMART
|
|
/*
|
|
* Predicted remaining battery capacity based on AverageCurrent().
|
|
* 65535 = Battery is not being discharged.
|
|
*/
|
|
if (!battery_time_to_empty(&minutes) && minutes != 65535)
|
|
to_full = 0;
|
|
/*
|
|
* Predicted time-to-full charge based on AverageCurrent().
|
|
* 65535 = Battery is not being discharged.
|
|
*/
|
|
else if (!battery_time_to_full(&minutes) && minutes != 65535)
|
|
to_full = 1;
|
|
/*
|
|
* If both time to empty and time to full have invalid data, consider
|
|
* measured current from the coulomb counter and ac present status to
|
|
* decide whether battery is about to full or empty.
|
|
*/
|
|
else {
|
|
to_full = curr.batt_is_charging;
|
|
rv = EC_ERROR_UNKNOWN;
|
|
}
|
|
#else
|
|
if (!curr.batt_is_charging) {
|
|
rv = battery_time_to_empty(&minutes);
|
|
to_full = 0;
|
|
} else {
|
|
rv = battery_time_to_full(&minutes);
|
|
to_full = 1;
|
|
}
|
|
#endif
|
|
|
|
if (rv)
|
|
CPRINTS("Battery %d%% / ??h:?? %s%s",
|
|
curr.batt.state_of_charge,
|
|
to_full ? "to full" : "to empty",
|
|
is_full ? ", not accepting current" : "");
|
|
else
|
|
CPRINTS("Battery %d%% / %dh:%d %s%s",
|
|
curr.batt.state_of_charge,
|
|
minutes / 60, minutes % 60,
|
|
to_full ? "to full" : "to empty",
|
|
is_full ? ", not accepting current" : "");
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
CPRINTS("Base battery %d%%", charge_base);
|
|
#endif
|
|
|
|
if (debugging) {
|
|
ccprintf("battery:\n");
|
|
print_battery_debug();
|
|
ccprintf("charger:\n");
|
|
print_charger_debug();
|
|
ccprintf("chg:\n");
|
|
dump_charge_state();
|
|
}
|
|
}
|
|
|
|
/* Calculate if battery is full based on whether it is accepting charge */
|
|
static int calc_is_full(void)
|
|
{
|
|
static int __bss_slow ret;
|
|
|
|
/* If bad state of charge reading, return last value */
|
|
if (curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE ||
|
|
curr.batt.state_of_charge > 100)
|
|
return ret;
|
|
/*
|
|
* Battery is full when SoC is above 90% and battery desired current
|
|
* is 0. This is necessary because some batteries stop charging when
|
|
* the SoC still reports <100%, so we need to check desired current
|
|
* to know if it is actually full.
|
|
*/
|
|
ret = (curr.batt.state_of_charge >= 90 &&
|
|
curr.batt.desired_current == 0);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Ask the charger for some voltage and current. If either value is 0,
|
|
* charging is disabled; otherwise it's enabled. Negative values are ignored.
|
|
*/
|
|
static int charge_request(int voltage, int current)
|
|
{
|
|
int r1 = EC_SUCCESS, r2 = EC_SUCCESS, r3 = EC_SUCCESS;
|
|
static int __bss_slow prev_volt, prev_curr;
|
|
|
|
if (!voltage || !current) {
|
|
#ifdef CONFIG_CHARGER_NARROW_VDC
|
|
current = 0;
|
|
/*
|
|
* With NVDC charger, keep VSYS voltage higher than battery,
|
|
* otherwise the BGATE FET body diode would conduct and
|
|
* discharge the battery.
|
|
*/
|
|
voltage = charger_closest_voltage(
|
|
curr.batt.voltage + charger_get_info()->voltage_step);
|
|
/* If the battery is full, request the max voltage. */
|
|
if (is_full)
|
|
voltage = battery_get_info()->voltage_max;
|
|
/* And handle dead battery case */
|
|
voltage = MAX(voltage, battery_get_info()->voltage_min);
|
|
#else
|
|
voltage = current = 0;
|
|
#endif
|
|
}
|
|
|
|
if (curr.ac) {
|
|
if (prev_volt != voltage || prev_curr != current)
|
|
CPRINTS("%s(%dmV, %dmA)", __func__, voltage, current);
|
|
}
|
|
|
|
/*
|
|
* Set current before voltage so that if we are just starting
|
|
* to charge, we allow some time (i2c delay) for charging circuit to
|
|
* start at a voltage just above battery voltage before jumping
|
|
* up. This helps avoid large current spikes when connecting
|
|
* battery.
|
|
*/
|
|
if (current >= 0)
|
|
r2 = charger_set_current(current);
|
|
if (r2 != EC_SUCCESS)
|
|
problem(PR_SET_CURRENT, r2);
|
|
|
|
if (voltage >= 0)
|
|
r1 = charger_set_voltage(voltage);
|
|
if (r1 != EC_SUCCESS)
|
|
problem(PR_SET_VOLTAGE, r1);
|
|
|
|
/*
|
|
* 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);
|
|
else
|
|
r3 = charger_set_mode(CHARGE_FLAG_INHIBIT_CHARGE);
|
|
if (r3 != EC_SUCCESS)
|
|
problem(PR_SET_MODE, r3);
|
|
|
|
/*
|
|
* Only update if the request worked, so we'll keep trying on failures.
|
|
*/
|
|
if (!r1 && !r2) {
|
|
prev_volt = voltage;
|
|
prev_curr = current;
|
|
}
|
|
|
|
return r1 ? r1 : r2;
|
|
}
|
|
|
|
|
|
/* Force charging off before the battery is full. */
|
|
static int set_chg_ctrl_mode(enum ec_charge_control_mode mode)
|
|
{
|
|
if (mode == CHARGE_CONTROL_NORMAL) {
|
|
chg_ctl_mode = mode;
|
|
manual_mode = 0;
|
|
} else {
|
|
/*
|
|
* Changing mode is only meaningful if external power is
|
|
* present. If it's not present we can't charge anyway.
|
|
*/
|
|
if (!curr.ac)
|
|
return EC_ERROR_NOT_POWERED;
|
|
|
|
chg_ctl_mode = mode;
|
|
charge_request(0, 0);
|
|
manual_mode = 1;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* True if we know the battery temp is too high or too low */
|
|
static inline int battery_too_hot(int batt_temp_c)
|
|
{
|
|
return (!(curr.batt.flags & BATT_FLAG_BAD_TEMPERATURE) &&
|
|
(batt_temp_c > batt_info->discharging_max_c ||
|
|
batt_temp_c < batt_info->discharging_min_c));
|
|
}
|
|
|
|
/* True if we know the charge is too low, or we know the voltage is too low. */
|
|
static inline int battery_too_low(void)
|
|
{
|
|
return ((!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
|
|
curr.batt.state_of_charge < BATTERY_LEVEL_SHUTDOWN) ||
|
|
(!(curr.batt.flags & BATT_FLAG_BAD_VOLTAGE) &&
|
|
curr.batt.voltage <= batt_info->voltage_min));
|
|
}
|
|
|
|
|
|
/*
|
|
* Send host event to the AP if the battery is temperature or charge level
|
|
* is critical. Force-shutdown if the problem isn't corrected after timeout.
|
|
*/
|
|
static int shutdown_on_critical_battery(void)
|
|
{
|
|
int batt_temp_c;
|
|
int battery_critical = 0;
|
|
|
|
/*
|
|
* TODO(crosbug.com/p/27642): The thermal loop should watch the battery
|
|
* temp, so it can turn fans on.
|
|
*/
|
|
batt_temp_c = DECI_KELVIN_TO_CELSIUS(curr.batt.temperature);
|
|
if (battery_too_hot(batt_temp_c)) {
|
|
CPRINTS("Batt temp out of range: %dC", batt_temp_c);
|
|
battery_critical = 1;
|
|
}
|
|
|
|
if (battery_too_low() && !curr.batt_is_charging) {
|
|
CPRINTS("Low battery: %d%%, %dmV",
|
|
curr.batt.state_of_charge, curr.batt.voltage);
|
|
battery_critical = 1;
|
|
}
|
|
|
|
if (!battery_critical) {
|
|
/* Reset shutdown warning time */
|
|
shutdown_warning_time.val = 0;
|
|
return battery_critical;
|
|
}
|
|
|
|
if (!shutdown_warning_time.val) {
|
|
CPRINTS("charge warn shutdown due to critical battery");
|
|
shutdown_warning_time = get_time();
|
|
#ifdef CONFIG_HOSTCMD_EVENTS
|
|
if (!chipset_in_state(CHIPSET_STATE_ANY_OFF))
|
|
host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN);
|
|
#endif
|
|
} else if (get_time().val > shutdown_warning_time.val +
|
|
CRITICAL_BATTERY_SHUTDOWN_TIMEOUT_US) {
|
|
if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
|
|
/* Timeout waiting for charger to provide more power */
|
|
#if defined(CONFIG_BATTERY_CRITICAL_SHUTDOWN_CUT_OFF)
|
|
CPRINTS(
|
|
"charge force battery cut-off due to critical level");
|
|
board_cut_off_battery();
|
|
#elif defined(CONFIG_HIBERNATE)
|
|
CPRINTS(
|
|
"charge force EC hibernate due to critical battery");
|
|
system_hibernate(0, 0);
|
|
#endif
|
|
} else {
|
|
/* Timeout waiting for AP to shut down, so kill it */
|
|
CPRINTS(
|
|
"charge force shutdown due to critical battery");
|
|
chipset_force_shutdown();
|
|
}
|
|
}
|
|
|
|
return battery_critical;
|
|
}
|
|
|
|
/*
|
|
* Send host events as the battery charge drops below certain thresholds.
|
|
* We handle forced shutdown and other actions elsewhere; this is just for the
|
|
* host events. We send these even if the AP is off, since the AP will read and
|
|
* discard any events it doesn't care about the next time it wakes up.
|
|
*/
|
|
static void notify_host_of_low_battery(void)
|
|
{
|
|
/* We can't tell what the current charge is. Assume it's okay. */
|
|
if (curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE)
|
|
return;
|
|
|
|
#ifdef CONFIG_HOSTCMD_EVENTS
|
|
if (curr.batt.state_of_charge <= BATTERY_LEVEL_LOW &&
|
|
prev_charge > BATTERY_LEVEL_LOW)
|
|
host_set_single_event(EC_HOST_EVENT_BATTERY_LOW);
|
|
|
|
if (curr.batt.state_of_charge <= BATTERY_LEVEL_CRITICAL &&
|
|
prev_charge > BATTERY_LEVEL_CRITICAL)
|
|
host_set_single_event(EC_HOST_EVENT_BATTERY_CRITICAL);
|
|
#endif
|
|
}
|
|
|
|
const struct batt_params *charger_current_battery_params(void)
|
|
{
|
|
return &curr.batt;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hooks */
|
|
void charger_init(void)
|
|
{
|
|
/* Initialize current state */
|
|
memset(&curr, 0, sizeof(curr));
|
|
curr.batt.is_present = BP_NOT_SURE;
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, charger_init, HOOK_PRIO_DEFAULT);
|
|
|
|
/* Wake up the task when something important happens */
|
|
static void charge_wakeup(void)
|
|
{
|
|
task_wake(TASK_ID_CHARGER);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_RESUME, charge_wakeup, HOOK_PRIO_DEFAULT);
|
|
DECLARE_HOOK(HOOK_AC_CHANGE, charge_wakeup, HOOK_PRIO_DEFAULT);
|
|
|
|
|
|
static int get_desired_input_current(enum battery_present batt_present,
|
|
const struct charger_info * const info)
|
|
{
|
|
if (batt_present == BP_YES || system_is_locked() || base_connected) {
|
|
#ifdef CONFIG_CHARGE_MANAGER
|
|
int ilim = charge_manager_get_charger_current();
|
|
return ilim == CHARGE_CURRENT_UNINITIALIZED ?
|
|
CHARGE_CURRENT_UNINITIALIZED :
|
|
MAX(CONFIG_CHARGER_INPUT_CURRENT, ilim);
|
|
#else
|
|
return CONFIG_CHARGER_INPUT_CURRENT;
|
|
#endif
|
|
} else {
|
|
#ifdef CONFIG_USB_POWER_DELIVERY
|
|
return MIN(PD_MAX_CURRENT_MA, info->input_current_max);
|
|
#else
|
|
return info->input_current_max;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Main loop */
|
|
void charger_task(void *u)
|
|
{
|
|
int sleep_usec;
|
|
int battery_critical;
|
|
int need_static = 1;
|
|
const struct charger_info * const info = charger_get_info();
|
|
|
|
/* Get the battery-specific values */
|
|
batt_info = battery_get_info();
|
|
|
|
prev_ac = prev_charge = -1;
|
|
chg_ctl_mode = CHARGE_CONTROL_NORMAL;
|
|
shutdown_warning_time.val = 0UL;
|
|
battery_seems_to_be_dead = 0;
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
base_responsive = 0;
|
|
curr.input_voltage = CHARGE_VOLTAGE_UNINITIALIZED;
|
|
base_battery_dynamic.flags = EC_BATT_FLAG_INVALID_DATA;
|
|
charge_base = -1;
|
|
#endif
|
|
|
|
/*
|
|
* If system is not locked and we don't have a battery to live on,
|
|
* then use max input current limit so that we can pull as much power
|
|
* as needed.
|
|
*/
|
|
battery_get_params(&curr.batt);
|
|
prev_bp = curr.batt.is_present;
|
|
curr.desired_input_current = get_desired_input_current(prev_bp, info);
|
|
|
|
while (1) {
|
|
|
|
/* Let's see what's going on... */
|
|
curr.ts = get_time();
|
|
sleep_usec = 0;
|
|
problems_exist = 0;
|
|
battery_critical = 0;
|
|
curr.ac = extpower_is_present();
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
/*
|
|
* When base is powering the system, make sure curr.ac stays 0.
|
|
* TODO(b:71723024): Fix extpower_is_present() in hardware
|
|
* instead.
|
|
*/
|
|
if (base_responsive && prev_current_base < 0)
|
|
curr.ac = 0;
|
|
#endif
|
|
if (curr.ac != prev_ac) {
|
|
if (curr.ac) {
|
|
/*
|
|
* Some chargers are unpowered when the AC is
|
|
* off, so we'll reinitialize it when AC
|
|
* comes back and set the input current limit.
|
|
* Try again if it fails.
|
|
*/
|
|
int rv = charger_post_init();
|
|
if (rv != EC_SUCCESS) {
|
|
problem(PR_POST_INIT, rv);
|
|
} else {
|
|
if (curr.desired_input_current !=
|
|
CHARGE_CURRENT_UNINITIALIZED)
|
|
rv = charger_set_input_current(
|
|
curr.desired_input_current);
|
|
if (rv != EC_SUCCESS)
|
|
problem(PR_SET_INPUT_CURR, rv);
|
|
else
|
|
prev_ac = curr.ac;
|
|
}
|
|
} else {
|
|
/* Some things are only meaningful on AC */
|
|
chg_ctl_mode = CHARGE_CONTROL_NORMAL;
|
|
battery_seems_to_be_dead = 0;
|
|
prev_ac = curr.ac;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
base_connected = board_is_base_connected();
|
|
|
|
if (!base_connected) {
|
|
/* Invalidate static/dynamic information */
|
|
base_battery_dynamic.flags = EC_BATT_FLAG_INVALID_DATA;
|
|
charge_base = -1;
|
|
base_responsive = 0;
|
|
prev_current_base = 0;
|
|
prev_allow_charge_base = 0;
|
|
} else if (base_responsive) {
|
|
int old_flags = base_battery_dynamic.flags;
|
|
|
|
ec_ec_master_base_get_dynamic_info();
|
|
|
|
/* Fetch static information when flags change. */
|
|
if (old_flags != base_battery_dynamic.flags)
|
|
ec_ec_master_base_get_static_info();
|
|
|
|
charge_base = charge_get_base_percent();
|
|
}
|
|
#endif
|
|
|
|
charger_get_params(&curr.chg);
|
|
battery_get_params(&curr.batt);
|
|
|
|
if (prev_bp != curr.batt.is_present) {
|
|
prev_bp = curr.batt.is_present;
|
|
|
|
/* Update battery info due to change of battery */
|
|
batt_info = battery_get_info();
|
|
need_static = 1;
|
|
|
|
curr.desired_input_current =
|
|
get_desired_input_current(prev_bp, info);
|
|
if (curr.desired_input_current !=
|
|
CHARGE_CURRENT_UNINITIALIZED)
|
|
charger_set_input_current(
|
|
curr.desired_input_current);
|
|
hook_notify(HOOK_BATTERY_SOC_CHANGE);
|
|
}
|
|
|
|
/*
|
|
* TODO(crosbug.com/p/27527). Sometimes the battery thinks its
|
|
* temperature is 6280C, which seems a bit high. Let's ignore
|
|
* anything above the boiling point of tungsten until this bug
|
|
* is fixed. If the battery is really that warm, we probably
|
|
* have more urgent problems.
|
|
*/
|
|
if (curr.batt.temperature > CELSIUS_TO_DECI_KELVIN(5660)) {
|
|
CPRINTS("ignoring ridiculous batt.temp of %dC",
|
|
DECI_KELVIN_TO_CELSIUS(curr.batt.temperature));
|
|
curr.batt.flags |= BATT_FLAG_BAD_TEMPERATURE;
|
|
}
|
|
|
|
/* If the battery thinks it's above 100%, don't believe it */
|
|
if (curr.batt.state_of_charge > 100) {
|
|
CPRINTS("ignoring ridiculous batt.soc of %d%%",
|
|
curr.batt.state_of_charge);
|
|
curr.batt.flags |= BATT_FLAG_BAD_STATE_OF_CHARGE;
|
|
}
|
|
|
|
/*
|
|
* Now decide what we want to do about it. We'll normally just
|
|
* pass along whatever the battery wants to the charger. Note
|
|
* that if battery_get_params() can't get valid values from the
|
|
* battery it uses (0, 0), which is probably safer than blindly
|
|
* applying power to a battery we can't talk to.
|
|
*/
|
|
curr.requested_voltage = curr.batt.desired_voltage;
|
|
curr.requested_current = curr.batt.desired_current;
|
|
|
|
/* If we *know* there's no battery, wait for one to appear. */
|
|
if (curr.batt.is_present == BP_NO) {
|
|
if (!curr.ac)
|
|
CPRINTS("running with no battery and no AC");
|
|
curr.state = ST_IDLE;
|
|
curr.batt_is_charging = 0;
|
|
battery_was_removed = 1;
|
|
goto wait_for_it;
|
|
}
|
|
|
|
/*
|
|
* If we had trouble talking to the battery or the charger, we
|
|
* should probably do nothing for a bit, and if it doesn't get
|
|
* better then flag it as an error.
|
|
*/
|
|
if (curr.chg.flags & CHG_FLAG_BAD_ANY)
|
|
problem(PR_CHG_FLAGS, curr.chg.flags);
|
|
if (curr.batt.flags & BATT_FLAG_BAD_ANY)
|
|
problem(PR_BATT_FLAGS, curr.batt.flags);
|
|
|
|
/*
|
|
* If AC is present, check if input current is sufficient to
|
|
* actually charge battery.
|
|
*/
|
|
curr.batt_is_charging = curr.ac && (curr.batt.current >= 0);
|
|
|
|
/* Don't let the battery hurt itself. */
|
|
battery_critical = shutdown_on_critical_battery();
|
|
|
|
if (!curr.ac) {
|
|
curr.state = ST_DISCHARGE;
|
|
goto wait_for_it;
|
|
}
|
|
|
|
/* Okay, we're on AC and we should have a battery. */
|
|
|
|
/* Used for factory tests. */
|
|
if (chg_ctl_mode != CHARGE_CONTROL_NORMAL) {
|
|
curr.state = ST_IDLE;
|
|
goto wait_for_it;
|
|
}
|
|
|
|
/* If the battery is not responsive, try to wake it up. */
|
|
if (!(curr.batt.flags & BATT_FLAG_RESPONSIVE)) {
|
|
if (battery_seems_to_be_dead || battery_is_cut_off()) {
|
|
/* It's dead, do nothing */
|
|
curr.state = ST_IDLE;
|
|
curr.requested_voltage = 0;
|
|
curr.requested_current = 0;
|
|
} else if (curr.state == ST_PRECHARGE &&
|
|
(get_time().val > precharge_start_time.val +
|
|
PRECHARGE_TIMEOUT_US)) {
|
|
/* We've tried long enough, give up */
|
|
CPRINTS("battery seems to be dead");
|
|
battery_seems_to_be_dead = 1;
|
|
curr.state = ST_IDLE;
|
|
curr.requested_voltage = 0;
|
|
curr.requested_current = 0;
|
|
} else {
|
|
/* See if we can wake it up */
|
|
if (curr.state != ST_PRECHARGE) {
|
|
CPRINTS("try to wake battery");
|
|
precharge_start_time = get_time();
|
|
need_static = 1;
|
|
}
|
|
curr.state = ST_PRECHARGE;
|
|
curr.requested_voltage =
|
|
batt_info->voltage_max;
|
|
curr.requested_current =
|
|
batt_info->precharge_current;
|
|
}
|
|
goto wait_for_it;
|
|
} else {
|
|
/* The battery is responding. Yay. Try to use it. */
|
|
#ifdef CONFIG_BATTERY_REQUESTS_NIL_WHEN_DEAD
|
|
/*
|
|
* TODO (crosbug.com/p/29467): remove this workaround
|
|
* for dead battery that requests no voltage/current
|
|
*/
|
|
if (curr.requested_voltage == 0 &&
|
|
curr.requested_current == 0 &&
|
|
curr.batt.state_of_charge == 0) {
|
|
/* Battery is dead, give precharge current */
|
|
curr.requested_voltage =
|
|
batt_info->voltage_max;
|
|
curr.requested_current =
|
|
batt_info->precharge_current;
|
|
} else
|
|
#endif
|
|
#ifdef CONFIG_BATTERY_REVIVE_DISCONNECT
|
|
battery_seems_to_be_disconnected = 0;
|
|
|
|
if (curr.requested_voltage == 0 &&
|
|
curr.requested_current == 0 &&
|
|
battery_get_disconnect_state() ==
|
|
BATTERY_DISCONNECTED) {
|
|
/*
|
|
* Battery is in disconnect state. Apply a
|
|
* current to kick it out of this state.
|
|
*/
|
|
CPRINTS("found battery in disconnect state");
|
|
curr.requested_voltage =
|
|
batt_info->voltage_max;
|
|
curr.requested_current =
|
|
batt_info->precharge_current;
|
|
battery_seems_to_be_disconnected = 1;
|
|
} else
|
|
#endif
|
|
if (curr.state == ST_PRECHARGE ||
|
|
battery_seems_to_be_dead ||
|
|
battery_was_removed) {
|
|
CPRINTS("battery woke up");
|
|
|
|
/* Update the battery-specific values */
|
|
batt_info = battery_get_info();
|
|
need_static = 1;
|
|
}
|
|
|
|
battery_seems_to_be_dead = battery_was_removed = 0;
|
|
curr.state = ST_CHARGE;
|
|
}
|
|
|
|
wait_for_it:
|
|
#ifdef CONFIG_CHARGER_PROFILE_OVERRIDE
|
|
if (chg_ctl_mode == CHARGE_CONTROL_NORMAL) {
|
|
sleep_usec = charger_profile_override(&curr);
|
|
if (sleep_usec < 0)
|
|
problem(PR_CUSTOM, sleep_usec);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_CHARGE_MANAGER
|
|
if (curr.batt.state_of_charge >=
|
|
CONFIG_CHARGE_MANAGER_BAT_PCT_SAFE_MODE_EXIT &&
|
|
!battery_seems_to_be_disconnected)
|
|
charge_manager_leave_safe_mode();
|
|
#endif
|
|
|
|
/* Keep the AP informed */
|
|
if (need_static)
|
|
need_static = update_static_battery_info();
|
|
/* Wait on the dynamic info until the static info is good. */
|
|
if (!need_static)
|
|
update_dynamic_battery_info();
|
|
notify_host_of_low_battery();
|
|
|
|
/* And the EC console */
|
|
is_full = calc_is_full();
|
|
if ((!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
|
|
curr.batt.state_of_charge != prev_charge) ||
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
(charge_base != prev_charge_base) ||
|
|
#endif
|
|
(is_full != prev_full)) {
|
|
show_charging_progress();
|
|
prev_charge = curr.batt.state_of_charge;
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
prev_charge_base = charge_base;
|
|
#endif
|
|
hook_notify(HOOK_BATTERY_SOC_CHANGE);
|
|
}
|
|
prev_full = is_full;
|
|
|
|
#ifndef CONFIG_CHARGER_MAINTAIN_VBAT
|
|
/* Turn charger off if it's not needed */
|
|
if (curr.state == ST_IDLE || curr.state == ST_DISCHARGE) {
|
|
curr.requested_voltage = 0;
|
|
curr.requested_current = 0;
|
|
}
|
|
#endif
|
|
|
|
/* Apply external limits */
|
|
if (curr.requested_current > user_current_limit)
|
|
curr.requested_current = user_current_limit;
|
|
|
|
/* Round to valid values */
|
|
curr.requested_voltage =
|
|
charger_closest_voltage(curr.requested_voltage);
|
|
curr.requested_current =
|
|
charger_closest_current(curr.requested_current);
|
|
|
|
/* Charger only accpets request when AC is on. */
|
|
if (curr.ac) {
|
|
/*
|
|
* Some batteries would wake up after cut-off if we keep
|
|
* charging it. Thus, we only charge when AC is on and
|
|
* battery is not cut off yet.
|
|
*/
|
|
if (battery_is_cut_off()) {
|
|
curr.requested_voltage = 0;
|
|
curr.requested_current = 0;
|
|
}
|
|
/*
|
|
* As a safety feature, some chargers will stop
|
|
* charging if we don't communicate with it frequently
|
|
* enough. In manual mode, we'll just tell it what it
|
|
* knows.
|
|
*/
|
|
else if (manual_mode) {
|
|
curr.requested_voltage = curr.chg.voltage;
|
|
curr.requested_current = curr.chg.current;
|
|
}
|
|
} else {
|
|
#ifndef CONFIG_CHARGER_MAINTAIN_VBAT
|
|
curr.requested_voltage = charger_closest_voltage(
|
|
curr.batt.voltage + info->voltage_step);
|
|
curr.requested_current = -1;
|
|
#endif
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_SLAVE
|
|
/*
|
|
* On EC-EC slave, do not charge if curr.ac is 0: there
|
|
* might still be some external power available but we
|
|
* do not want to use it for charging.
|
|
*/
|
|
curr.requested_current = 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
charge_allocate_input_current_limit();
|
|
#else
|
|
charge_request(curr.requested_voltage, curr.requested_current);
|
|
#endif
|
|
|
|
/* How long to sleep? */
|
|
if (problems_exist)
|
|
/* If there are errors, don't wait very long. */
|
|
sleep_usec = CHARGE_POLL_PERIOD_SHORT;
|
|
else if (sleep_usec <= 0) {
|
|
/* default values depend on the state */
|
|
if (!curr.ac &&
|
|
(curr.state == ST_IDLE ||
|
|
curr.state == ST_DISCHARGE)) {
|
|
#ifdef CONFIG_CHARGER_OTG
|
|
int output_current = curr.output_current;
|
|
#else
|
|
int output_current = 0;
|
|
#endif
|
|
/*
|
|
* If AP is off and we do not provide power, we
|
|
* can sleep a long time.
|
|
*/
|
|
if (chipset_in_state(CHIPSET_STATE_ANY_OFF |
|
|
CHIPSET_STATE_ANY_SUSPEND)
|
|
&& output_current == 0)
|
|
sleep_usec =
|
|
CHARGE_POLL_PERIOD_VERY_LONG;
|
|
else
|
|
/* Discharging, not too urgent */
|
|
sleep_usec = CHARGE_POLL_PERIOD_LONG;
|
|
} else {
|
|
/* AC present, so pay closer attention */
|
|
sleep_usec = CHARGE_POLL_PERIOD_CHARGE;
|
|
}
|
|
}
|
|
|
|
/* Adjust for time spent in this loop */
|
|
sleep_usec -= (int)(get_time().val - curr.ts.val);
|
|
if (sleep_usec < CHARGE_MIN_SLEEP_USEC)
|
|
sleep_usec = CHARGE_MIN_SLEEP_USEC;
|
|
else if (sleep_usec > CHARGE_MAX_SLEEP_USEC)
|
|
sleep_usec = CHARGE_MAX_SLEEP_USEC;
|
|
|
|
/*
|
|
* If battery is critical, ensure that the sleep time is not
|
|
* very long since we might want to hibernate or cut-off
|
|
* battery sooner.
|
|
*/
|
|
if (battery_critical &&
|
|
(sleep_usec > CRITICAL_BATTERY_SHUTDOWN_TIMEOUT_US))
|
|
sleep_usec = CRITICAL_BATTERY_SHUTDOWN_TIMEOUT_US;
|
|
|
|
task_wait_event(sleep_usec);
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Exported functions */
|
|
|
|
int charge_want_shutdown(void)
|
|
{
|
|
return (curr.state == ST_DISCHARGE) &&
|
|
!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
|
|
(curr.batt.state_of_charge < BATTERY_LEVEL_SHUTDOWN);
|
|
}
|
|
|
|
int charge_prevent_power_on(int power_button_pressed)
|
|
{
|
|
int prevent_power_on = 0;
|
|
#ifdef CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON
|
|
struct batt_params params;
|
|
struct batt_params *current_batt_params = &curr.batt;
|
|
static int automatic_power_on = 1;
|
|
|
|
/*
|
|
* Remember that a power button was pressed, and assume subsequent
|
|
* power-ups are user-requested and non-automatic.
|
|
*/
|
|
if (power_button_pressed)
|
|
automatic_power_on = 0;
|
|
|
|
/* If battery params seem uninitialized then retrieve them */
|
|
if (current_batt_params->is_present == BP_NOT_SURE) {
|
|
battery_get_params(¶ms);
|
|
current_batt_params = ¶ms;
|
|
}
|
|
/* Require a minimum battery level to power on */
|
|
if (current_batt_params->is_present != BP_YES ||
|
|
current_batt_params->state_of_charge <
|
|
CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON)
|
|
prevent_power_on = 1;
|
|
|
|
#ifdef CONFIG_CHARGER_LIMIT_POWER_THRESH_BAT_PCT
|
|
/*
|
|
* Allow power-on if our charger advertises more than
|
|
* LIKELY_PD_USBC_POWER_MW since it may speak PD and provide
|
|
* sufficient power once we enable PD communication.
|
|
*/
|
|
if (prevent_power_on)
|
|
if (charge_manager_get_power_limit_uw() >=
|
|
MIN(LIKELY_PD_USBC_POWER_MW * 1000,
|
|
CONFIG_CHARGER_LIMIT_POWER_THRESH_CHG_MW * 1000))
|
|
prevent_power_on = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Factory override: Always allow power on if WP is disabled,
|
|
* except when auto-power-on at EC startup and the battery
|
|
* is physically present.
|
|
*/
|
|
prevent_power_on &= (system_is_locked() || (automatic_power_on
|
|
#ifdef CONFIG_BATTERY_HW_PRESENT_CUSTOM
|
|
&& battery_hw_present() == BP_YES
|
|
#endif
|
|
));
|
|
#endif
|
|
|
|
#ifdef CONFIG_CHARGE_MANAGER
|
|
/* Always prevent power on until charge current is initialized */
|
|
if (extpower_is_present() &&
|
|
(charge_manager_get_charger_current() ==
|
|
CHARGE_CURRENT_UNINITIALIZED))
|
|
prevent_power_on = 1;
|
|
#ifdef CONFIG_BATTERY_HW_PRESENT_CUSTOM
|
|
/*
|
|
* If battery is NOT physically present then prevent power on until
|
|
* charge manager provides at least LIKELY_PD_USBC_POWER_MW.
|
|
*/
|
|
if (extpower_is_present() && battery_hw_present() == BP_NO &&
|
|
charge_manager_get_power_limit_uw() <
|
|
#ifdef CONFIG_CHARGER_LIMIT_POWER_THRESH_BAT_PCT
|
|
MIN(LIKELY_PD_USBC_POWER_MW * 1000,
|
|
CONFIG_CHARGER_LIMIT_POWER_THRESH_CHG_MW * 1000))
|
|
#else
|
|
(LIKELY_PD_USBC_POWER_MW * 1000))
|
|
#endif
|
|
prevent_power_on = 1;
|
|
#endif
|
|
#endif
|
|
return prevent_power_on;
|
|
}
|
|
|
|
enum charge_state charge_get_state(void)
|
|
{
|
|
switch (curr.state) {
|
|
case ST_IDLE:
|
|
if (battery_seems_to_be_dead || battery_was_removed)
|
|
return PWR_STATE_ERROR;
|
|
return PWR_STATE_IDLE;
|
|
case ST_DISCHARGE:
|
|
#ifdef CONFIG_PWR_STATE_DISCHARGE_FULL
|
|
if (curr.batt.state_of_charge >= BATTERY_LEVEL_NEAR_FULL)
|
|
return PWR_STATE_DISCHARGE_FULL;
|
|
else
|
|
#endif
|
|
return PWR_STATE_DISCHARGE;
|
|
case ST_CHARGE:
|
|
/* The only difference here is what the LEDs display. */
|
|
if (curr.batt.state_of_charge >= BATTERY_LEVEL_NEAR_FULL)
|
|
return PWR_STATE_CHARGE_NEAR_FULL;
|
|
else
|
|
return PWR_STATE_CHARGE;
|
|
default:
|
|
/* Anything else can be considered an error for LED purposes */
|
|
return PWR_STATE_ERROR;
|
|
}
|
|
}
|
|
|
|
uint32_t charge_get_flags(void)
|
|
{
|
|
uint32_t flags = 0;
|
|
|
|
if (chg_ctl_mode != CHARGE_CONTROL_NORMAL)
|
|
flags |= CHARGE_FLAG_FORCE_IDLE;
|
|
if (curr.ac)
|
|
flags |= CHARGE_FLAG_EXTERNAL_POWER;
|
|
if (curr.batt.flags & BATT_FLAG_RESPONSIVE)
|
|
flags |= CHARGE_FLAG_BATT_RESPONSIVE;
|
|
|
|
return flags;
|
|
}
|
|
|
|
int charge_get_percent(void)
|
|
{
|
|
/*
|
|
* Since there's no way to indicate an error to the caller, we'll just
|
|
* return the last known value. Even if we've never been able to talk
|
|
* to the battery, that'll be zero, which is probably as good as
|
|
* anything.
|
|
*/
|
|
return is_full ? 100 : curr.batt.state_of_charge;
|
|
}
|
|
|
|
int charge_get_battery_temp(int idx, int *temp_ptr)
|
|
{
|
|
if (curr.batt.flags & BATT_FLAG_BAD_TEMPERATURE)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
/* Battery temp is 10ths of degrees K, temp wants degrees K */
|
|
*temp_ptr = curr.batt.temperature / 10;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int charge_is_consuming_full_input_current(void)
|
|
{
|
|
int chg_pct = charge_get_percent();
|
|
|
|
return chg_pct > 2 && chg_pct < 95;
|
|
}
|
|
|
|
#ifdef CONFIG_CHARGER_OTG
|
|
int charge_set_output_current_limit(int ma, int mv)
|
|
{
|
|
int ret;
|
|
int enable = ma > 0;
|
|
|
|
if (enable) {
|
|
ret = charger_set_otg_current_voltage(ma, mv);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
}
|
|
|
|
ret = charger_enable_otg_power(enable);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
|
|
/* If we start/stop providing power, wake the charger task. */
|
|
if ((curr.output_current == 0 && enable) ||
|
|
(curr.output_current > 0 && !enable))
|
|
task_wake(TASK_ID_CHARGER);
|
|
|
|
curr.output_current = ma;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
int charge_set_input_current_limit(int ma, int mv)
|
|
{
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
curr.input_voltage = mv;
|
|
#endif
|
|
/*
|
|
* If battery is not present, we are not locked, and base is not
|
|
* connected then allow system to pull as much input current as needed.
|
|
* Yes, we might overcurrent the charger but this is no worse than
|
|
* browning out due to insufficient input current.
|
|
*/
|
|
if (curr.batt.is_present != BP_YES && !system_is_locked() &&
|
|
!base_connected) {
|
|
#ifdef CONFIG_USB_POWER_DELIVERY
|
|
#if ((PD_MAX_POWER_MW * 1000) / PD_MAX_VOLTAGE_MV != PD_MAX_CURRENT_MA)
|
|
/*
|
|
* If battery is not present, input current is set to
|
|
* PD_MAX_CURRENT_MA. If the input power set is greater than
|
|
* the maximum allowed system power, system might get damaged.
|
|
* Hence, limit the input current to meet maximum allowed
|
|
* input system power.
|
|
*/
|
|
if (mv > 0 && mv * curr.desired_input_current >
|
|
PD_MAX_POWER_MW * 1000)
|
|
ma = (PD_MAX_POWER_MW * 1000) / mv;
|
|
else
|
|
return EC_SUCCESS;
|
|
#else
|
|
return EC_SUCCESS;
|
|
#endif
|
|
#endif /* CONFIG_USB_POWER_DELIVERY */
|
|
}
|
|
|
|
#ifdef CONFIG_CHARGER_MAX_INPUT_CURRENT
|
|
/* Limit input current limit to max limit for this board */
|
|
ma = MIN(ma, CONFIG_CHARGER_MAX_INPUT_CURRENT);
|
|
#endif
|
|
curr.desired_input_current = ma;
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
/* Wake up charger task to allocate current between lid and base. */
|
|
charge_wakeup();
|
|
return EC_SUCCESS;
|
|
#else
|
|
return charger_set_input_current(ma);
|
|
#endif
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Host commands */
|
|
|
|
static int charge_command_charge_control(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_charge_control *p = args->params;
|
|
int rv;
|
|
|
|
rv = set_chg_ctrl_mode(p->mode);
|
|
if (rv != EC_SUCCESS)
|
|
return EC_RES_ERROR;
|
|
|
|
#ifdef CONFIG_CHARGER_DISCHARGE_ON_AC
|
|
#ifdef CONFIG_CHARGER_DISCHARGE_ON_AC_CUSTOM
|
|
rv = board_discharge_on_ac(p->mode == CHARGE_CONTROL_DISCHARGE);
|
|
#else
|
|
rv = charger_discharge_on_ac(p->mode == CHARGE_CONTROL_DISCHARGE);
|
|
#endif
|
|
if (rv != EC_SUCCESS)
|
|
return EC_RES_ERROR;
|
|
#endif
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_CHARGE_CONTROL, charge_command_charge_control,
|
|
EC_VER_MASK(1));
|
|
|
|
static void reset_current_limit(void)
|
|
{
|
|
user_current_limit = -1U;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, reset_current_limit, HOOK_PRIO_DEFAULT);
|
|
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, reset_current_limit, HOOK_PRIO_DEFAULT);
|
|
|
|
static int charge_command_current_limit(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_current_limit *p = args->params;
|
|
|
|
user_current_limit = p->limit;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_CHARGE_CURRENT_LIMIT, charge_command_current_limit,
|
|
EC_VER_MASK(0));
|
|
|
|
static int charge_command_charge_state(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_charge_state *in = args->params;
|
|
struct ec_response_charge_state *out = args->response;
|
|
uint32_t val;
|
|
int rv = EC_RES_SUCCESS;
|
|
|
|
switch (in->cmd) {
|
|
|
|
case CHARGE_STATE_CMD_GET_STATE:
|
|
out->get_state.ac = curr.ac;
|
|
out->get_state.chg_voltage = curr.chg.voltage;
|
|
out->get_state.chg_current = curr.chg.current;
|
|
out->get_state.chg_input_current = curr.chg.input_current;
|
|
out->get_state.batt_state_of_charge = curr.batt.state_of_charge;
|
|
args->response_size = sizeof(out->get_state);
|
|
break;
|
|
|
|
case CHARGE_STATE_CMD_GET_PARAM:
|
|
val = 0;
|
|
#ifdef CONFIG_CHARGER_PROFILE_OVERRIDE
|
|
/* custom profile params */
|
|
if (in->get_param.param >= CS_PARAM_CUSTOM_PROFILE_MIN &&
|
|
in->get_param.param <= CS_PARAM_CUSTOM_PROFILE_MAX) {
|
|
rv = charger_profile_override_get_param(
|
|
in->get_param.param, &val);
|
|
} else
|
|
#endif
|
|
#ifdef CONFIG_CHARGE_STATE_DEBUG
|
|
/* debug params */
|
|
if (in->get_param.param >= CS_PARAM_DEBUG_MIN &&
|
|
in->get_param.param <= CS_PARAM_DEBUG_MAX) {
|
|
rv = charge_get_charge_state_debug(
|
|
in->get_param.param, &val);
|
|
} else
|
|
#endif
|
|
/* standard params */
|
|
switch (in->get_param.param) {
|
|
case CS_PARAM_CHG_VOLTAGE:
|
|
val = curr.chg.voltage;
|
|
break;
|
|
case CS_PARAM_CHG_CURRENT:
|
|
val = curr.chg.current;
|
|
break;
|
|
case CS_PARAM_CHG_INPUT_CURRENT:
|
|
val = curr.chg.input_current;
|
|
break;
|
|
case CS_PARAM_CHG_STATUS:
|
|
val = curr.chg.status;
|
|
break;
|
|
case CS_PARAM_CHG_OPTION:
|
|
val = curr.chg.option;
|
|
break;
|
|
case CS_PARAM_LIMIT_POWER:
|
|
#ifdef CONFIG_CHARGER_LIMIT_POWER_THRESH_BAT_PCT
|
|
/*
|
|
* LIMIT_POWER status is based on battery level
|
|
* and external charger power.
|
|
*/
|
|
if ((curr.batt.is_present != BP_YES ||
|
|
curr.batt.state_of_charge <
|
|
CONFIG_CHARGER_LIMIT_POWER_THRESH_BAT_PCT)
|
|
&& charge_manager_get_power_limit_uw() <
|
|
CONFIG_CHARGER_LIMIT_POWER_THRESH_CHG_MW
|
|
* 1000 && system_is_locked())
|
|
val = 1;
|
|
else
|
|
#endif
|
|
val = 0;
|
|
break;
|
|
default:
|
|
rv = EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
/* got something */
|
|
out->get_param.value = val;
|
|
args->response_size = sizeof(out->get_param);
|
|
break;
|
|
|
|
case CHARGE_STATE_CMD_SET_PARAM:
|
|
if (system_is_locked())
|
|
return EC_RES_ACCESS_DENIED;
|
|
|
|
val = in->set_param.value;
|
|
#ifdef CONFIG_CHARGER_PROFILE_OVERRIDE
|
|
/* custom profile params */
|
|
if (in->set_param.param >= CS_PARAM_CUSTOM_PROFILE_MIN &&
|
|
in->set_param.param <= CS_PARAM_CUSTOM_PROFILE_MAX) {
|
|
rv = charger_profile_override_set_param(
|
|
in->set_param.param, val);
|
|
} else
|
|
#endif
|
|
switch (in->set_param.param) {
|
|
case CS_PARAM_CHG_VOLTAGE:
|
|
val = charger_closest_voltage(val);
|
|
if (charge_request(val, -1))
|
|
rv = EC_RES_ERROR;
|
|
manual_mode = 1;
|
|
break;
|
|
case CS_PARAM_CHG_CURRENT:
|
|
val = charger_closest_current(val);
|
|
if (charge_request(-1, val))
|
|
rv = EC_RES_ERROR;
|
|
manual_mode = 1;
|
|
break;
|
|
case CS_PARAM_CHG_INPUT_CURRENT:
|
|
if (charger_set_input_current(val))
|
|
rv = EC_RES_ERROR;
|
|
break;
|
|
case CS_PARAM_CHG_STATUS:
|
|
case CS_PARAM_LIMIT_POWER:
|
|
/* Can't set this */
|
|
rv = EC_RES_ACCESS_DENIED;
|
|
break;
|
|
case CS_PARAM_CHG_OPTION:
|
|
if (charger_set_option(val))
|
|
rv = EC_RES_ERROR;
|
|
break;
|
|
default:
|
|
rv = EC_RES_INVALID_PARAM;
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
CPRINTS("EC_CMD_CHARGE_STATE: bad cmd 0x%x", in->cmd);
|
|
rv = EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
DECLARE_HOST_COMMAND(EC_CMD_CHARGE_STATE, charge_command_charge_state,
|
|
EC_VER_MASK(0));
|
|
|
|
/*****************************************************************************/
|
|
/* Console commands */
|
|
|
|
#ifdef CONFIG_CMD_PWR_AVG
|
|
|
|
static int command_pwr_avg(int argc, char **argv)
|
|
{
|
|
int avg_mv;
|
|
int avg_ma;
|
|
int avg_mw;
|
|
|
|
if (argc != 1)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
avg_mv = battery_get_avg_voltage();
|
|
if (avg_mv < 0)
|
|
return EC_ERROR_UNKNOWN;
|
|
avg_ma = battery_get_avg_current();
|
|
avg_mw = avg_mv * avg_ma / 1000;
|
|
|
|
ccprintf("mv = %d\nma = %d\nmw = %d\n",
|
|
avg_mv, avg_ma, avg_mw);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
DECLARE_CONSOLE_COMMAND(pwr_avg, command_pwr_avg,
|
|
NULL,
|
|
"Get 1 min power average");
|
|
|
|
#endif /* CONFIG_CMD_PWR_AVG */
|
|
|
|
static int command_chgstate(int argc, char **argv)
|
|
{
|
|
int rv;
|
|
int val;
|
|
|
|
if (argc > 1) {
|
|
if (!strcasecmp(argv[1], "idle")) {
|
|
if (argc <= 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
if (!parse_bool(argv[2], &val))
|
|
return EC_ERROR_PARAM2;
|
|
rv = set_chg_ctrl_mode(val ? CHARGE_CONTROL_IDLE :
|
|
CHARGE_CONTROL_NORMAL);
|
|
if (rv)
|
|
return rv;
|
|
#ifdef CONFIG_CHARGER_DISCHARGE_ON_AC
|
|
} else if (!strcasecmp(argv[1], "discharge")) {
|
|
if (argc <= 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
if (!parse_bool(argv[2], &val))
|
|
return EC_ERROR_PARAM2;
|
|
rv = set_chg_ctrl_mode(val ? CHARGE_CONTROL_DISCHARGE :
|
|
CHARGE_CONTROL_NORMAL);
|
|
if (rv)
|
|
return rv;
|
|
#ifdef CONFIG_CHARGER_DISCHARGE_ON_AC_CUSTOM
|
|
rv = board_discharge_on_ac(val);
|
|
#else
|
|
rv = charger_discharge_on_ac(val);
|
|
#endif /* CONFIG_CHARGER_DISCHARGE_ON_AC_CUSTOM */
|
|
if (rv)
|
|
return rv;
|
|
#endif /* CONFIG_CHARGER_DISCHARGE_ON_AC */
|
|
} else if (!strcasecmp(argv[1], "debug")) {
|
|
if (argc <= 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
if (!parse_bool(argv[2], &debugging))
|
|
return EC_ERROR_PARAM2;
|
|
} else {
|
|
return EC_ERROR_PARAM1;
|
|
}
|
|
}
|
|
|
|
dump_charge_state();
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(chgstate, command_chgstate,
|
|
"[idle|discharge|debug on|off]",
|
|
"Get/set charge state machine status");
|
|
|
|
#ifdef CONFIG_CHARGE_STATE_DEBUG
|
|
int charge_get_charge_state_debug(int param, uint32_t *value)
|
|
{
|
|
switch (param) {
|
|
case CS_PARAM_DEBUG_CTL_MODE:
|
|
*value = chg_ctl_mode;
|
|
break;
|
|
case CS_PARAM_DEBUG_MANUAL_MODE:
|
|
*value = manual_mode;
|
|
break;
|
|
case CS_PARAM_DEBUG_SEEMS_DEAD:
|
|
*value = battery_seems_to_be_dead;
|
|
break;
|
|
case CS_PARAM_DEBUG_SEEMS_DISCONNECTED:
|
|
*value = battery_seems_to_be_disconnected;
|
|
break;
|
|
case CS_PARAM_DEBUG_BATT_REMOVED:
|
|
*value = battery_was_removed;
|
|
break;
|
|
default:
|
|
*value = 0;
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif
|