mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-07 08:01:35 +00:00
Currently the EC waits until it reads a battery status with the flag STATUS_INITIALIZED set, but the EC does not use this flag for charging or any other battery operation. If this flag is not set, it does not mean that the battery is unusable, it just means that its values may not be trustworthy. This change will remove the check for STATUS_INITIALIZED and just check that the battery responds. The battery response shows that the battery is connected and can be used by the EC. BRANCH=none BUG=chromium:564893 TEST=see that device without STATUS_INITIALIZED set will exit battery_wait_for_stable() without timing out. Change-Id: I07778e8570b6d9400b61beec6b2e222984a40692 Signed-off-by: Mary Ruthven <mruthven@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/315200 Reviewed-by: Randall Spangler <rspangler@chromium.org>
479 lines
11 KiB
C
479 lines
11 KiB
C
/* 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.
|
|
*
|
|
* Smart battery driver.
|
|
*/
|
|
|
|
#include "battery.h"
|
|
#include "battery_smart.h"
|
|
#include "console.h"
|
|
#include "host_command.h"
|
|
#include "i2c.h"
|
|
#include "smbus.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_CHARGER, outstr);
|
|
#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args)
|
|
|
|
#define BATTERY_NO_RESPONSE_TIMEOUT (1000*MSEC)
|
|
|
|
static int fake_state_of_charge = -1;
|
|
|
|
test_mockable int sbc_read(int cmd, int *param)
|
|
{
|
|
return i2c_read16(I2C_PORT_CHARGER, CHARGER_ADDR, cmd, param);
|
|
}
|
|
|
|
test_mockable int sbc_write(int cmd, int param)
|
|
{
|
|
return i2c_write16(I2C_PORT_CHARGER, CHARGER_ADDR, cmd, param);
|
|
}
|
|
|
|
test_mockable int sb_read(int cmd, int *param)
|
|
{
|
|
#ifdef CONFIG_BATTERY_CUT_OFF
|
|
/*
|
|
* Some batteries would wake up after cut-off if we talk to it.
|
|
*/
|
|
if (battery_is_cut_off())
|
|
return EC_RES_ACCESS_DENIED;
|
|
#endif
|
|
#ifdef CONFIG_SMBUS
|
|
{
|
|
int rv;
|
|
uint16_t d16 = 0;
|
|
rv = smbus_read_word(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, &d16);
|
|
*param = d16;
|
|
return rv;
|
|
}
|
|
#else
|
|
return i2c_read16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
|
|
#endif
|
|
}
|
|
|
|
test_mockable int sb_write(int cmd, int param)
|
|
{
|
|
#ifdef CONFIG_BATTERY_CUT_OFF
|
|
/*
|
|
* Some batteries would wake up after cut-off if we talk to it.
|
|
*/
|
|
if (battery_is_cut_off())
|
|
return EC_RES_ACCESS_DENIED;
|
|
#endif
|
|
#ifdef CONFIG_SMBUS
|
|
return smbus_write_word(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
|
|
#else
|
|
return i2c_write16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param);
|
|
#endif
|
|
}
|
|
|
|
int sb_read_string(int port, int slave_addr, int offset, uint8_t *data,
|
|
int len)
|
|
{
|
|
#ifdef CONFIG_BATTERY_CUT_OFF
|
|
/*
|
|
* Some batteries would wake up after cut-off if we talk to it.
|
|
*/
|
|
if (battery_is_cut_off())
|
|
return EC_RES_ACCESS_DENIED;
|
|
#endif
|
|
#ifdef CONFIG_SMBUS
|
|
return smbus_read_string(port, slave_addr, offset, data, len);
|
|
#else
|
|
return i2c_read_string(port, slave_addr, offset, data, len);
|
|
#endif
|
|
}
|
|
|
|
int battery_get_mode(int *mode)
|
|
{
|
|
return sb_read(SB_BATTERY_MODE, mode);
|
|
}
|
|
|
|
/**
|
|
* Force battery to mAh mode (instead of 10mW mode) for reporting capacity.
|
|
*
|
|
* @return non-zero if error.
|
|
*/
|
|
|
|
static int battery_force_mah_mode(void)
|
|
{
|
|
int val, rv;
|
|
rv = battery_get_mode(&val);
|
|
if (rv)
|
|
return rv;
|
|
|
|
if (val & MODE_CAPACITY)
|
|
rv = sb_write(SB_BATTERY_MODE, val & ~MODE_CAPACITY);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int battery_state_of_charge_abs(int *percent)
|
|
{
|
|
return sb_read(SB_ABSOLUTE_STATE_OF_CHARGE, percent);
|
|
}
|
|
|
|
int battery_remaining_capacity(int *capacity)
|
|
{
|
|
int rv = battery_force_mah_mode();
|
|
if (rv)
|
|
return rv;
|
|
|
|
return sb_read(SB_REMAINING_CAPACITY, capacity);
|
|
}
|
|
|
|
int battery_full_charge_capacity(int *capacity)
|
|
{
|
|
int rv = battery_force_mah_mode();
|
|
if (rv)
|
|
return rv;
|
|
|
|
return sb_read(SB_FULL_CHARGE_CAPACITY, capacity);
|
|
}
|
|
|
|
int battery_time_to_empty(int *minutes)
|
|
{
|
|
return sb_read(SB_AVERAGE_TIME_TO_EMPTY, minutes);
|
|
}
|
|
|
|
int battery_run_time_to_empty(int *minutes)
|
|
{
|
|
return sb_read(SB_RUN_TIME_TO_EMPTY, minutes);
|
|
}
|
|
|
|
int battery_time_to_full(int *minutes)
|
|
{
|
|
return sb_read(SB_AVERAGE_TIME_TO_FULL, minutes);
|
|
}
|
|
|
|
/* Read battery status */
|
|
int battery_status(int *status)
|
|
{
|
|
return sb_read(SB_BATTERY_STATUS, status);
|
|
}
|
|
|
|
/* Battery charge cycle count */
|
|
int battery_cycle_count(int *count)
|
|
{
|
|
return sb_read(SB_CYCLE_COUNT, count);
|
|
}
|
|
|
|
int battery_design_capacity(int *capacity)
|
|
{
|
|
int rv = battery_force_mah_mode();
|
|
if (rv)
|
|
return rv;
|
|
|
|
return sb_read(SB_DESIGN_CAPACITY, capacity);
|
|
}
|
|
|
|
/* Designed battery output voltage
|
|
* unit: mV
|
|
*/
|
|
int battery_design_voltage(int *voltage)
|
|
{
|
|
return sb_read(SB_DESIGN_VOLTAGE, voltage);
|
|
}
|
|
|
|
/* Read serial number */
|
|
int battery_serial_number(int *serial)
|
|
{
|
|
return sb_read(SB_SERIAL_NUMBER, serial);
|
|
}
|
|
|
|
test_mockable int battery_time_at_rate(int rate, int *minutes)
|
|
{
|
|
int rv;
|
|
int ok, time;
|
|
int loop, cmd, output_sign;
|
|
|
|
if (rate == 0) {
|
|
*minutes = 0;
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
rv = sb_write(SB_AT_RATE, rate);
|
|
if (rv)
|
|
return rv;
|
|
loop = 5;
|
|
while (loop--) {
|
|
rv = sb_read(SB_AT_RATE_OK, &ok);
|
|
if (rv)
|
|
return rv;
|
|
if (ok) {
|
|
if (rate > 0) {
|
|
cmd = SB_AT_RATE_TIME_TO_FULL;
|
|
output_sign = -1;
|
|
} else {
|
|
cmd = SB_AT_RATE_TIME_TO_EMPTY;
|
|
output_sign = 1;
|
|
}
|
|
rv = sb_read(cmd, &time);
|
|
if (rv)
|
|
return rv;
|
|
|
|
*minutes = (time == 0xffff) ? 0 : output_sign * time;
|
|
return EC_SUCCESS;
|
|
} else {
|
|
/* wait 10ms for AT_RATE_OK */
|
|
msleep(10);
|
|
}
|
|
}
|
|
return EC_ERROR_TIMEOUT;
|
|
}
|
|
|
|
test_mockable int battery_manufacture_date(int *year, int *month, int *day)
|
|
{
|
|
int rv;
|
|
int ymd;
|
|
|
|
rv = sb_read(SB_SPECIFICATION_INFO, &ymd);
|
|
if (rv)
|
|
return rv;
|
|
|
|
/* battery date format:
|
|
* ymd = day + month * 32 + (year - 1980) * 256
|
|
*/
|
|
*year = (ymd >> 8) + 1980;
|
|
*month = (ymd & 0xff) / 32;
|
|
*day = (ymd & 0xff) % 32;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* Read manufacturer name */
|
|
test_mockable int battery_manufacturer_name(char *dest, int size)
|
|
{
|
|
return sb_read_string(I2C_PORT_BATTERY, BATTERY_ADDR,
|
|
SB_MANUFACTURER_NAME, dest, size);
|
|
}
|
|
|
|
/* Read device name */
|
|
test_mockable int battery_device_name(char *dest, int size)
|
|
{
|
|
return sb_read_string(I2C_PORT_BATTERY, BATTERY_ADDR,
|
|
SB_DEVICE_NAME, dest, size);
|
|
}
|
|
|
|
/* Read battery type/chemistry */
|
|
test_mockable int battery_device_chemistry(char *dest, int size)
|
|
{
|
|
return sb_read_string(I2C_PORT_BATTERY, BATTERY_ADDR,
|
|
SB_DEVICE_CHEMISTRY, dest, size);
|
|
}
|
|
|
|
void battery_get_params(struct batt_params *batt)
|
|
{
|
|
struct batt_params batt_new = {0};
|
|
int v;
|
|
|
|
if (sb_read(SB_TEMPERATURE, &batt_new.temperature))
|
|
batt_new.flags |= BATT_FLAG_BAD_TEMPERATURE;
|
|
|
|
if (sb_read(SB_RELATIVE_STATE_OF_CHARGE, &batt_new.state_of_charge)
|
|
&& fake_state_of_charge < 0)
|
|
batt_new.flags |= BATT_FLAG_BAD_STATE_OF_CHARGE;
|
|
|
|
/* If soc is faked, override with faked data */
|
|
if (fake_state_of_charge >= 0)
|
|
batt_new.state_of_charge = fake_state_of_charge;
|
|
|
|
if (sb_read(SB_VOLTAGE, &batt_new.voltage))
|
|
batt_new.flags |= BATT_FLAG_BAD_VOLTAGE;
|
|
|
|
/* This is a signed 16-bit value. */
|
|
if (sb_read(SB_CURRENT, &v))
|
|
batt_new.flags |= BATT_FLAG_BAD_CURRENT;
|
|
else
|
|
batt_new.current = (int16_t)v;
|
|
|
|
if (sb_read(SB_CHARGING_VOLTAGE, &batt_new.desired_voltage))
|
|
batt_new.flags |= BATT_FLAG_BAD_DESIRED_VOLTAGE;
|
|
|
|
if (sb_read(SB_CHARGING_CURRENT, &batt_new.desired_current))
|
|
batt_new.flags |= BATT_FLAG_BAD_DESIRED_CURRENT;
|
|
|
|
if (battery_remaining_capacity(&batt_new.remaining_capacity))
|
|
batt_new.flags |= BATT_FLAG_BAD_REMAINING_CAPACITY;
|
|
|
|
if (battery_full_charge_capacity(&batt_new.full_capacity))
|
|
batt_new.flags |= BATT_FLAG_BAD_FULL_CAPACITY;
|
|
|
|
if (battery_status(&batt_new.status))
|
|
batt_new.flags |= BATT_FLAG_BAD_STATUS;
|
|
|
|
/* If any of those reads worked, the battery is responsive */
|
|
if ((batt_new.flags & BATT_FLAG_BAD_ANY) != BATT_FLAG_BAD_ANY)
|
|
batt_new.flags |= BATT_FLAG_RESPONSIVE;
|
|
|
|
#if defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \
|
|
defined(CONFIG_BATTERY_PRESENT_GPIO)
|
|
/* Hardware can tell us for certain */
|
|
batt_new.is_present = battery_is_present();
|
|
#else
|
|
/* No hardware test, so we only know it's there if it responds */
|
|
if (batt_new.flags & BATT_FLAG_RESPONSIVE)
|
|
batt_new.is_present = BP_YES;
|
|
else
|
|
batt_new.is_present = BP_NOT_SURE;
|
|
#endif
|
|
|
|
/*
|
|
* Charging allowed if both desired voltage and current are nonzero
|
|
* and battery isn't full (and we read them all correctly).
|
|
*/
|
|
if (!(batt_new.flags & (BATT_FLAG_BAD_DESIRED_VOLTAGE |
|
|
BATT_FLAG_BAD_DESIRED_CURRENT |
|
|
BATT_FLAG_BAD_STATE_OF_CHARGE)) &&
|
|
#ifdef CONFIG_BATTERY_REQUESTS_NIL_WHEN_DEAD
|
|
/*
|
|
* TODO (crosbug.com/p/29467): remove this workaround
|
|
* for dead battery that requests no voltage/current
|
|
*/
|
|
((batt_new.desired_voltage &&
|
|
batt_new.desired_current &&
|
|
batt_new.state_of_charge < BATTERY_LEVEL_FULL) ||
|
|
(batt_new.desired_voltage == 0 &&
|
|
batt_new.desired_current == 0 &&
|
|
batt_new.state_of_charge == 0)))
|
|
#else
|
|
batt_new.desired_voltage &&
|
|
batt_new.desired_current &&
|
|
batt_new.state_of_charge < BATTERY_LEVEL_FULL)
|
|
#endif
|
|
batt_new.flags |= BATT_FLAG_WANT_CHARGE;
|
|
else
|
|
/* Force both to zero */
|
|
batt_new.desired_voltage = batt_new.desired_current = 0;
|
|
|
|
/* Update visible battery parameters */
|
|
memcpy(batt, &batt_new, sizeof(*batt));
|
|
}
|
|
|
|
/* Wait until battery is totally stable */
|
|
int battery_wait_for_stable(void)
|
|
{
|
|
int status;
|
|
uint64_t wait_timeout = get_time().val + BATTERY_NO_RESPONSE_TIMEOUT;
|
|
|
|
CPRINTS("Wait for battery stabilized during %d",
|
|
BATTERY_NO_RESPONSE_TIMEOUT);
|
|
while (get_time().val < wait_timeout) {
|
|
/* Starting pinging battery */
|
|
if (battery_status(&status) == EC_SUCCESS) {
|
|
/* Battery is stable */
|
|
CPRINTS("battery responded with status %x", status);
|
|
return EC_SUCCESS;
|
|
}
|
|
msleep(25); /* clock stretching could hold 25ms */
|
|
}
|
|
CPRINTS("battery not responding");
|
|
return EC_ERROR_NOT_POWERED;
|
|
}
|
|
|
|
#ifndef CONFIG_CHARGER_V1
|
|
static int command_battfake(int argc, char **argv)
|
|
{
|
|
char *e;
|
|
int v;
|
|
|
|
if (argc == 2) {
|
|
v = strtoi(argv[1], &e, 0);
|
|
if (*e || v < -1 || v > 100)
|
|
return EC_ERROR_PARAM1;
|
|
|
|
fake_state_of_charge = v;
|
|
}
|
|
|
|
if (fake_state_of_charge >= 0)
|
|
ccprintf("Fake batt %d%%\n",
|
|
fake_state_of_charge);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(battfake, command_battfake,
|
|
"percent (-1 = use real level)",
|
|
"Set fake battery level",
|
|
NULL);
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
/* Smart battery pass-through
|
|
*/
|
|
#ifdef CONFIG_I2C_PASSTHROUGH
|
|
static int host_command_sb_read_word(struct host_cmd_handler_args *args)
|
|
{
|
|
int rv;
|
|
int val;
|
|
const struct ec_params_sb_rd *p = args->params;
|
|
struct ec_response_sb_rd_word *r = args->response;
|
|
|
|
if (p->reg > 0x1c)
|
|
return EC_RES_INVALID_PARAM;
|
|
rv = sb_read(p->reg, &val);
|
|
if (rv)
|
|
return EC_RES_ERROR;
|
|
|
|
r->value = val;
|
|
args->response_size = sizeof(struct ec_response_sb_rd_word);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_SB_READ_WORD,
|
|
host_command_sb_read_word,
|
|
EC_VER_MASK(0));
|
|
|
|
static int host_command_sb_write_word(struct host_cmd_handler_args *args)
|
|
{
|
|
int rv;
|
|
const struct ec_params_sb_wr_word *p = args->params;
|
|
|
|
if (p->reg > 0x1c)
|
|
return EC_RES_INVALID_PARAM;
|
|
rv = sb_write(p->reg, p->value);
|
|
if (rv)
|
|
return EC_RES_ERROR;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_SB_WRITE_WORD,
|
|
host_command_sb_write_word,
|
|
EC_VER_MASK(0));
|
|
|
|
static int host_command_sb_read_block(struct host_cmd_handler_args *args)
|
|
{
|
|
int rv;
|
|
const struct ec_params_sb_rd *p = args->params;
|
|
struct ec_response_sb_rd_block *r = args->response;
|
|
|
|
if ((p->reg != SB_MANUFACTURER_NAME) &&
|
|
(p->reg != SB_DEVICE_NAME) &&
|
|
(p->reg != SB_DEVICE_CHEMISTRY) &&
|
|
(p->reg != SB_MANUFACTURER_DATA))
|
|
return EC_RES_INVALID_PARAM;
|
|
rv = sb_read_string(I2C_PORT_BATTERY, BATTERY_ADDR, p->reg,
|
|
r->data, 32);
|
|
if (rv)
|
|
return EC_RES_ERROR;
|
|
|
|
args->response_size = sizeof(struct ec_response_sb_rd_block);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_SB_READ_BLOCK,
|
|
host_command_sb_read_block,
|
|
EC_VER_MASK(0));
|
|
|
|
static int host_command_sb_write_block(struct host_cmd_handler_args *args)
|
|
{
|
|
/* Not implemented */
|
|
return EC_RES_INVALID_COMMAND;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_SB_WRITE_BLOCK,
|
|
host_command_sb_write_block,
|
|
EC_VER_MASK(0));
|
|
#endif
|