Files
OpenCellular/driver/battery/smart.c
Sheng-Liang Song 51a279cf3a EC: smart battery using smbus API
Ref: Common Smart Battery System Inferface Specification v8.0.
Ref: http://smbus.org/specs/smbus20.pdf

- Enable smbus read/write APIs with compile options

BUG=chrome-os-partner:30930
BRANCH=ToT,glimmer
TEST=Verified with LGC & Simplo firmware update.

Change-Id: I3f4bb23147f22365adb378c2e39c40d5ba100889
Signed-off-by: Sheng-Liang Song <ssl@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/209906
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2014-09-03 22:49:57 +00:00

457 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_WAIT_TIMEOUT (2800*MSEC)
#define BATTERY_NO_RESPONSE_TIMEOUT (1000*MSEC)
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))
batt_new.flags |= BATT_FLAG_BAD_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 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, got_response;
uint64_t wait_timeout = get_time().val + BATTERY_WAIT_TIMEOUT;
uint64_t no_response_timeout = get_time().val +
BATTERY_NO_RESPONSE_TIMEOUT;
got_response = 0;
CPRINTS("Wait for battery stabilized during %d",
BATTERY_WAIT_TIMEOUT);
while (get_time().val < wait_timeout) {
/* Starting pinging battery */
if (battery_status(&status) == EC_SUCCESS) {
got_response = 1;
/* Battery is stable */
if (status & STATUS_INITIALIZED) {
CPRINTS("battery initialized");
return EC_SUCCESS;
}
}
/* Assume no battery connected if no response for a while */
else if (!got_response &&
get_time().val > no_response_timeout) {
CPRINTS("battery not responding");
return EC_ERROR_NOT_POWERED;
}
msleep(25); /* clock stretching could hold 25ms */
}
CPRINTS("battery wait stable timeout");
return EC_ERROR_TIMEOUT;
}
/*****************************************************************************/
/* 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