| /* 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 "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); |
| |
| /* |
| * 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; |
| 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_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; |
| |
| /* |
| * 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%% / ??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_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)); |
| } |
| |
| /* 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)); |
| } |
| |
| /* |
| * 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) |
| { |
| 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) |
| #ifdef CONFIG_BATTERY_CRITICAL_CUT_OFF_CUSTOM_CONDITION |
| if (!board_critical_shutdown_check(&curr)) |
| return battery_critical; |
| #endif /* CONFIG_BATTERY_CRITICAL_CUT_OFF_CUSTOM_CONDITION */ |
| 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(CHIPSET_SHUTDOWN_BATTERY_CRIT); |
| } |
| } |
| |
| 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_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 = -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; |
| 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); |
| |
| 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)) { |
| 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_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 |
| && (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 (curr.batt.state_of_charge < 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 || battery_was_removed) |
| 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; |
| 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: |
| 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 |