mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-04 22:11:41 +00:00
pwr_avg provides an average voltage, current, and power over the last 1 minute. It's up to the battery drivers to implement this functionality. This change allows us to have better power tracking while minimizing the power impact on the EC, because - the pwr_avg command only needs to be called once every minute, and is short, thus less expensive to parse on ECs without a UART buffer - the work done to keep the avg is partially done by the batteries already and it's just a question of retrieving it. undefined on wheatley since no power debugging planned on that board. usage: > pwr_avg mv = 7153 ma = -605 mw = -4327 BUG=chromium:752320 BRANCH=None TEST=make buildall -j Change-Id: Id1a3479d277aedf90dfa965afb4ee9136654b1cf Signed-off-by: Ruben Rodriguez Buchillon <coconutruben@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/823884 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
406 lines
9.2 KiB
C
406 lines
9.2 KiB
C
/* Copyright 2017 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 driver for MAX17055.
|
|
*/
|
|
|
|
#include "battery.h"
|
|
#include "console.h"
|
|
#include "extpower.h"
|
|
#include "hooks.h"
|
|
#include "i2c.h"
|
|
#include "max17055.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args)
|
|
|
|
/*
|
|
* Convert the register values to the units that match
|
|
* smart battery protocol.
|
|
*/
|
|
|
|
/* Voltage reg value to mV */
|
|
#define VOLTAGE_CONV(REG) ((REG * 5) >> 6)
|
|
/* Current reg value to mA */
|
|
#define CURRENT_CONV(REG) (((REG * 25) >> 4) / BATTERY_MAX17055_RSENSE)
|
|
/* Capacity reg value to mAh */
|
|
#define CAPACITY_CONV(REG) (REG * 5 / BATTERY_MAX17055_RSENSE)
|
|
/* Time reg value to minute */
|
|
#define TIME_CONV(REG) ((REG * 3) >> 5)
|
|
/* Temperature reg value to 0.1K */
|
|
#define TEMPERATURE_CONV(REG) (((REG * 10) >> 8) + 2731)
|
|
/* Percentage reg value to 1% */
|
|
#define PERCENTAGE_CONV(REG) (REG >> 8)
|
|
|
|
/* Useful macros */
|
|
#define MAX17055_READ_DEBUG(offset, ptr_reg) \
|
|
do { \
|
|
if (max17055_read(offset, ptr_reg)) { \
|
|
CPRINTS("%s: failed to read reg %02x", \
|
|
__func__, offset); \
|
|
return; \
|
|
} \
|
|
} while (0)
|
|
#define MAX17055_WRITE_DEBUG(offset, reg) \
|
|
do { \
|
|
if (max17055_write(offset, reg)) { \
|
|
CPRINTS("%s: failed to read reg %02x", \
|
|
__func__, offset); \
|
|
return; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int fake_state_of_charge = -1;
|
|
|
|
static int max17055_read(int offset, int *data)
|
|
{
|
|
return i2c_read16(I2C_PORT_BATTERY, MAX17055_ADDR, offset, data);
|
|
}
|
|
|
|
static int max17055_write(int offset, int data)
|
|
{
|
|
return i2c_write16(I2C_PORT_BATTERY, MAX17055_ADDR, offset, data);
|
|
}
|
|
|
|
/* Return 1 if the device id is correct. */
|
|
static int max17055_probe(void)
|
|
{
|
|
int dev_id;
|
|
|
|
if (max17055_read(REG_DEVICE_NAME, &dev_id))
|
|
return 0;
|
|
if (dev_id == MAX17055_DEVICE_ID)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int battery_device_name(char *device_name, int buf_size)
|
|
{
|
|
strzcpy(device_name, "<BATT>", buf_size);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int battery_state_of_charge_abs(int *percent)
|
|
{
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
int battery_remaining_capacity(int *capacity)
|
|
{
|
|
int rv;
|
|
int reg;
|
|
|
|
rv = max17055_read(REG_REMAINING_CAPACITY, ®);
|
|
if (!rv)
|
|
*capacity = CAPACITY_CONV(reg);
|
|
return rv;
|
|
}
|
|
|
|
int battery_full_charge_capacity(int *capacity)
|
|
{
|
|
int rv;
|
|
int reg;
|
|
|
|
rv = max17055_read(REG_FULL_CHARGE_CAPACITY, ®);
|
|
if (!rv)
|
|
*capacity = CAPACITY_CONV(reg);
|
|
return rv;
|
|
}
|
|
|
|
int battery_time_to_empty(int *minutes)
|
|
{
|
|
int rv;
|
|
int reg;
|
|
|
|
rv = max17055_read(REG_TIME_TO_EMPTY, ®);
|
|
if (!rv)
|
|
*minutes = TIME_CONV(reg);
|
|
return rv;
|
|
}
|
|
|
|
int battery_time_to_full(int *minutes)
|
|
{
|
|
int rv;
|
|
int reg;
|
|
|
|
rv = max17055_read(REG_TIME_TO_FULL, ®);
|
|
if (!rv)
|
|
*minutes = TIME_CONV(reg);
|
|
return rv;
|
|
}
|
|
|
|
int battery_cycle_count(int *count)
|
|
{
|
|
return max17055_read(REG_CYCLE_COUNT, count);
|
|
}
|
|
|
|
int battery_design_capacity(int *capacity)
|
|
{
|
|
int rv;
|
|
int reg;
|
|
|
|
rv = max17055_read(REG_DESIGN_CAPACITY, ®);
|
|
if (!rv)
|
|
*capacity = CAPACITY_CONV(reg);
|
|
return rv;
|
|
}
|
|
|
|
int battery_time_at_rate(int rate, int *minutes)
|
|
{
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
int battery_manufacturer_name(char *dest, int size)
|
|
{
|
|
strzcpy(dest, "<unkn>", size);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int battery_device_chemistry(char *dest, int size)
|
|
{
|
|
strzcpy(dest, "<unkn>", size);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int battery_serial_number(int *serial)
|
|
{
|
|
/* TODO(philipchen): Implement this function. */
|
|
*serial = 0xFFFFFFFF;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int battery_design_voltage(int *voltage)
|
|
{
|
|
*voltage = battery_get_info()->voltage_normal;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int battery_get_mode(int *mode)
|
|
{
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
int battery_status(int *status)
|
|
{
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
enum battery_present battery_is_present(void)
|
|
{
|
|
int status = 0;
|
|
|
|
if (max17055_read(REG_STATUS, &status))
|
|
return BP_NOT_SURE;
|
|
if (status & STATUS_BST)
|
|
return BP_NO;
|
|
return BP_YES;
|
|
}
|
|
|
|
void battery_get_params(struct batt_params *batt)
|
|
{
|
|
int reg = 0;
|
|
const uint32_t flags_to_check = BATT_FLAG_BAD_TEMPERATURE |
|
|
BATT_FLAG_BAD_STATE_OF_CHARGE |
|
|
BATT_FLAG_BAD_VOLTAGE |
|
|
BATT_FLAG_BAD_CURRENT;
|
|
|
|
/* Reset flags */
|
|
batt->flags = 0;
|
|
|
|
if (max17055_read(REG_TEMPERATURE, ®))
|
|
batt->flags |= BATT_FLAG_BAD_TEMPERATURE;
|
|
|
|
batt->temperature = TEMPERATURE_CONV((int16_t)reg);
|
|
|
|
if (max17055_read(REG_STATE_OF_CHARGE, ®) &&
|
|
fake_state_of_charge < 0)
|
|
batt->flags |= BATT_FLAG_BAD_STATE_OF_CHARGE;
|
|
|
|
batt->state_of_charge = fake_state_of_charge >= 0 ?
|
|
fake_state_of_charge : PERCENTAGE_CONV(reg);
|
|
|
|
if (max17055_read(REG_VOLTAGE, ®))
|
|
batt->flags |= BATT_FLAG_BAD_VOLTAGE;
|
|
|
|
batt->voltage = VOLTAGE_CONV(reg);
|
|
|
|
if (max17055_read(REG_AVERAGE_CURRENT, ®))
|
|
batt->flags |= BATT_FLAG_BAD_CURRENT;
|
|
|
|
batt->current = CURRENT_CONV((int16_t)reg);
|
|
|
|
batt->desired_voltage = battery_get_info()->voltage_max;
|
|
batt->desired_current = BATTERY_DESIRED_CHARGING_CURRENT;
|
|
|
|
if (battery_remaining_capacity(&batt->remaining_capacity))
|
|
batt->flags |= BATT_FLAG_BAD_REMAINING_CAPACITY;
|
|
|
|
if (battery_full_charge_capacity(&batt->full_capacity))
|
|
batt->flags |= BATT_FLAG_BAD_FULL_CAPACITY;
|
|
|
|
/* If any of those reads worked, the battery is responsive */
|
|
if ((batt->flags & flags_to_check) != flags_to_check) {
|
|
batt->flags |= BATT_FLAG_RESPONSIVE;
|
|
batt->is_present = BP_YES;
|
|
} else
|
|
batt->is_present = BP_NOT_SURE;
|
|
|
|
/*
|
|
* Charging allowed if both desired voltage and current are nonzero
|
|
* and battery isn't full (and we read them all correctly).
|
|
*/
|
|
if (!(batt->flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
|
|
batt->desired_voltage &&
|
|
batt->desired_current &&
|
|
batt->state_of_charge < BATTERY_LEVEL_FULL)
|
|
batt->flags |= BATT_FLAG_WANT_CHARGE;
|
|
}
|
|
|
|
#ifdef CONFIG_CMD_PWR_AVG
|
|
int battery_get_avg_current(void)
|
|
{
|
|
/* TODO(crbug.com/752320) implement this */
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
int battery_get_avg_voltage(void)
|
|
{
|
|
/* TODO(crbug.com/752320) implement this */
|
|
return -EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
#endif /* CONFIG_CMD_PWR_AVG */
|
|
|
|
/* Wait until battery is totally stable. */
|
|
int battery_wait_for_stable(void)
|
|
{
|
|
/* TODO(philipchen): Implement this function. */
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* Configured MAX17055 with the battery parameters for optimal performance. */
|
|
static int max17055_init_config(void)
|
|
{
|
|
int reg;
|
|
int hib_cfg;
|
|
int dqacc;
|
|
int dpacc;
|
|
|
|
int retries = 50;
|
|
|
|
const struct max17055_batt_profile *config;
|
|
|
|
config = max17055_get_batt_profile();
|
|
|
|
if (config->is_ez_config) {
|
|
dqacc = config->design_cap / 32;
|
|
/* Choose the model for charge voltage > 4.275V. */
|
|
dpacc = dqacc * 51200 / config->design_cap;
|
|
} else {
|
|
dqacc = config->design_cap / 16;
|
|
dpacc = config->dpacc;
|
|
}
|
|
|
|
if (max17055_write(REG_DESIGN_CAPACITY, config->design_cap) ||
|
|
max17055_write(REG_DQACC, dqacc) ||
|
|
max17055_write(REG_CHARGE_TERM_CURRENT, config->ichg_term) ||
|
|
max17055_write(REG_EMPTY_VOLTAGE, config->v_empty_detect))
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
if (!config->is_ez_config) {
|
|
if (max17055_write(REG_LEARNCFG, config->learn_cfg))
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Store the original HibCFG value. */
|
|
if (max17055_read(REG_HIBCFG, &hib_cfg))
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
/* Special sequence to exit hibernate mode. */
|
|
if (max17055_write(0x60, 0x90) ||
|
|
max17055_write(REG_HIBCFG, 0) ||
|
|
max17055_write(0x60, 0))
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
if (max17055_write(REG_DPACC, dpacc) ||
|
|
max17055_write(REG_MODELCFG, (MODELCFG_REFRESH | MODELCFG_VCHG)))
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
/* Delay up to 500 ms until MODELCFG.REFRESH bit == 0. */
|
|
while (--retries) {
|
|
if (max17055_read(REG_MODELCFG, ®))
|
|
return EC_ERROR_UNKNOWN;
|
|
if (!(MODELCFG_REFRESH & reg))
|
|
break;
|
|
msleep(10);
|
|
}
|
|
if (!retries)
|
|
return EC_ERROR_TIMEOUT;
|
|
|
|
if (!config->is_ez_config) {
|
|
if (max17055_write(REG_RCOMP0, config->rcomp0) ||
|
|
max17055_write(REG_TEMPCO, config->tempco) ||
|
|
max17055_write(REG_QR_TABLE00, config->qr_table00) ||
|
|
max17055_write(REG_QR_TABLE10, config->qr_table10) ||
|
|
max17055_write(REG_QR_TABLE20, config->qr_table20) ||
|
|
max17055_write(REG_QR_TABLE30, config->qr_table30))
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Restore the original HibCFG value. */
|
|
if (max17055_write(REG_HIBCFG, hib_cfg))
|
|
return EC_ERROR_UNKNOWN;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static void max17055_init(void)
|
|
{
|
|
int reg;
|
|
int retries = 80;
|
|
|
|
if (!max17055_probe()) {
|
|
CPRINTS("Wrong max17055 id!");
|
|
return;
|
|
}
|
|
|
|
MAX17055_READ_DEBUG(REG_STATUS, ®);
|
|
|
|
/* Check for POR */
|
|
if (STATUS_POR & reg) {
|
|
/* Delay up to 800 ms until FSTAT.DNR bit == 0. */
|
|
while (--retries) {
|
|
MAX17055_READ_DEBUG(REG_FSTAT, ®);
|
|
if (!(FSTAT_DNR & reg))
|
|
break;
|
|
msleep(10);
|
|
}
|
|
if (!retries) {
|
|
CPRINTS("%s: timeout waiting for FSTAT.DNR cleared",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
if (max17055_init_config()) {
|
|
CPRINTS("max17055 configuration failed!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Clear POR bit */
|
|
MAX17055_READ_DEBUG(REG_STATUS, ®);
|
|
MAX17055_WRITE_DEBUG(REG_STATUS, (reg & ~STATUS_POR));
|
|
|
|
/* Set CONFIG.TSEL to measure temperature using external thermistor */
|
|
MAX17055_READ_DEBUG(REG_CONFIG, ®);
|
|
MAX17055_WRITE_DEBUG(REG_CONFIG, (reg | CONF_TSEL));
|
|
|
|
CPRINTS("max17055 configuration succeeded!");
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, max17055_init, HOOK_PRIO_DEFAULT);
|