/* Copyright 2016 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. */ /* Virtual battery cross-platform code for Chrome EC */ #include "battery.h" #include "charge_state.h" #include "i2c.h" #include "system.h" #include "util.h" #include "virtual_battery.h" /* Console output macros */ #define CPUTS(outstr) cputs(CC_I2C, outstr) #define CPRINTS(format, args...) cprints(CC_I2C, format, ## args) /* * The state machine used to parse smart battery command * to support virtual battery. */ enum batt_cmd_parse_state { IDLE = 0, /* initial state */ START = 1, /* received the register address (command code) */ WRITE_VB, /* writing data bytes to the slave */ READ_VB, /* reading data bytes to the slave */ }; static enum batt_cmd_parse_state sb_cmd_state; static uint8_t cache_hit; static const uint8_t *batt_cmd_head; static int acc_write_len; int virtual_battery_handler(struct ec_response_i2c_passthru *resp, int in_len, int *err_code, int xferflags, int read_len, int write_len, const uint8_t *out) { #if defined(CONFIG_BATTERY_PRESENT_GPIO) || \ defined(CONFIG_BATTERY_PRESENT_CUSTOM) /* * If the battery isn't present, return a NAK (which we * would have gotten anyways had we attempted to talk to * the battery.) */ if (battery_is_present() != BP_YES) { resp->i2c_status = EC_I2C_STATUS_NAK; return EC_ERROR_INVAL; } #endif switch (sb_cmd_state) { case IDLE: /* * A legal battery command must start * with a i2c write for reg index. */ if (write_len == 0) { resp->i2c_status = EC_I2C_STATUS_NAK; return EC_ERROR_INVAL; } /* Record the head of battery command. */ batt_cmd_head = out; sb_cmd_state = START; *err_code = 0; break; case START: if (write_len > 0) { sb_cmd_state = WRITE_VB; *err_code = 0; } else { sb_cmd_state = READ_VB; /* Test if the reg is cached. */ *err_code = virtual_battery_operation(batt_cmd_head, NULL, 0, 0); /* * If the reg is not cached in the virtual memory, * we need to physically write the reg index to * the battery. */ if (*err_code) { *err_code = i2c_xfer( I2C_PORT_VIRTUAL_BATTERY, VIRTUAL_BATTERY_ADDR, batt_cmd_head, 1, NULL, 0, I2C_XFER_START); /* sent a stop bit here */ if (*err_code) { if (*err_code == EC_ERROR_TIMEOUT) { resp->i2c_status = EC_I2C_STATUS_TIMEOUT; } else { resp->i2c_status = EC_I2C_STATUS_NAK; } reset_parse_state(); return EC_ERROR_INVAL; } *err_code = 1; } else cache_hit = 1; } break; case WRITE_VB: if (write_len == 0) { resp->i2c_status = EC_I2C_STATUS_NAK; reset_parse_state(); return EC_ERROR_INVAL; } *err_code = 0; break; case READ_VB: if (read_len == 0) { resp->i2c_status = EC_I2C_STATUS_NAK; reset_parse_state(); return EC_ERROR_INVAL; } /* * Do not send the command to battery * if the reg is cached. */ if (cache_hit) *err_code = 0; break; default: reset_parse_state(); return EC_ERROR_INVAL; } acc_write_len += write_len; /* the last message */ if (xferflags & I2C_XFER_STOP) { switch (sb_cmd_state) { /* write to virtual battery */ case START: case WRITE_VB: virtual_battery_operation(batt_cmd_head, &resp->data[in_len], 0, acc_write_len); break; /* read from virtual battery */ case READ_VB: if (cache_hit) { virtual_battery_operation(batt_cmd_head, &resp->data[0], in_len + read_len, 0); } break; default: reset_parse_state(); return EC_ERROR_INVAL; } /* Reset the state in the end of messages */ reset_parse_state(); } return EC_RES_SUCCESS; } void reset_parse_state(void) { sb_cmd_state = IDLE; cache_hit = 0; acc_write_len = 0; } int virtual_battery_operation(const uint8_t *batt_cmd_head, uint8_t *dest, int read_len, int write_len) { int val; /* * We cache battery operational mode locally for both read and write * commands. If MODE_CAPACITY bit is set, battery capacity will be * reported in 10mW/10mWh, instead of the default unit, mA/mAh. * Note that we don't update the cached capacity: We do a real-time * conversion and return the converted values. */ static uint16_t batt_mode_cache; const struct batt_params *curr_batt; curr_batt = charger_current_battery_params(); switch (*batt_cmd_head) { case SB_BATTERY_MODE: if (write_len == 3) { batt_mode_cache = batt_cmd_head[1] | (batt_cmd_head[2] << 8); } else if (read_len > 0) { if (batt_mode_cache == 0) { /* * Read the battery operational mode from * the battery to initialize batt_mode_cache. */ i2c_xfer(I2C_PORT_VIRTUAL_BATTERY, VIRTUAL_BATTERY_ADDR, batt_cmd_head, 1, (uint8_t *)&batt_mode_cache, 2, I2C_XFER_SINGLE); } memcpy(dest, &batt_mode_cache, read_len); } break; case SB_SERIAL_NUMBER: val = strtoi(host_get_memmap(EC_MEMMAP_BATT_SERIAL), NULL, 16); memcpy(dest, &val, read_len); break; case SB_VOLTAGE: memcpy(dest, &(curr_batt->voltage), read_len); break; case SB_RELATIVE_STATE_OF_CHARGE: memcpy(dest, &(curr_batt->state_of_charge), read_len); break; case SB_TEMPERATURE: memcpy(dest, &(curr_batt->temperature), read_len); break; case SB_CURRENT: memcpy(dest, &(curr_batt->current), read_len); break; case SB_FULL_CHARGE_CAPACITY: val = curr_batt->full_capacity; if (batt_mode_cache & MODE_CAPACITY) val = val * curr_batt->voltage / 10; memcpy(dest, &val, read_len); break; case SB_BATTERY_STATUS: memcpy(dest, &(curr_batt->status), read_len); break; case SB_CYCLE_COUNT: memcpy(dest, (int *)host_get_memmap(EC_MEMMAP_BATT_CCNT), read_len); break; case SB_DESIGN_CAPACITY: val = *(int *)host_get_memmap(EC_MEMMAP_BATT_DCAP); if (batt_mode_cache & MODE_CAPACITY) val = val * curr_batt->voltage / 10; memcpy(dest, &val, read_len); break; case SB_DESIGN_VOLTAGE: memcpy(dest, (int *)host_get_memmap(EC_MEMMAP_BATT_DVLT), read_len); break; case SB_REMAINING_CAPACITY: val = curr_batt->remaining_capacity; if (batt_mode_cache & MODE_CAPACITY) val = val * curr_batt->voltage / 10; memcpy(dest, &val, read_len); break; default: return EC_ERROR_INVAL; } return EC_SUCCESS; }