| /* Copyright 2021 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Battery V2 APIs. |
| */ |
| |
| #include "battery.h" |
| #include "charge_state.h" |
| #include "common.h" |
| #include "console.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "math_util.h" |
| #include "printf.h" |
| #include "util.h" |
| |
| #define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ##args) |
| #define CPRINTS(format, args...) cprints(CC_CHARGER, format, ##args) |
| |
| /* |
| * Store battery information in these 2 structures. Main (lid) battery is always |
| * at index 0, and secondary (base) battery at index 1. |
| */ |
| struct battery_static_info battery_static[CONFIG_BATTERY_COUNT]; |
| struct ec_response_battery_dynamic_info battery_dynamic[CONFIG_BATTERY_COUNT]; |
| |
| #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_ext, EC_MEMMAP_TEXT_MAX); |
| batt_str[EC_MEMMAP_TEXT_MAX - 1] = 0; |
| |
| /* 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_ext, |
| EC_MEMMAP_TEXT_MAX); |
| batt_str[EC_MEMMAP_TEXT_MAX - 1] = 0; |
| |
| /* Battery Model string */ |
| batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_MODEL); |
| memcpy(batt_str, battery_static[i].model_ext, EC_MEMMAP_TEXT_MAX); |
| batt_str[EC_MEMMAP_TEXT_MAX - 1] = 0; |
| |
| /* Battery Type string */ |
| batt_str = (char *)host_get_memmap(EC_MEMMAP_BATT_TYPE); |
| memcpy(batt_str, battery_static[i].type_ext, EC_MEMMAP_TEXT_MAX); |
| batt_str[EC_MEMMAP_TEXT_MAX - 1] = 0; |
| |
| *memmap_volt = battery_dynamic[i].actual_voltage; |
| /* |
| * Rate must be absolute, flags will indicate whether |
| * the battery is charging or discharging. |
| */ |
| *memmap_rate = ABS(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; |
| } |
| |
| #ifdef CONFIG_HOSTCMD_BATTERY_V2 |
| static enum ec_status |
| host_command_battery_get_static(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_battery_static_info *p = args->params; |
| const struct battery_static_info *bs; |
| |
| if (p->index >= CONFIG_BATTERY_COUNT) |
| return EC_RES_INVALID_PARAM; |
| bs = &battery_static[p->index]; |
| |
| battery_update(p->index); |
| if (args->version == 0) { |
| struct ec_response_battery_static_info *r = args->response; |
| |
| r->design_capacity = bs->design_capacity; |
| r->design_voltage = bs->design_voltage; |
| r->cycle_count = bs->cycle_count; |
| |
| strzcpy(r->manufacturer, bs->manufacturer_ext, |
| sizeof(r->manufacturer)); |
| strzcpy(r->model, bs->model_ext, sizeof(r->model)); |
| strzcpy(r->serial, bs->serial_ext, sizeof(r->serial)); |
| strzcpy(r->type, bs->type_ext, sizeof(r->type)); |
| |
| args->response_size = sizeof(*r); |
| } else if (args->version == 1) { |
| struct ec_response_battery_static_info_v1 *r = args->response; |
| |
| r->design_capacity = bs->design_capacity; |
| r->design_voltage = bs->design_voltage; |
| r->cycle_count = bs->cycle_count; |
| |
| strzcpy(r->manufacturer_ext, bs->manufacturer_ext, |
| sizeof(r->manufacturer_ext)); |
| strzcpy(r->model_ext, bs->model_ext, sizeof(r->model_ext)); |
| strzcpy(r->serial_ext, bs->serial_ext, sizeof(r->serial_ext)); |
| strzcpy(r->type_ext, bs->type_ext, sizeof(r->type_ext)); |
| |
| args->response_size = sizeof(*r); |
| } else if (args->version == 2) { |
| struct ec_response_battery_static_info_v2 *r = args->response; |
| |
| r->design_capacity = bs->design_capacity; |
| r->design_voltage = bs->design_voltage; |
| r->cycle_count = bs->cycle_count; |
| |
| strzcpy(r->manufacturer, bs->manufacturer_ext, |
| sizeof(r->manufacturer)); |
| strzcpy(r->device_name, bs->model_ext, sizeof(r->device_name)); |
| strzcpy(r->serial, bs->serial_ext, sizeof(r->serial)); |
| strzcpy(r->chemistry, bs->type_ext, sizeof(r->chemistry)); |
| |
| args->response_size = sizeof(*r); |
| } else { |
| return EC_RES_INVALID_VERSION; |
| } |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_BATTERY_GET_STATIC, host_command_battery_get_static, |
| EC_VER_MASK(0) | EC_VER_MASK(1) | EC_VER_MASK(2)); |
| |
| static enum ec_status |
| 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 >= 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 */ |
| |
| 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 */ |
| |
| static int is_battery_string_reliable(const char *buf) |
| { |
| /* |
| * From is_string_printable rule, 0xFF is not printable. |
| * So, EC should think battery string is unreliable if string |
| * include 0xFF. |
| */ |
| while (*buf) { |
| if ((*buf) == '\xff') |
| return 0; |
| buf++; |
| } |
| |
| return 1; |
| } |
| |
| 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 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) |
| if (snprintf(bs->serial_ext, sizeof(bs->serial_ext), "%04X", |
| batt_serial) <= 0) |
| rv |= EC_ERROR_UNKNOWN; |
| |
| /* 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_ext, |
| sizeof(bs->manufacturer_ext)); |
| |
| /* Battery Model string */ |
| rv |= battery_device_name(bs->model_ext, sizeof(bs->model_ext)); |
| |
| /* Battery Type string */ |
| rv |= battery_device_chemistry(bs->type_ext, sizeof(bs->type_ext)); |
| |
| /* |
| * b/181639264: Battery gauge follow SMBus SPEC and SMBus define |
| * cumulative clock low extend time for both controller (master) and |
| * peripheral (slave). However, I2C doesn't. |
| * Regarding this issue, we observe EC sometimes pull I2C CLK low |
| * a while after EC start running. Actually, we are not sure the |
| * reason until now. |
| * If EC pull I2C CLK low too long, and it may cause battery fw timeout |
| * because battery count cumulative clock extend time over 25ms. |
| * When it happened, battery will release both its CLK and DATA and |
| * reset itself. So, EC may get 0xFF when EC keep reading data from |
| * battery. Battery static information will be unreliable and need to |
| * be updated. |
| * This change is improvement that EC should retry if battery string is |
| * unreliable. |
| */ |
| if (!is_battery_string_reliable(bs->serial_ext) || |
| !is_battery_string_reliable(bs->manufacturer_ext) || |
| !is_battery_string_reliable(bs->model_ext) || |
| !is_battery_string_reliable(bs->type_ext)) |
| rv |= EC_ERROR_UNKNOWN; |
| |
| /* Zero the dynamic entries. They'll come next. */ |
| memset(&battery_dynamic[BATT_IDX_MAIN], 0, |
| sizeof(battery_dynamic[BATT_IDX_MAIN])); |
| |
| if (rv) |
| charge_problem(PR_STATIC_UPDATE, rv); |
| |
| #ifdef HAS_TASK_HOSTCMD |
| battery_memmap_refresh(BATT_IDX_MAIN); |
| #endif |
| |
| return rv; |
| } |
| |
| void update_dynamic_battery_info(void) |
| { |
| static int batt_present; |
| uint8_t tmp; |
| __maybe_unused int send_batt_status_event = 0; |
| __maybe_unused int send_batt_info_event = 0; |
| struct charge_state_data *curr; |
| struct ec_response_battery_dynamic_info *const bd = |
| &battery_dynamic[BATT_IDX_MAIN]; |
| |
| curr = charge_get_status(); |
| 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 & BATT_FLAG_BAD_ANY) |
| 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 && |
| battery_is_below_threshold(BATT_THRESHOLD_TYPE_SHUTDOWN, false)) |
| tmp |= EC_BATT_FLAG_LEVEL_CRITICAL; |
| |
| tmp |= curr->batt_is_charging ? EC_BATT_FLAG_CHARGING : |
| EC_BATT_FLAG_DISCHARGING; |
| |
| if (battery_is_cut_off()) |
| tmp |= EC_BATT_FLAG_CUT_OFF; |
| |
| /* 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 |
| } |