/* Copyright 2012 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. * * Common battery command. */ #include "battery.h" #include "charge_state.h" #include "common.h" #include "console.h" #include "ec_ec_comm_master.h" #include "extpower.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" #include "timer.h" #include "util.h" #include "watchdog.h" #define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args) #define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) /* See config.h for details */ const static int batt_full_factor = CONFIG_BATT_FULL_FACTOR; const static int batt_host_full_factor = CONFIG_BATT_HOST_FULL_FACTOR; const static int batt_host_shutdown_pct = CONFIG_BATT_HOST_SHUTDOWN_PERCENTAGE; #ifdef CONFIG_BATTERY_V2 /* * Store battery information in these 2 structures. Main (lid) battery is always * at index 0, and secondary (base) battery at index 1. */ struct ec_response_battery_static_info battery_static[CONFIG_BATTERY_COUNT]; struct ec_response_battery_dynamic_info battery_dynamic[CONFIG_BATTERY_COUNT]; #endif #ifdef CONFIG_BATTERY_CUT_OFF #ifndef CONFIG_BATTERY_CUTOFF_DELAY_US #define CONFIG_BATTERY_CUTOFF_DELAY_US (1 * SECOND) #endif static enum battery_cutoff_states battery_cutoff_state = BATTERY_CUTOFF_STATE_NORMAL; #endif #ifdef CONFIG_BATTERY_PRESENT_GPIO #ifdef CONFIG_BATTERY_PRESENT_CUSTOM #error "Don't define both CONFIG_BATTERY_PRESENT_CUSTOM and" \ "CONFIG_BATTERY_PRESENT_GPIO" #endif /** * Physical detection of battery. */ enum battery_present battery_is_present(void) { /* The GPIO is low when the battery is present */ return gpio_get_level(CONFIG_BATTERY_PRESENT_GPIO) ? BP_NO : BP_YES; } #endif static const char *get_error_text(int rv) { if (rv == EC_ERROR_UNIMPLEMENTED) return "(unsupported)"; else return "(error)"; } static void print_item_name(const char *name) { ccprintf(" %-11s", name); } static int check_print_error(int rv) { if (rv != EC_SUCCESS) ccprintf("%s\n", get_error_text(rv)); return rv == EC_SUCCESS; } static void print_battery_status(void) { static const char * const st[] = {"EMPTY", "FULL", "DCHG", "INIT",}; static const char * const al[] = {"RT", "RC", "--", "TD", "OT", "--", "TC", "OC"}; int value, i; print_item_name("Status:"); if (check_print_error(battery_status(&value))) { ccprintf("0x%04x", value); /* bits 0-3 are only valid when the previous transaction * failed, so ignore them */ /* bits 4-7 are status */ for (i = 0; i < 4; i++) if (value & (1 << (i+4))) ccprintf(" %s", st[i]); /* bits 15-8 are alarms */ for (i = 0; i < 8; i++) if (value & (1 << (i+8))) ccprintf(" %s", al[i]); ccprintf("\n"); } } static void print_battery_strings(void) { char text[32]; print_item_name("Manuf:"); if (check_print_error(battery_manufacturer_name(text, sizeof(text)))) ccprintf("%s\n", text); print_item_name("Device:"); if (check_print_error(battery_device_name(text, sizeof(text)))) ccprintf("%s\n", text); print_item_name("Chem:"); if (check_print_error(battery_device_chemistry(text, sizeof(text)))) ccprintf("%s\n", text); } static void print_battery_params(void) { #if defined(HAS_TASK_CHARGER) /* Ask charger so that we don't need to ask battery again. */ const struct batt_params *batt = charger_current_battery_params(); #else /* This is for test code, where doesn't have charger task. */ struct batt_params _batt; const struct batt_params *batt = &_batt; battery_get_params(&_batt); #endif print_item_name("Param flags:"); ccprintf("%08x\n", batt->flags); print_item_name("Temp:"); ccprintf("0x%04x = %.1d K (%.1d C)\n", batt->temperature, batt->temperature, batt->temperature - 2731); print_item_name("V:"); ccprintf("0x%04x = %d mV\n", batt->voltage, batt->voltage); print_item_name("V-desired:"); ccprintf("0x%04x = %d mV\n", batt->desired_voltage, batt->desired_voltage); print_item_name("I:"); ccprintf("0x%04x = %d mA", batt->current & 0xffff, batt->current); if (batt->current > 0) ccputs("(CHG)"); else if (batt->current < 0) ccputs("(DISCHG)"); ccputs("\n"); print_item_name("I-desired:"); ccprintf("0x%04x = %d mA\n", batt->desired_current, batt->desired_current); print_item_name("Charging:"); ccprintf("%sAllowed\n", batt->flags & BATT_FLAG_WANT_CHARGE ? "" : "Not "); print_item_name("Charge:"); ccprintf("%d %%\n", batt->state_of_charge); } static void print_battery_info(void) { int value; int hour, minute; print_item_name("Serial:"); if (check_print_error(battery_serial_number(&value))) ccprintf("0x%04x\n", value); print_item_name("V-design:"); if (check_print_error(battery_design_voltage(&value))) ccprintf("0x%04x = %d mV\n", value, value); print_item_name("Mode:"); if (check_print_error(battery_get_mode(&value))) ccprintf("0x%04x\n", value); print_item_name("Abs charge:"); if (check_print_error(battery_state_of_charge_abs(&value))) ccprintf("%d %%\n", value); print_item_name("Remaining:"); if (check_print_error(battery_remaining_capacity(&value))) ccprintf("%d mAh\n", value); print_item_name("Cap-full:"); if (check_print_error(battery_full_charge_capacity(&value))) ccprintf("%d mAh (%d mAh with %d %% compensation)\n", value, value*batt_full_factor/100, batt_full_factor); #ifdef CONFIG_CHARGER_V2 print_item_name("Display:"); value = charge_get_display_charge(); ccprintf("%d.%d %%\n", value / 10, value % 10); #endif print_item_name(" Design:"); if (check_print_error(battery_design_capacity(&value))) ccprintf("%d mAh\n", value); print_item_name("Time-full:"); if (check_print_error(battery_time_to_full(&value))) { if (value == 65535) { hour = 0; minute = 0; } else { hour = value / 60; minute = value % 60; } ccprintf("%dh:%d\n", hour, minute); } print_item_name(" Empty:"); if (check_print_error(battery_time_to_empty(&value))) { if (value == 65535) { hour = 0; minute = 0; } else { hour = value / 60; minute = value % 60; } ccprintf("%dh:%d\n", hour, minute); } } void print_battery_debug(void) { print_battery_status(); print_battery_params(); print_battery_strings(); print_battery_info(); } static int command_battery(int argc, char **argv) { int repeat = 1; int loop; int sleep_ms = 0; char *e; if (argc > 1) { repeat = strtoi(argv[1], &e, 0); if (*e) { ccputs("Invalid repeat count\n"); return EC_ERROR_INVAL; } } if (argc > 2) { sleep_ms = strtoi(argv[2], &e, 0); if (*e) { ccputs("Invalid sleep ms\n"); return EC_ERROR_INVAL; } } for (loop = 0; loop < repeat; loop++) { print_battery_debug(); /* * Running with a high repeat count will take so long the * watchdog timer fires. So reset the watchdog timer each * iteration. */ watchdog_reload(); if (sleep_ms) msleep(sleep_ms); } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(battery, command_battery, " ", "Print battery info"); #ifdef CONFIG_BATTERY_CUT_OFF int battery_is_cut_off(void) { return (battery_cutoff_state == BATTERY_CUTOFF_STATE_CUT_OFF); } static void pending_cutoff_deferred(void) { int rv; rv = board_cut_off_battery(); if (rv == EC_RES_SUCCESS) CPRINTS("Battery cut off succeeded."); else CPRINTS("Battery cut off failed!"); } DECLARE_DEFERRED(pending_cutoff_deferred); static void clear_pending_cutoff(void) { if (extpower_is_present()) { battery_cutoff_state = BATTERY_CUTOFF_STATE_NORMAL; hook_call_deferred(&pending_cutoff_deferred_data, -1); } } DECLARE_HOOK(HOOK_AC_CHANGE, clear_pending_cutoff, HOOK_PRIO_DEFAULT); static int battery_command_cutoff(struct host_cmd_handler_args *args) { const struct ec_params_battery_cutoff *p; int rv; if (args->version == 1) { p = args->params; if (p->flags & EC_BATTERY_CUTOFF_FLAG_AT_SHUTDOWN) { battery_cutoff_state = BATTERY_CUTOFF_STATE_PENDING; CPRINTS("Battery cut off at-shutdown is scheduled"); return EC_RES_SUCCESS; } } rv = board_cut_off_battery(); if (rv == EC_RES_SUCCESS) { CPRINTS("Battery cut off is successful."); battery_cutoff_state = BATTERY_CUTOFF_STATE_CUT_OFF; } else { CPRINTS("Battery cut off has failed."); } return rv; } DECLARE_HOST_COMMAND(EC_CMD_BATTERY_CUT_OFF, battery_command_cutoff, EC_VER_MASK(0) | EC_VER_MASK(1)); static void check_pending_cutoff(void) { if (battery_cutoff_state == BATTERY_CUTOFF_STATE_PENDING) { CPRINTF("[%T Cutting off battery in %d second(s)]\n", CONFIG_BATTERY_CUTOFF_DELAY_US / SECOND); hook_call_deferred(&pending_cutoff_deferred_data, CONFIG_BATTERY_CUTOFF_DELAY_US); } } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, check_pending_cutoff, HOOK_PRIO_LAST); static int command_cutoff(int argc, char **argv) { int rv; if (argc > 1) { if (!strcasecmp(argv[1], "at-shutdown")) { battery_cutoff_state = BATTERY_CUTOFF_STATE_PENDING; return EC_SUCCESS; } else { return EC_ERROR_INVAL; } } rv = board_cut_off_battery(); if (rv == EC_RES_SUCCESS) { ccprints("Battery cut off"); battery_cutoff_state = BATTERY_CUTOFF_STATE_CUT_OFF; return EC_SUCCESS; } return EC_ERROR_UNKNOWN; } DECLARE_CONSOLE_COMMAND(cutoff, command_cutoff, "[at-shutdown]", "Cut off the battery output"); #else int battery_is_cut_off(void) { return 0; /* Always return NOT cut off */ } #endif /* CONFIG_BATTERY_CUT_OFF */ #ifdef CONFIG_BATTERY_VENDOR_PARAM static int console_command_battery_vendor_param(int argc, char **argv) { uint32_t param; uint32_t value; char *e; int rv; if (argc < 2) return EC_ERROR_INVAL; param = strtoi(argv[1], &e, 0); if (*e) { ccputs("Invalid param\n"); return EC_ERROR_INVAL; } if (argc > 2) { value = strtoi(argv[2], &e, 0); if (*e) { ccputs("Invalid value\n"); return EC_ERROR_INVAL; } rv = battery_set_vendor_param(param, value); if (rv != EC_SUCCESS) return rv; } rv = battery_get_vendor_param(param, &value); if (rv != EC_SUCCESS) return rv; ccprintf("0x%08x\n", value); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(battparam, console_command_battery_vendor_param, " [value]", "Get or set battery vendor parameters"); static int host_command_battery_vendor_param(struct host_cmd_handler_args *args) { int rv; const struct ec_params_battery_vendor_param *p = args->params; struct ec_response_battery_vendor_param *r = args->response; args->response_size = sizeof(*r); if (p->mode != BATTERY_VENDOR_PARAM_MODE_GET && p->mode != BATTERY_VENDOR_PARAM_MODE_SET) return EC_RES_INVALID_PARAM; if (p->mode == BATTERY_VENDOR_PARAM_MODE_SET) { rv = battery_set_vendor_param(p->param, p->value); if (rv != EC_SUCCESS) return rv; } rv = battery_get_vendor_param(p->param, &r->value); return rv; } DECLARE_HOST_COMMAND(EC_CMD_BATTERY_VENDOR_PARAM, host_command_battery_vendor_param, EC_VER_MASK(0)); #endif /* CONFIG_BATTERY_VENDOR_PARAM */ #ifdef CONFIG_BATTERY_V2 #ifdef CONFIG_HOSTCMD_BATTERY_V2 static void battery_update(enum battery_index i); static int host_command_battery_get_static(struct host_cmd_handler_args *args) { const struct ec_params_battery_static_info *p = args->params; struct ec_response_battery_static_info *r = args->response; if (p->index < 0 || p->index >= CONFIG_BATTERY_COUNT) return EC_RES_INVALID_PARAM; battery_update(p->index); args->response_size = sizeof(*r); memcpy(r, &battery_static[p->index], sizeof(*r)); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_BATTERY_GET_STATIC, host_command_battery_get_static, EC_VER_MASK(0)); static int host_command_battery_get_dynamic(struct host_cmd_handler_args *args) { const struct ec_params_battery_dynamic_info *p = args->params; struct ec_response_battery_dynamic_info *r = args->response; if (p->index < 0 || p->index >= CONFIG_BATTERY_COUNT) return EC_RES_INVALID_PARAM; args->response_size = sizeof(*r); memcpy(r, &battery_dynamic[p->index], sizeof(*r)); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_BATTERY_GET_DYNAMIC, host_command_battery_get_dynamic, EC_VER_MASK(0)); #endif /* CONFIG_HOSTCMD_BATTERY_V2 */ #ifdef HAS_TASK_HOSTCMD static void battery_update(enum battery_index i) { char *batt_str; int *memmap_dcap = (int *)host_get_memmap(EC_MEMMAP_BATT_DCAP); int *memmap_dvlt = (int *)host_get_memmap(EC_MEMMAP_BATT_DVLT); int *memmap_ccnt = (int *)host_get_memmap(EC_MEMMAP_BATT_CCNT); 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); /* Smart battery serial number is 16 bits */ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_SERIAL); memcpy(batt_str, battery_static[i].serial, EC_MEMMAP_TEXT_MAX); /* Design Capacity of Full */ *memmap_dcap = battery_static[i].design_capacity; /* Design Voltage */ *memmap_dvlt = battery_static[i].design_voltage; /* Cycle Count */ *memmap_ccnt = battery_static[i].cycle_count; /* Battery Manufacturer string */ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MFGR); memcpy(batt_str, battery_static[i].manufacturer, EC_MEMMAP_TEXT_MAX); /* Battery Model string */ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MODEL); memcpy(batt_str, battery_static[i].model, EC_MEMMAP_TEXT_MAX); /* Battery Type string */ batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_TYPE); memcpy(batt_str, battery_static[i].type, EC_MEMMAP_TEXT_MAX); *memmap_volt = battery_dynamic[i].actual_voltage; *memmap_rate = battery_dynamic[i].actual_current; *memmap_cap = battery_dynamic[i].remaining_capacity; *memmap_lfcc = battery_dynamic[i].full_capacity; *memmap_flags = battery_dynamic[i].flags; } void battery_memmap_refresh(enum battery_index index) { if (*host_get_memmap(EC_MEMMAP_BATT_INDEX) == index) battery_update(index); } void battery_memmap_set_index(enum battery_index index) { if (*host_get_memmap(EC_MEMMAP_BATT_INDEX) == index) return; *host_get_memmap(EC_MEMMAP_BATT_INDEX) = BATT_IDX_INVALID; if (index < 0 || index >= CONFIG_BATTERY_COUNT) return; battery_update(index); *host_get_memmap(EC_MEMMAP_BATT_INDEX) = index; } static void battery_init(void) { *host_get_memmap(EC_MEMMAP_BATT_INDEX) = BATT_IDX_INVALID; *host_get_memmap(EC_MEMMAP_BATT_COUNT) = CONFIG_BATTERY_COUNT; *host_get_memmap(EC_MEMMAP_BATTERY_VERSION) = 2; battery_memmap_set_index(BATT_IDX_MAIN); } DECLARE_HOOK(HOOK_INIT, battery_init, HOOK_PRIO_DEFAULT); #endif /* HAS_TASK_HOSTCMD */ #endif /* CONFIG_BATTERY_V2 */ void battery_compensate_params(struct batt_params *batt) { int numer, denom; int remain = batt->remaining_capacity; int full = batt->full_capacity; int lfcc = *(int *)host_get_memmap(EC_MEMMAP_BATT_LFCC); if ((batt->flags & BATT_FLAG_BAD_FULL_CAPACITY) || (batt->flags & BATT_FLAG_BAD_REMAINING_CAPACITY)) return; if (remain <= 0 || full <= 0) return; /* full_factor != 100 isn't supported. EC and host are not able to * act on soc changes synchronously. */ if (batt_host_full_factor != 100) return; /* full_factor is effectively disabled in powerd. */ batt->full_capacity = full * batt_full_factor / 100; if (lfcc == 0) /* EC just reset. Assume host full is equal. */ lfcc = batt->full_capacity; if (remain > lfcc) { batt->remaining_capacity = lfcc; remain = batt->remaining_capacity; } /* * Powerd uses the following equation to calculate display percentage: * charge = 100 * remain/full; * 100 * (charge - shutdown_pct) / (full_factor - shutdown_pct); */ numer = (100 * remain - lfcc * batt_host_shutdown_pct) * 1000; denom = lfcc * (100 - batt_host_shutdown_pct); /* Rounding (instead of truncating) */ batt->display_charge = (numer + denom / 2) / denom; if (batt->display_charge < 0) batt->display_charge = 0; } __attribute__((weak)) int get_battery_manufacturer_name(char *dest, int size) { strzcpy(dest, "", size); return EC_SUCCESS; } int battery_manufacturer_name(char *dest, int size) { return get_battery_manufacturer_name(dest, size); }