2601 lines
74 KiB
C
2601 lines
74 KiB
C
/* Copyright 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 "throttle_ap.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 */
|
|
|
|
#ifdef CONFIG_THROTTLE_AP_ON_BAT_DISCHG_CURRENT
|
|
#ifndef CONFIG_HOSTCMD_EVENTS
|
|
#error "CONFIG_THROTTLE_AP_ON_BAT_DISCHG_CURRENT needs CONFIG_HOSTCMD_EVENTS"
|
|
#endif /* CONFIG_HOSTCMD_EVENTS */
|
|
#define BAT_OCP_TIMEOUT_US (60 * SECOND)
|
|
/* BAT_OCP_HYSTERESIS_PCT can be optionally overridden in board.h. */
|
|
#ifndef BAT_OCP_HYSTERESIS_PCT
|
|
#define BAT_OCP_HYSTERESIS_PCT 10
|
|
#endif /* BAT_OCP_HYSTERESIS_PCT */
|
|
#define BAT_OCP_HYSTERESIS \
|
|
(BAT_MAX_DISCHG_CURRENT * BAT_OCP_HYSTERESIS_PCT / 100) /* mA */
|
|
#endif /* CONFIG_THROTTLE_AP_ON_BAT_DISCHG_CURRENT */
|
|
|
|
#ifdef CONFIG_THROTTLE_AP_ON_BAT_VOLTAGE
|
|
#ifndef CONFIG_HOSTCMD_EVENTS
|
|
#error "CONFIG_THROTTLE_AP_ON_BAT_VOLTAGE needs CONFIG_HOSTCMD_EVENTS"
|
|
#endif /* CONFIG_HOSTCMD_EVENTS */
|
|
#define BAT_UVP_TIMEOUT_US (60 * SECOND)
|
|
/* BAT_UVP_HYSTERESIS_PCT can be optionally overridden in board.h. */
|
|
#ifndef BAT_UVP_HYSTERESIS_PCT
|
|
#define BAT_UVP_HYSTERESIS_PCT 3
|
|
#endif /* BAT_UVP_HYSTERESIS_PCT */
|
|
#define BAT_UVP_HYSTERESIS \
|
|
(BAT_LOW_VOLTAGE_THRESH * BAT_UVP_HYSTERESIS_PCT / 100) /* mV */
|
|
static timestamp_t uvp_throttle_start_time;
|
|
#endif /* CONFIG_THROTTLE_AP_ON_BAT_OLTAGE */
|
|
|
|
static int charge_request(int voltage, int current);
|
|
|
|
static uint8_t battery_level_shutdown;
|
|
|
|
/*
|
|
* 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 enum charge_state_v2 prev_state;
|
|
static int prev_ac, prev_charge, prev_full, prev_disp_charge;
|
|
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_voltage; /* Manual voltage override (-1 = no override) */
|
|
static int manual_current; /* Manual current override (-1 = no override) */
|
|
static unsigned int user_current_limit = -1U;
|
|
test_export_static timestamp_t shutdown_target_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;
|
|
|
|
/*
|
|
* 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;
|
|
#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;
|
|
/*
|
|
* 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)
|
|
|
|
/* Update base battery information */
|
|
static void update_base_battery_info(void)
|
|
{
|
|
struct ec_response_battery_dynamic_info *const bd =
|
|
&battery_dynamic[BATT_IDX_BASE];
|
|
|
|
base_connected = board_is_base_connected();
|
|
|
|
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_master_base_get_dynamic_info();
|
|
flags_changed = (old_flags != bd->flags);
|
|
/* Fetch static information when flags change. */
|
|
if (flags_changed)
|
|
ec_ec_master_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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 && 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;
|
|
}
|
|
|
|
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();
|
|
|
|
const struct ec_response_battery_dynamic_info *const base_bd =
|
|
&battery_dynamic[BATT_IDX_BASE];
|
|
|
|
|
|
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) {
|
|
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);
|
|
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);
|
|
if (base_responsive) {
|
|
/* Base still responsive, put it to sleep. */
|
|
CPRINTF("Hibernating base\n");
|
|
ec_ec_master_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);
|
|
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);
|
|
} 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);
|
|
}
|
|
|
|
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);
|
|
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_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);
|
|
} else { /* Discharging */
|
|
}
|
|
|
|
if (debugging)
|
|
CPRINTF("====\n");
|
|
}
|
|
#endif /* CONFIG_EC_EC_COMM_BATTERY_MASTER */
|
|
|
|
#ifndef CONFIG_BATTERY_V2
|
|
/* Returns zero if every item was updated. */
|
|
static int update_static_battery_info(void)
|
|
{
|
|
char *batt_str;
|
|
int batt_serial;
|
|
uint8_t batt_flags = 0;
|
|
/*
|
|
* 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;
|
|
if (extpower_is_present())
|
|
batt_flags |= EC_BATT_FLAG_AC_PRESENT;
|
|
*host_get_memmap(EC_MEMMAP_BATT_FLAG) = batt_flags;
|
|
|
|
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);
|
|
}
|
|
#else /* CONFIG_BATTERY_V2 */
|
|
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;
|
|
|
|
struct ec_response_battery_static_info *const bs =
|
|
&battery_static[BATT_IDX_MAIN];
|
|
|
|
/* Clear all static information. */
|
|
memset(bs, 0, sizeof(*bs));
|
|
|
|
/* Smart battery serial number is 16 bits */
|
|
rv = battery_serial_number(&batt_serial);
|
|
if (!rv)
|
|
snprintf(bs->serial, sizeof(bs->serial), "%04X", batt_serial);
|
|
|
|
/* Design Capacity of Full */
|
|
ret = battery_design_capacity(&val);
|
|
if (!ret)
|
|
bs->design_capacity = val;
|
|
rv |= ret;
|
|
|
|
/* Design Voltage */
|
|
ret = battery_design_voltage(&val);
|
|
if (!ret)
|
|
bs->design_voltage = val;
|
|
rv |= ret;
|
|
|
|
/* Cycle Count */
|
|
ret = battery_cycle_count(&val);
|
|
if (!ret)
|
|
bs->cycle_count = val;
|
|
rv |= ret;
|
|
|
|
/* Battery Manufacturer string */
|
|
rv |= battery_manufacturer_name(bs->manufacturer,
|
|
sizeof(bs->manufacturer));
|
|
|
|
/* Battery Model string */
|
|
rv |= battery_device_name(bs->model, sizeof(bs->model));
|
|
|
|
/* Battery Type string */
|
|
rv |= battery_device_chemistry(bs->type, sizeof(bs->type));
|
|
|
|
/* Zero the dynamic entries. They'll come next. */
|
|
memset(&battery_dynamic[BATT_IDX_MAIN], 0,
|
|
sizeof(battery_dynamic[BATT_IDX_MAIN]));
|
|
|
|
if (rv)
|
|
problem(PR_STATIC_UPDATE, rv);
|
|
|
|
#ifdef HAS_TASK_HOSTCMD
|
|
battery_memmap_refresh(BATT_IDX_MAIN);
|
|
#endif
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void update_dynamic_battery_info(void)
|
|
{
|
|
static int __bss_slow batt_present;
|
|
uint8_t tmp;
|
|
int send_batt_status_event = 0;
|
|
int send_batt_info_event = 0;
|
|
|
|
struct ec_response_battery_dynamic_info *const bd =
|
|
&battery_dynamic[BATT_IDX_MAIN];
|
|
|
|
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 (!(bd->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 (bd->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))
|
|
bd->actual_voltage = curr.batt.voltage;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_CURRENT))
|
|
bd->actual_current = curr.batt.current;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_DESIRED_VOLTAGE))
|
|
bd->desired_voltage = curr.batt.desired_voltage;
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_DESIRED_CURRENT))
|
|
bd->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)
|
|
bd->remaining_capacity = 1;
|
|
else
|
|
bd->remaining_capacity = curr.batt.remaining_capacity;
|
|
}
|
|
|
|
if (!(curr.batt.flags & BATT_FLAG_BAD_FULL_CAPACITY) &&
|
|
(curr.batt.full_capacity <=
|
|
(bd->full_capacity - LFCC_EVENT_THRESH) ||
|
|
curr.batt.full_capacity >=
|
|
(bd->full_capacity + LFCC_EVENT_THRESH))) {
|
|
bd->full_capacity = 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 (bd->flags != tmp)
|
|
send_batt_status_event++;
|
|
|
|
bd->flags = tmp;
|
|
|
|
#ifdef HAS_TASK_HOSTCMD
|
|
battery_memmap_refresh(BATT_IDX_MAIN);
|
|
#endif
|
|
|
|
#ifdef CONFIG_HOSTCMD_EVENTS
|
|
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);
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_BATTERY_V2 */
|
|
|
|
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_voltage = %d\n", manual_voltage);
|
|
ccprintf("manual_current = %d\n", manual_current);
|
|
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%% (Display %d.%d %%) / ??h:?? %s%s",
|
|
curr.batt.state_of_charge,
|
|
curr.batt.display_charge / 10,
|
|
curr.batt.display_charge % 10,
|
|
to_full ? "to full" : "to empty",
|
|
is_full ? ", not accepting current" : "");
|
|
else
|
|
CPRINTS("Battery %d%% (Display %d.%d %%) / %dh:%d %s%s",
|
|
curr.batt.state_of_charge,
|
|
curr.batt.display_charge / 10,
|
|
curr.batt.display_charge % 10,
|
|
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_normal);
|
|
#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;
|
|
}
|
|
|
|
void chgstate_set_manual_current(int curr_ma)
|
|
{
|
|
if (curr_ma < 0)
|
|
manual_current = -1;
|
|
else
|
|
manual_current = charger_closest_current(curr_ma);
|
|
}
|
|
|
|
void chgstate_set_manual_voltage(int volt_mv)
|
|
{
|
|
manual_voltage = charger_closest_voltage(volt_mv);
|
|
}
|
|
|
|
/* 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_current = -1;
|
|
manual_voltage = -1;
|
|
} 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;
|
|
manual_current = 0;
|
|
manual_voltage = 0;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
__attribute__((weak)) uint8_t board_set_battery_level_shutdown(void)
|
|
{
|
|
return BATTERY_LEVEL_SHUTDOWN;
|
|
}
|
|
|
|
/* 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));
|
|
}
|
|
|
|
__attribute__((weak))
|
|
enum critical_shutdown board_critical_shutdown_check(
|
|
struct charge_state_data *curr)
|
|
{
|
|
#ifdef CONFIG_BATTERY_CRITICAL_SHUTDOWN_CUT_OFF
|
|
return CRITICAL_SHUTDOWN_CUTOFF;
|
|
#elif defined(CONFIG_HIBERNATE)
|
|
return CRITICAL_SHUTDOWN_HIBERNATE;
|
|
#else
|
|
return CRITICAL_SHUTDOWN_IGNORE;
|
|
#endif
|
|
}
|
|
|
|
static int is_battery_critical(void)
|
|
{
|
|
int batt_temp_c = DECI_KELVIN_TO_CELSIUS(curr.batt.temperature);
|
|
|
|
/*
|
|
* TODO(crosbug.com/p/27642): The thermal loop should watch the battery
|
|
* temp, so it can turn fans on.
|
|
*/
|
|
if (battery_too_hot(batt_temp_c)) {
|
|
CPRINTS("Batt temp out of range: %dC", batt_temp_c);
|
|
return 1;
|
|
}
|
|
|
|
if (battery_too_low() && !curr.batt_is_charging) {
|
|
CPRINTS("Low battery: %d%%, %dmV",
|
|
curr.batt.state_of_charge, curr.batt.voltage);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If the battery is at extremely low charge (and discharging) or extremely
|
|
* high temperature, the EC will notify the AP and start a timer. If the
|
|
* critical condition is not corrected before the timeout expires, the EC
|
|
* will shut down the AP (if the AP is not already off) and then optionally
|
|
* hibernate or cut off battery.
|
|
*/
|
|
static int shutdown_on_critical_battery(void)
|
|
{
|
|
if (!is_battery_critical()) {
|
|
/* Reset shutdown warning time */
|
|
shutdown_target_time.val = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (!shutdown_target_time.val) {
|
|
/* Start count down timer */
|
|
CPRINTS("Start shutdown due to critical battery");
|
|
shutdown_target_time.val = get_time().val
|
|
+ CRITICAL_BATTERY_SHUTDOWN_TIMEOUT_US;
|
|
#ifdef CONFIG_HOSTCMD_EVENTS
|
|
if (!chipset_in_state(CHIPSET_STATE_ANY_OFF))
|
|
host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
if (!timestamp_expired(shutdown_target_time, 0))
|
|
return 1;
|
|
|
|
/* Timer has expired */
|
|
if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
|
|
switch (board_critical_shutdown_check(&curr)) {
|
|
case CRITICAL_SHUTDOWN_HIBERNATE:
|
|
CPRINTS("Hibernate due to critical battery");
|
|
system_hibernate(0, 0);
|
|
break;
|
|
case CRITICAL_SHUTDOWN_CUTOFF:
|
|
CPRINTS("Cutoff due to critical battery");
|
|
/* Ensure logs are flushed. */
|
|
cflush();
|
|
board_cut_off_battery();
|
|
break;
|
|
case CRITICAL_SHUTDOWN_IGNORE:
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
/* Timeout waiting for AP to shut down, so kill it */
|
|
CPRINTS(
|
|
"charge force shutdown due to critical battery");
|
|
chipset_force_shutdown(CHIPSET_SHUTDOWN_BATTERY_CRIT);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* 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_charge(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
|
|
}
|
|
|
|
static void set_charge_state(enum charge_state_v2 state)
|
|
{
|
|
prev_state = curr.state;
|
|
curr.state = state;
|
|
}
|
|
|
|
static void notify_host_of_low_battery_voltage(void)
|
|
{
|
|
#ifdef CONFIG_THROTTLE_AP_ON_BAT_VOLTAGE
|
|
if ((curr.batt.flags & BATT_FLAG_BAD_VOLTAGE) ||
|
|
chipset_in_state(CHIPSET_STATE_ANY_OFF))
|
|
return;
|
|
|
|
if (!uvp_throttle_start_time.val &&
|
|
(curr.batt.voltage < BAT_LOW_VOLTAGE_THRESH)) {
|
|
throttle_ap(THROTTLE_ON, THROTTLE_SOFT,
|
|
THROTTLE_SRC_BAT_VOLTAGE);
|
|
uvp_throttle_start_time = get_time();
|
|
} else if (uvp_throttle_start_time.val &&
|
|
(curr.batt.voltage < BAT_LOW_VOLTAGE_THRESH +
|
|
BAT_UVP_HYSTERESIS)) {
|
|
/*
|
|
* Reset the timer when we are not sure if VBAT can stay
|
|
* above BAT_LOW_VOLTAGE_THRESH after we stop throttling.
|
|
*/
|
|
uvp_throttle_start_time = get_time();
|
|
} else if (uvp_throttle_start_time.val &&
|
|
(get_time().val > uvp_throttle_start_time.val +
|
|
BAT_UVP_TIMEOUT_US)) {
|
|
throttle_ap(THROTTLE_OFF, THROTTLE_SOFT,
|
|
THROTTLE_SRC_BAT_VOLTAGE);
|
|
uvp_throttle_start_time.val = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void notify_host_of_over_current(struct batt_params *batt)
|
|
{
|
|
#ifdef CONFIG_THROTTLE_AP_ON_BAT_DISCHG_CURRENT
|
|
static timestamp_t ocp_throttle_start_time;
|
|
|
|
if (batt->flags & BATT_FLAG_BAD_CURRENT)
|
|
return;
|
|
|
|
if ((!ocp_throttle_start_time.val &&
|
|
(batt->current < -BAT_MAX_DISCHG_CURRENT)) ||
|
|
(ocp_throttle_start_time.val &&
|
|
(batt->current < -BAT_MAX_DISCHG_CURRENT + BAT_OCP_HYSTERESIS))) {
|
|
ocp_throttle_start_time = get_time();
|
|
throttle_ap(THROTTLE_ON, THROTTLE_SOFT,
|
|
THROTTLE_SRC_BAT_DISCHG_CURRENT);
|
|
} else if (ocp_throttle_start_time.val &&
|
|
(get_time().val > ocp_throttle_start_time.val +
|
|
BAT_OCP_TIMEOUT_US)) {
|
|
/*
|
|
* Clear the timer and notify AP to stop throttling if
|
|
* we haven't seen over current for BAT_OCP_TIMEOUT_US.
|
|
*/
|
|
ocp_throttle_start_time.val = 0;
|
|
throttle_ap(THROTTLE_OFF, THROTTLE_SOFT,
|
|
THROTTLE_SRC_BAT_DISCHG_CURRENT);
|
|
}
|
|
#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;
|
|
/* Manual voltage/current set to off */
|
|
manual_voltage = -1;
|
|
manual_current = -1;
|
|
}
|
|
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);
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
/* 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)
|
|
{
|
|
uvp_throttle_start_time.val = 0;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN,
|
|
bat_low_voltage_throttle_reset,
|
|
HOOK_PRIO_DEFAULT);
|
|
#endif
|
|
|
|
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 = prev_disp_charge = -1;
|
|
chg_ctl_mode = CHARGE_CONTROL_NORMAL;
|
|
shutdown_target_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;
|
|
battery_dynamic[BATT_IDX_BASE].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 = BP_NOT_INIT;
|
|
curr.desired_input_current = get_desired_input_current(
|
|
curr.batt.is_present, info);
|
|
|
|
battery_level_shutdown = board_set_battery_level_shutdown();
|
|
|
|
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;
|
|
|
|
/* System is off: if AC gets connected, reset the base. */
|
|
if (chipset_in_state(CHIPSET_STATE_ANY_OFF) &&
|
|
!prev_ac && curr.ac)
|
|
board_base_reset();
|
|
#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
|
|
update_base_battery_info();
|
|
#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;
|
|
}
|
|
|
|
notify_host_of_over_current(&curr.batt);
|
|
|
|
/*
|
|
* 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");
|
|
set_charge_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) {
|
|
set_charge_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) {
|
|
set_charge_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 */
|
|
set_charge_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;
|
|
set_charge_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;
|
|
}
|
|
set_charge_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
|
|
/*
|
|
* Always check the disconnect state. This is because
|
|
* the battery disconnect state is one of the items used
|
|
* to decide whether or not to leave safe mode.
|
|
*/
|
|
battery_seems_to_be_disconnected =
|
|
battery_get_disconnect_state() ==
|
|
BATTERY_DISCONNECTED;
|
|
|
|
if (curr.requested_voltage == 0 &&
|
|
curr.requested_current == 0 &&
|
|
battery_seems_to_be_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;
|
|
} 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;
|
|
set_charge_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) {
|
|
/*
|
|
* Sometimes the fuel gauge will report that it has
|
|
* sufficient state of charge and remaining capacity,
|
|
* but in actuality it doesn't. When the EC sees that
|
|
* information, it trusts it and leaves charge manager
|
|
* safe mode. Doing so will allow CHARGE_PORT_NONE to
|
|
* be selected, thereby cutting off the input FETs.
|
|
* When the battery cannot provide the charge it claims,
|
|
* the system loses power, shuts down, and the battery
|
|
* is not charged even though the charger is plugged in.
|
|
* By waiting 500ms, we can avoid the selection of
|
|
* CHARGE_PORT_NONE around init time and not cut off the
|
|
* input FETs.
|
|
*/
|
|
msleep(500);
|
|
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_charge();
|
|
notify_host_of_low_battery_voltage();
|
|
|
|
/* 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) ||
|
|
(curr.state != prev_state) ||
|
|
(curr.batt.display_charge != prev_disp_charge)) {
|
|
show_charging_progress();
|
|
prev_charge = curr.batt.state_of_charge;
|
|
prev_disp_charge = curr.batt.display_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_voltage != -1)
|
|
curr.requested_voltage = manual_voltage;
|
|
if (manual_current != -1)
|
|
curr.requested_current = manual_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;
|
|
struct batt_params params;
|
|
struct batt_params *current_batt_params = &curr.batt;
|
|
#ifdef CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON
|
|
static int automatic_power_on = 1;
|
|
#endif
|
|
|
|
/* 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;
|
|
}
|
|
|
|
#ifdef CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON
|
|
|
|
/*
|
|
* 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;
|
|
/*
|
|
* Require a minimum battery level to power on and ensure that the
|
|
* battery can provide power to the system.
|
|
*/
|
|
if (current_batt_params->is_present != BP_YES ||
|
|
#ifdef CONFIG_BATTERY_MEASURE_IMBALANCE
|
|
(current_batt_params->flags & BATT_FLAG_IMBALANCED_CELL &&
|
|
current_batt_params->state_of_charge <
|
|
CONFIG_CHARGER_MIN_BAT_PCT_IMBALANCED_POWER_ON) ||
|
|
#endif
|
|
#ifdef CONFIG_BATTERY_REVIVE_DISCONNECT
|
|
battery_get_disconnect_state() != BATTERY_NOT_DISCONNECTED ||
|
|
#endif
|
|
current_batt_params->state_of_charge <
|
|
CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON)
|
|
prevent_power_on = 1;
|
|
|
|
#if defined(CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON) && \
|
|
defined(CONFIG_CHARGE_MANAGER)
|
|
/* However, we can power on if a sufficient charger is present. */
|
|
if (prevent_power_on) {
|
|
if (charge_manager_get_power_limit_uw() >=
|
|
CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON * 1000)
|
|
prevent_power_on = 0;
|
|
#if defined(CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON_WITH_BATT) && \
|
|
defined(CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON_WITH_AC)
|
|
else if (charge_manager_get_power_limit_uw() >=
|
|
CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON_WITH_BATT * 1000
|
|
#ifdef CONFIG_BATTERY_REVIVE_DISCONNECT
|
|
&& battery_get_disconnect_state() ==
|
|
BATTERY_NOT_DISCONNECTED
|
|
#endif
|
|
&& (current_batt_params->state_of_charge >=
|
|
CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON_WITH_AC))
|
|
prevent_power_on = 0;
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_CHARGE_MANAGER && CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON */
|
|
|
|
/*
|
|
* 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 /* CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON */
|
|
|
|
#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
|
|
* a sufficient charger is present.
|
|
*/
|
|
if (extpower_is_present() && battery_hw_present() == BP_NO
|
|
#ifdef CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON
|
|
&& charge_manager_get_power_limit_uw() <
|
|
CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON * 1000
|
|
#endif /* CONFIG_CHARGER_MIN_POWER_MW_FOR_POWER_ON */
|
|
)
|
|
prevent_power_on = 1;
|
|
#endif /* CONFIG_BATTERY_HW_PRESENT_CUSTOM */
|
|
#endif /* CONFIG_CHARGE_MANAGER */
|
|
|
|
/*
|
|
* Prevent power on if there is no battery nor ac power. This
|
|
* happens when the servo is powering the EC to flash it. Only include
|
|
* this logic for boards in initial bring up phase since this won't
|
|
* happen for released boards.
|
|
*/
|
|
#ifdef CONFIG_SYSTEM_UNLOCKED
|
|
if (!current_batt_params->is_present && !curr.ac)
|
|
prevent_power_on = 1;
|
|
#endif /* CONFIG_SYSTEM_UNLOCKED */
|
|
|
|
return prevent_power_on;
|
|
}
|
|
|
|
static int battery_near_full(void)
|
|
{
|
|
if (charge_get_percent() < BATTERY_LEVEL_NEAR_FULL)
|
|
return 0;
|
|
|
|
#ifdef CONFIG_EC_EC_COMM_BATTERY_MASTER
|
|
if (charge_base > -1 && charge_base < BATTERY_LEVEL_NEAR_FULL)
|
|
return 0;
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
enum charge_state charge_get_state(void)
|
|
{
|
|
switch (curr.state) {
|
|
case ST_IDLE:
|
|
if (battery_seems_to_be_dead || curr.batt.is_present == BP_NO)
|
|
return PWR_STATE_ERROR;
|
|
return PWR_STATE_IDLE;
|
|
case ST_DISCHARGE:
|
|
#ifdef CONFIG_PWR_STATE_DISCHARGE_FULL
|
|
if (battery_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 (battery_near_full())
|
|
return PWR_STATE_CHARGE_NEAR_FULL;
|
|
else
|
|
return PWR_STATE_CHARGE;
|
|
case ST_PRECHARGE:
|
|
/* we're in battery discovery mode */
|
|
return PWR_STATE_IDLE;
|
|
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_display_charge(void)
|
|
{
|
|
return curr.batt.display_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 enum ec_status
|
|
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 enum ec_status
|
|
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 enum ec_status
|
|
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_CHG_MW
|
|
/*
|
|
* 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:
|
|
chgstate_set_manual_voltage(val);
|
|
break;
|
|
case CS_PARAM_CHG_CURRENT:
|
|
chgstate_set_manual_current(val);
|
|
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_EC_EC_COMM_BATTERY_MASTER
|
|
static int command_chgdualdebug(int argc, 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
|
|
|
|
#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_CURRENT:
|
|
*value = manual_current;
|
|
break;
|
|
case CS_PARAM_DEBUG_MANUAL_VOLTAGE:
|
|
*value = manual_voltage;
|
|
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
|