blob: e42b9036d75ec8c6ec52d0182a8a21aa9d3becd7 [file] [log] [blame]
/*
* Fix for capacity drift for max1720x and for max77759
*
* Copyright (C) 2021 Google LLC
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include "gbms_power_supply.h"
#include "google_bms.h"
#include "max1720x_battery.h"
/* delay between attempts after a write failure */
#define MAX17201_FIXUP_UPDATE_DELAY_MS 10
/* max 110% of design cap, max 60% of design cap for age model */
#define MAX1720x_CC_UPPER_BOUND 110
#define MAX1720x_CC_LOWER_BOUND 60
#define ALGO_VER_CHECK(algo_ver) ((algo_ver) == MAX1720X_DA_VER_MWA2 || \
(algo_ver) == MAX1720X_DA_VER_NONE)
/* registers accessed from the workaround */
enum {
MAX17X0X_REPCAP = 0x05,
MAX17X0X_REPSOC = 0x06,
MAX17X0X_MIXCAP = 0x0F,
MAX17X0X_FULLCAPNOM = 0x23,
MAX17X0X_FULLCAPREP = 0x35,
MAX17X0X_RCOMP0 = 0x38, /* 16 bits in MW A1+ */
MAX17X0X_TEMPCO = 0x39,
MAX17X0X_DQACC = 0x45,
MAX17X0X_DPACC = 0x46,
MAX17X0X_VFSOC = 0xFF,
};
/* 1 = success, 0 compare error, < 0 error */
static int max1720x_update_compare(struct max17x0x_regmap *map, int reg,
u16 data0, u16 data1)
{
u16 data[2] = {data0, data1};
int ret;
ret = regmap_raw_write(map->regmap, reg, data, sizeof(data));
if (ret < 0)
return -EIO;
msleep(2);
ret = regmap_raw_read(map->regmap, reg, data, sizeof(data));
if (ret < 0)
return -EIO;
return (data[0] == data0) && (data[1] == data1);
}
/* 0 not updated, 1 updated, doesn't return IO errors */
static int max1720x_update_capacity(struct max17x0x_regmap *map,
u16 mixcap, u16 repcap,
u16 fullcaprep)
{
u16 temp;
int err;
err = REGMAP_WRITE(map, MAX17X0X_MIXCAP, mixcap);
if (err == 0)
err = REGMAP_READ(map, MAX17X0X_MIXCAP, &temp);
if (err < 0 || temp != mixcap)
return 0;
/* RepCap and FullCapRep must be updated together */
err = REGMAP_WRITE(map, MAX17X0X_REPCAP, repcap);
if (err == 0)
err = REGMAP_READ(map, MAX17X0X_REPCAP, &temp);
if (err < 0 || temp != repcap)
return 0;
err = REGMAP_WRITE(map, MAX17X0X_FULLCAPREP, fullcaprep);
if (err == 0)
err = REGMAP_READ(map, MAX17X0X_FULLCAPREP, &temp);
if (err < 0 || temp != fullcaprep)
return 0;
return 1;
}
/* return fullcapnom if no changes */
static int max1720x_capacity_check(int fullcapnom, int cycle_count,
const struct max1720x_drift_data *ddata)
{
const int fcn = fullcapnom;
int refcap, upper_bound, lower_bound;
if (!ddata->design_capacity || cycle_count < ddata->cycle_stable)
return fullcapnom;
/* apply fade model to design capacity, bound to absolute min/max */
refcap = ddata->design_capacity;
if (ddata->cycle_fade) {
const int base_capacity = ddata->design_capacity;
lower_bound = (base_capacity * MAX1720x_CC_LOWER_BOUND) / 100;
upper_bound = (base_capacity * MAX1720x_CC_UPPER_BOUND) / 100;
refcap -= (base_capacity * cycle_count) / ddata->cycle_fade;
if (refcap < lower_bound)
refcap = lower_bound;
else if (refcap > upper_bound)
refcap = upper_bound;
pr_debug("refcap@%d=%d abs_min=%d abs_max=%d\n",
cycle_count, refcap, lower_bound, upper_bound);
}
/* bound FCN max to the target age. Will not operate on devices
* that underestimate capacity.
* NOTE: range decrease with cycle count
*/
upper_bound = (refcap * (100 + ddata->cycle_band)) / 100;
if (fullcapnom > upper_bound)
fullcapnom = upper_bound;
pr_debug("fullcapnom=%d->%d upper_bound=%d\n",
fcn, fullcapnom, upper_bound);
return fullcapnom;
}
/*
* dQACC @0x45 battery charge between relaxation points.
* dPACC @0x46 change in battery state of charge between relaxation points.
*/
/* 1 changed, 0 no changes, < 0 error*/
int max1720x_fixup_dxacc(struct max1720x_drift_data *ddata,
struct max17x0x_regmap *map,
int cycle_count,
int plugged,
int lsb)
{
u16 temp, vfsoc = 0, repsoc = 0, fullcapnom, mixcap, repcap, fcrep;
int capacity, new_capacity;
int dpacc, dqacc;
int err, loops;
if (ddata->design_capacity <= 0 || ALGO_VER_CHECK(ddata->algo_ver))
return 0;
err = REGMAP_READ(map, MAX17X0X_FULLCAPNOM, &fullcapnom);
if (err < 0)
return err;
capacity = reg_to_micro_amp_h(fullcapnom, ddata->rsense, lsb) / 1000;
/* return the expected FCN, done if the same of th eold one */
new_capacity = max1720x_capacity_check(capacity, cycle_count, ddata);
if (new_capacity == capacity)
return 0;
/* You can use a ratio of dPAcc = 0x190 ( = 25%) with dQACC with 64 mAh
* LSB. Can make dPACC larger (ex 0xC80, 200%) and give dQAcc a smaller
* LSB (FullCapNom >> 4, LSB = 8 mAh). The equation can be written a
* (DesignCap * scale) >> 4 when writing the age-compensated value.
*/
/* TODO: fix for TaskPeriod == 351ms b/177099997 */
fcrep = micro_amp_h_to_reg(new_capacity * 1000, ddata->rsense, lsb);
dqacc = fcrep >> 4;
dpacc = 0xc80;
/* will not update if dqacc/dpacc is already in line */
err = REGMAP_READ(map, MAX17X0X_DQACC, &temp);
if (err == 0 && temp == dqacc) {
err = REGMAP_READ(map, MAX17X0X_DPACC, &temp);
if (err == 0 && temp == dpacc) {
pr_debug("Fix capacity: same dqacc=0x%x dpacc=0x%x\n",
dqacc, dpacc);
return 0;
}
}
/* fast convergence, avoid ghost drain */
err = REGMAP_READ(map, MAX17X0X_VFSOC, &vfsoc);
if (err == 0)
err = REGMAP_READ(map, MAX17X0X_REPSOC, &repsoc);
if (err < 0) {
pr_warn("Fix capacity: fcn=%d new=%d vfsoc=0x%x repsoc=0x%x (%d)\n",
fullcapnom, new_capacity, vfsoc, repsoc, err);
return err;
}
/* vfsoc/repsoc perc, lsb = 1/256 */
mixcap = (((u32)vfsoc) * fcrep) / 25600;
repcap = (((u32)repsoc) * fcrep) / 25600;
for (loops = 0; loops < 3; loops++) {
err = max1720x_update_capacity(map, mixcap, repcap, fcrep);
if (err == -EIO || err > 0)
break;
/* arbitrary delay between attempts */
msleep(MAX17201_FIXUP_UPDATE_DELAY_MS);
}
pr_info("Fix capacity: fixing caps retries=%d (%d)\n", loops, err);
/* 3 loops suggested from vendor */
for (loops = 0; loops < 3; loops++) {
err = max1720x_update_compare(map, MAX17X0X_DQACC, dqacc, dpacc);
if (err == -EIO || err > 0)
break;
/* arbitrary delay between attempts */
msleep(MAX17201_FIXUP_UPDATE_DELAY_MS);
}
pr_info("Fix capacity: %d->%d, vfsoc=0x%x repsoc=0x%x fcrep=0x%x mixcap=0x%x repcap=0x%x ddqacc=0x%x dpacc=0x%x retries=%d (%d)\n",
fullcapnom, new_capacity, vfsoc, repsoc, fcrep, mixcap, repcap,
dqacc, dpacc, loops, err);
/* TODO: b/144630261 fix Google Capacity */
return (loops == 3) ? -ETIMEDOUT : err;
}
/* Tempco and rcomp0 must remain within the following limits to avoid capacity
* drift. Note that tempco has a hi and low limit (one byte).
* (RCOMP0 > INI_RCOMP0 * 1.4) or (RCOMP0 < 0.7 * INI_RCOMP0)
* (TempCoHot >INI_TempCoHot * 1.4) or (TempCoHot < 0.7 * INI_TempCoHot)
* (TempCoCold >INI_TempCoCold * 1.4) or (TempCoCold < 0.7 * INI_TempCoCold)
*/
#define MAXIM_RCOMP0_LIM_HI 140
#define MAXIM_RCOMP0_LIM_LO 70
#define MAXIM_TEMPCO_LIM_HI 140
#define MAXIM_TEMPCO_LIM_LO 70
static u8 comp_check(int value, int scale, int lim_low, int lim_high)
{
if ((value * scale) < lim_low) {
value = lim_low / scale;
} else if ((value * scale) > lim_high) {
value = lim_high / scale;
if (value > 0xff)
value = 0xff;
}
return value;
}
/* NOT OK For MW A1+ */
static u16 max1720x_check_rcomp0(const struct max1720x_drift_data *ddata,
u16 rcomp0)
{
const int ini_rcomp0_lob = ddata->ini_rcomp0 & 0xff;
int rcomp0_lob = rcomp0 & 0xff;
rcomp0_lob = comp_check(rcomp0_lob, 100,
ini_rcomp0_lob * MAXIM_RCOMP0_LIM_LO,
ini_rcomp0_lob * MAXIM_RCOMP0_LIM_HI);
pr_debug("rcomp0=%x rcomp0_lob=%x->%x min=%x max=%x\n",
rcomp0, (rcomp0 & 0xff), rcomp0_lob,
(ini_rcomp0_lob * MAXIM_RCOMP0_LIM_LO) / 100,
(ini_rcomp0_lob * MAXIM_RCOMP0_LIM_HI) / 100);
/* always write 0 to the high byte */
return rcomp0_lob & 0xff;
}
/* MW A1+ */
static u16 max1720x_check_mw_rcomp0(const struct max1720x_drift_data *ddata,
u16 rcomp0)
{
const int lim_low = ddata->ini_rcomp0 * MAXIM_RCOMP0_LIM_LO;
const int lim_high = ddata->ini_rcomp0 * MAXIM_RCOMP0_LIM_HI;
const int scale = 100;
int value = rcomp0;
if ((value * scale) < lim_low) {
value = lim_low / scale;
} else if ((value * scale) > lim_high) {
value = lim_high / scale;
if (value > 0xffff)
value = 0xffff;
}
return value;
}
/* 0 no changes, >0 changes */
static bool max1720x_comp_check(u16 *new_rcomp0, u16 *new_tempco,
const struct max1720x_drift_data *ddata)
{
const u16 rcomp0 = *new_rcomp0;
const u16 tempco = *new_tempco;
const int ini_tc_lob = ddata->ini_tempco & 0xff;
const int ini_tc_hib = (ddata->ini_tempco >> 8) & 0xff;
int tc_hib = (tempco >> 8) & 0xff;
int tc_lob = tempco & 0xff;
bool fix_rcomp0 = false;
if (ddata->algo_ver == MAX1720X_DA_VER_ORIG) {
*new_rcomp0 = max1720x_check_rcomp0(ddata, rcomp0);
fix_rcomp0 = (rcomp0 & 0xff) != *new_rcomp0;
} else if (ddata->algo_ver == MAX1720X_DA_VER_MWA1) {
if (*new_rcomp0 < 0x100)
*new_rcomp0 = *new_rcomp0 << 4;
*new_rcomp0 = max1720x_check_mw_rcomp0(ddata, *new_rcomp0);
fix_rcomp0 = rcomp0 != *new_rcomp0;
}
tc_lob = comp_check(tc_lob, 100, ini_tc_lob * MAXIM_TEMPCO_LIM_LO,
ini_tc_lob * MAXIM_TEMPCO_LIM_HI);
tc_hib = comp_check(tc_hib, 100, ini_tc_hib * MAXIM_TEMPCO_LIM_LO,
ini_tc_hib * MAXIM_TEMPCO_LIM_HI);
pr_debug("tempco=%x tempco_lob=%x->%x min=%x max=%x, tempco_hib=%x->%x min=%x max=%x\n",
tempco, tempco & 0xff, tc_lob,
(ini_tc_lob * MAXIM_TEMPCO_LIM_LO) / 100,
(ini_tc_lob * MAXIM_TEMPCO_LIM_HI) / 100,
(tempco >> 8) & 0xff, tc_hib,
(ini_tc_hib * MAXIM_TEMPCO_LIM_LO) / 100,
(ini_tc_hib * MAXIM_TEMPCO_LIM_HI) / 100);
*new_tempco = (tc_hib << 8) | (tc_lob);
/* fix for MW A1+ */
return fix_rcomp0 || (tempco != *new_tempco);
}
/* fix rcomp0 and tempco */
int max1720x_fixup_comp(struct max1720x_drift_data *ddata,
struct max17x0x_regmap *map,
int plugged)
{
u16 new_rcomp0, new_tempco, data[2] = { 0 };
int err, loops;
if (ddata->ini_rcomp0 == -1 || ddata->ini_tempco == -1 ||
ALGO_VER_CHECK(ddata->algo_ver))
return 0;
err = regmap_raw_read(map->regmap, MAX17X0X_RCOMP0, data,
sizeof(data));
if (err < 0)
return -EIO;
new_rcomp0 = data[0];
new_tempco = data[1];
err = max1720x_comp_check(&new_rcomp0, &new_tempco, ddata);
pr_debug("rcomp0=0x%x tempco=0x%x (%d)\n", data[0], data[1], err);
if (err <= 0)
return err;
/* 3 loops suggested from vendor */
for (loops = 0; loops < 3; loops++) {
err = max1720x_update_compare(map, MAX17X0X_RCOMP0,
new_rcomp0, new_tempco);
if (err == -EIO || err > 0)
break;
/* arbitrary delay between attempts */
msleep(MAX17201_FIXUP_UPDATE_DELAY_MS);
}
pr_info("Fix rcomp0=0x%x->0x%x tempco:0x%x->0x%x, retries=%d, (%d)\n",
data[0], new_rcomp0, data[1], new_tempco, loops, err);
return (loops == 3) ? -ETIMEDOUT : err;
}