/* Copyright 2021 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 V2 APIs. */ #include "battery.h" #include "charge_state.h" #include "common.h" #include "console.h" #include "hooks.h" #include "host_command.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; *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; } #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 < 0 || 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; /* Truncate strings to reduced v0 size */ memcpy(&r->manufacturer, &bs->manufacturer_ext, sizeof(r->manufacturer)); r->manufacturer[sizeof(r->manufacturer) - 1] = 0; memcpy(&r->model, &bs->model_ext, sizeof(r->model)); r->model[sizeof(r->model) - 1] = 0; memcpy(&r->serial, &bs->serial_ext, sizeof(r->serial)); r->serial[sizeof(r->serial) - 1] = 0; memcpy(&r->type, &bs->type_ext, sizeof(r->type)); r->type[sizeof(r->type) - 1] = 0; 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; /* Truncate strings to reduced size */ memcpy(r->manufacturer_ext, &bs->manufacturer_ext, sizeof(r->manufacturer_ext)); r->manufacturer_ext[sizeof(r->manufacturer_ext) - 1] = 0; memcpy(r->model_ext, &bs->model_ext, sizeof(r->model_ext)); r->model_ext[sizeof(r->model_ext) - 1] = 0; memcpy(r->serial_ext, &bs->serial_ext, sizeof(r->serial_ext)); r->serial_ext[sizeof(r->serial_ext) - 1] = 0; memcpy(r->type_ext, &bs->type_ext, sizeof(r->type_ext)); r->type_ext[sizeof(r->type_ext) - 1] = 0; 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)); 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 < 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 */ 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) == 0xFF) 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) snprintf(bs->serial_ext, sizeof(bs->serial_ext), "%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_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; int send_batt_status_event = 0; 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 & 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 }