/* 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|)|discharge (auto|)]", "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