295 lines
7.5 KiB
C
295 lines
7.5 KiB
C
|
/* 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)
|
||
|
|
||
|
#define BATT_MODE_UNINITIALIZED -1
|
||
|
|
||
|
/*
|
||
|
* 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;
|
||
|
*err_code = virtual_battery_operation(batt_cmd_head,
|
||
|
NULL, 0, 0);
|
||
|
/*
|
||
|
* If the reg is not handled by virtual battery, we
|
||
|
* do not support it.
|
||
|
*/
|
||
|
if (*err_code)
|
||
|
return EC_ERROR_INVAL;
|
||
|
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,
|
||
|
NULL,
|
||
|
0,
|
||
|
acc_write_len);
|
||
|
break;
|
||
|
/* read from virtual battery */
|
||
|
case READ_VB:
|
||
|
if (cache_hit) {
|
||
|
read_len += in_len;
|
||
|
memset(&resp->data[0], 0, read_len);
|
||
|
virtual_battery_operation(batt_cmd_head,
|
||
|
&resp->data[0],
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Copy memmap string data from offset to dest, up to size len, in the format
|
||
|
* expected by SBS (first byte of dest contains strlen).
|
||
|
*/
|
||
|
void copy_memmap_string(uint8_t *dest, int offset, int len)
|
||
|
{
|
||
|
uint8_t *memmap_str;
|
||
|
uint8_t memmap_strlen;
|
||
|
|
||
|
if (len == 0)
|
||
|
return;
|
||
|
memmap_str = host_get_memmap(offset);
|
||
|
/* memmap_str might not be NULL terminated */
|
||
|
memmap_strlen = *(memmap_str + EC_MEMMAP_TEXT_MAX - 1) == '\0' ?
|
||
|
strlen(memmap_str) : EC_MEMMAP_TEXT_MAX;
|
||
|
dest[0] = memmap_strlen;
|
||
|
memcpy(dest + 1, memmap_str, MIN(memmap_strlen, len - 1));
|
||
|
}
|
||
|
|
||
|
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 int batt_mode_cache = BATT_MODE_UNINITIALIZED;
|
||
|
const struct batt_params *curr_batt;
|
||
|
/*
|
||
|
* Don't allow host reads into arbitrary memory space, most params
|
||
|
* are two bytes.
|
||
|
*/
|
||
|
int bounded_read_len = MIN(read_len, 2);
|
||
|
|
||
|
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 == BATT_MODE_UNINITIALIZED)
|
||
|
/*
|
||
|
* Read the battery operational mode from
|
||
|
* the battery to initialize batt_mode_cache.
|
||
|
* This may cause an i2c transaction.
|
||
|
*/
|
||
|
if (battery_get_mode(&batt_mode_cache) ==
|
||
|
EC_ERROR_UNIMPLEMENTED)
|
||
|
/*
|
||
|
* Register not supported, choose
|
||
|
* typical SB defaults.
|
||
|
*/
|
||
|
batt_mode_cache =
|
||
|
MODE_INTERNAL_CHARGE_CONTROLLER |
|
||
|
MODE_ALARM |
|
||
|
MODE_CHARGER;
|
||
|
|
||
|
memcpy(dest, &batt_mode_cache, bounded_read_len);
|
||
|
}
|
||
|
break;
|
||
|
case SB_SERIAL_NUMBER:
|
||
|
val = strtoi(host_get_memmap(EC_MEMMAP_BATT_SERIAL), NULL, 16);
|
||
|
memcpy(dest, &val, bounded_read_len);
|
||
|
break;
|
||
|
case SB_VOLTAGE:
|
||
|
memcpy(dest, &(curr_batt->voltage), bounded_read_len);
|
||
|
break;
|
||
|
case SB_RELATIVE_STATE_OF_CHARGE:
|
||
|
memcpy(dest, &(curr_batt->state_of_charge), bounded_read_len);
|
||
|
break;
|
||
|
case SB_TEMPERATURE:
|
||
|
memcpy(dest, &(curr_batt->temperature), bounded_read_len);
|
||
|
break;
|
||
|
case SB_CURRENT:
|
||
|
memcpy(dest, &(curr_batt->current), bounded_read_len);
|
||
|
break;
|
||
|
case SB_FULL_CHARGE_CAPACITY:
|
||
|
val = curr_batt->full_capacity;
|
||
|
if (batt_mode_cache & MODE_CAPACITY)
|
||
|
val = val * curr_batt->voltage / 10000;
|
||
|
memcpy(dest, &val, bounded_read_len);
|
||
|
break;
|
||
|
case SB_BATTERY_STATUS:
|
||
|
memcpy(dest, &(curr_batt->status), bounded_read_len);
|
||
|
break;
|
||
|
case SB_CYCLE_COUNT:
|
||
|
memcpy(dest, (int *)host_get_memmap(EC_MEMMAP_BATT_CCNT),
|
||
|
bounded_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 / 10000;
|
||
|
memcpy(dest, &val, bounded_read_len);
|
||
|
break;
|
||
|
case SB_DESIGN_VOLTAGE:
|
||
|
memcpy(dest, (int *)host_get_memmap(EC_MEMMAP_BATT_DVLT),
|
||
|
bounded_read_len);
|
||
|
break;
|
||
|
case SB_REMAINING_CAPACITY:
|
||
|
val = curr_batt->remaining_capacity;
|
||
|
if (batt_mode_cache & MODE_CAPACITY)
|
||
|
val = val * curr_batt->voltage / 10000;
|
||
|
memcpy(dest, &val, bounded_read_len);
|
||
|
break;
|
||
|
case SB_MANUFACTURER_NAME:
|
||
|
copy_memmap_string(dest, EC_MEMMAP_BATT_MFGR, read_len);
|
||
|
break;
|
||
|
case SB_DEVICE_NAME:
|
||
|
copy_memmap_string(dest, EC_MEMMAP_BATT_MODEL, read_len);
|
||
|
break;
|
||
|
case SB_AVERAGE_TIME_TO_FULL:
|
||
|
/* This may cause an i2c transaction */
|
||
|
if (battery_time_to_full(&val))
|
||
|
return EC_ERROR_INVAL;
|
||
|
memcpy(dest, &val, bounded_read_len);
|
||
|
break;
|
||
|
case SB_AVERAGE_TIME_TO_EMPTY:
|
||
|
/* This may cause an i2c transaction */
|
||
|
if (battery_time_to_empty(&val))
|
||
|
return EC_ERROR_INVAL;
|
||
|
memcpy(dest, &val, bounded_read_len);
|
||
|
break;
|
||
|
case SB_MANUFACTURER_ACCESS:
|
||
|
/* No manuf. access reg access allowed over VB interface */
|
||
|
return EC_ERROR_INVAL;
|
||
|
default:
|
||
|
CPRINTS("Unhandled VB reg %x", *batt_cmd_head);
|
||
|
return EC_ERROR_INVAL;
|
||
|
}
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|