/* Copyright (c) 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) #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\n", value); 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_SUCCESS) CPRINTF("[%T Battery cut off succeeded.]\n"); else CPRINTF("[%T Battery cut off failed!]\n"); } 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) { 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) { ccprintf("[%T Battery cut off]\n"); battery_cutoff_state = BATTERY_CUTOFF_STATE_CUT_OFF; } return rv; } 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_HOSTCMD_BATTERY_V2 #ifndef CONFIG_BATTERY_V2 #error "CONFIG_HOSTCMD_BATTERY_V2 cannot be set without CONFIG_BATTERY_V2." #endif 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; 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 */