blob: 56319c9a2cbf127d18b740ccd2ee6d78e4fce172 [file] [log] [blame]
/*
* Copyright 2018 Google, LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include "google_bms.h"
#include "google_psy.h"
#include "qmath.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#endif
#define ELAP_LIMIT_S 60
void ttf_log(const struct batt_ttf_stats *stats, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
logbuffer_vlog(stats->ttf_log, fmt, args);
va_end(args);
}
/* actual adapter current capability for this charging event
* NOTE: peformance for a tier are known only after entering the tier
*/
static int ttf_pwr_icl(const struct gbms_ce_tier_stats *ts,
const union gbms_ce_adapter_details *ad)
{
int elap, amperage;
elap = ts->time_fast + ts->time_taper;
if (elap <= ELAP_LIMIT_S)
amperage = ad->ad_amperage * 100;
else
amperage = ts->icl_sum / (elap + ts->time_other);
return amperage;
}
/* NOTE: the current in taper might need to be accounted in a different way */
int ttf_pwr_ibatt(const struct gbms_ce_tier_stats *ts)
{
int avg_ibatt, elap, sign = 1;
elap = ts->time_fast + ts->time_taper;
/* averages are not reliable until after some time in tier */
if (elap <= ELAP_LIMIT_S) {
pr_debug("%s: limit=%d elap=%d (%d+%d) o=%d\n", __func__,
elap, ELAP_LIMIT_S, ts->time_fast, ts->time_taper,
ts->time_other);
return 0;
}
/* actual, called only when avg_ibatt in tier indicates charging */
avg_ibatt = ts->ibatt_sum / (elap + ts->time_other);
if (avg_ibatt < 0)
sign = -1;
pr_debug("%s: elap=%d (%d+%d+%d) sum=%ld avg_ibatt=%d\n", __func__,
elap, ts->time_fast, ts->time_taper, ts->time_other,
ts->ibatt_sum, avg_ibatt * sign);
return avg_ibatt * sign;
}
/* nominal voltage tier index for this soc */
int ttf_pwr_vtier_idx(const struct batt_ttf_stats *stats, int soc)
{
int i;
for (i = 1; i < GBMS_STATS_TIER_COUNT; i++)
if (soc < stats->tier_stats[i].soc_in >> 8)
break;
return i - 1;
}
/*
* reference or current average current demand for a soc at max rate.
* NOTE: always <= cc_max for reference temperature
*/
int ttf_ref_cc(const struct batt_ttf_stats *stats, int soc)
{
const struct ttf_soc_stats *sstat = NULL;
int delta_cc;
/* out of range */
if (soc + 1 >= GBMS_SOC_STATS_LEN)
return 0;
/* soc average current demand */
if (stats->soc_stats.cc[soc + 1] && stats->soc_stats.cc[soc] &&
stats->soc_stats.elap[soc])
sstat = &stats->soc_stats;
else if (stats->soc_ref.cc[soc + 1] && stats->soc_ref.cc[soc] &&
stats->soc_ref.elap[soc])
sstat = &stats->soc_ref;
else
return 0;
delta_cc = (sstat->cc[soc + 1] - sstat->cc[soc]);
pr_debug("%s %d: delta_cc=%d elap=%ld\n", __func__, soc,
delta_cc, sstat->elap[soc]);
return (delta_cc * 3600) / sstat->elap[soc];
}
/* assumes that health is active for any soc greater than CHG_HEALTH_REST_SOC */
static int ttf_pwr_health(const struct gbms_charging_event *ce_data,
int soc)
{
return CHG_HEALTH_REST_IS_ACTIVE(&ce_data->ce_health) &&
soc >= CHG_HEALTH_REST_SOC(&ce_data->ce_health);
}
static int ttf_pwr_health_pause(const struct gbms_charging_event *ce_data,
int soc)
{
return CHG_HEALTH_REST_IS_PAUSE(&ce_data->ce_health) &&
soc >= CHG_HEALTH_REST_SOC(&ce_data->ce_health);
}
/*
* equivalent icl: minimum between actual input current limit and battery
* everage current _while_in_tier. actual_icl will be lower in high current
* tiers for bad cables, ibatt is affected by temperature tier and sysload.
*/
static int ttf_pwr_equiv_icl(const struct gbms_charging_event *ce_data,
int vbatt_idx, int soc)
{
const struct gbms_chg_profile *profile = ce_data->chg_profile;
const int aratio = (ce_data->adapter_details.ad_voltage * 10000) /
(profile->volt_limits[vbatt_idx] / 1000);
const struct gbms_ce_tier_stats *tier_stats;
const int efficiency = 95; /* TODO: use real efficiency */
const u32 capacity_ma = profile->capacity_ma;
const int rest_rate = ce_data->ce_health.rest_rate;
int equiv_icl, act_icl, act_ibatt, health_ibatt = -1;
/* Health collects in ce_data->health_stats vtier */
if (ttf_pwr_health(ce_data, soc)) {
health_ibatt = ce_data->ce_health.rest_cc_max / 1000;
tier_stats = &ce_data->health_stats;
} else if (ttf_pwr_health_pause(ce_data, soc)) {
/* use ACTIVE current in PAUSE stat for ttf calculation */
health_ibatt = (capacity_ma * rest_rate * 10) / 1000;
/* use ACTIVE tier in PAUSE stat for ttf calculation */
tier_stats = &ce_data->health_stats;
} else {
tier_stats = &ce_data->tier_stats[vbatt_idx];
}
/*
* actual adapter capabilities at adapter voltage for vtier
* NOTE: demand and cable might cause voltage and icl do droop
*/
act_icl = ttf_pwr_icl(tier_stats, &ce_data->adapter_details);
if (act_icl <= 0) {
pr_debug("%s: negative,null act_icl=%d\n", __func__, act_icl);
return -EINVAL;
}
/* scale icl (at adapter voltage) to vtier */
equiv_icl = (act_icl * aratio * 100) / efficiency;
pr_debug("%s: act_icl=%d aratio=%d equiv_icl=%d\n",
__func__, act_icl, aratio, equiv_icl);
/* actual ibatt in this tier: act_ibatt==0 when too early to tell */
act_ibatt = ttf_pwr_ibatt(tier_stats);
if (act_ibatt == 0 && health_ibatt > 0)
act_ibatt = health_ibatt;
if (act_ibatt < 0) {
pr_debug("%s: discharging ibatt=%d\n", __func__, act_ibatt);
return -EINVAL;
}
/* assume that can deliver equiv_icl when act_ibatt == 0 */
if (act_ibatt > 0 && act_ibatt < equiv_icl) {
pr_debug("%s: sysload ibatt=%d, reduce icl %d->%d\n",
__func__, act_ibatt, equiv_icl, act_ibatt);
equiv_icl = act_ibatt;
}
pr_debug("%s: equiv_icl=%d\n", __func__, equiv_icl);
return equiv_icl;
}
/*
* time scaling factor for available power and SOC demand.
* NOTE: usually called when soc < ssoc_in && soc > ce_data->last_soc
* TODO: this is very inefficient
*/
static int ttf_pwr_ratio(const struct batt_ttf_stats *stats,
const struct gbms_charging_event *ce_data,
int soc)
{
const struct gbms_chg_profile *profile = ce_data->chg_profile;
int cc_max, vbatt_idx, temp_idx;
int avg_cc, equiv_icl;
int ratio;
/* regular charging tier */
vbatt_idx = ttf_pwr_vtier_idx(stats, soc);
if (vbatt_idx < 0)
return -EINVAL;
/* TODO: compensate with average increase/decrease of temperature? */
temp_idx = ce_data->tier_stats[vbatt_idx].temp_idx;
if (temp_idx == -1) {
int64_t t_avg = 0;
const int elap = ce_data->tier_stats[vbatt_idx].time_fast +
ce_data->tier_stats[vbatt_idx].time_taper +
ce_data->tier_stats[vbatt_idx].time_other;
if (ce_data->tier_stats[vbatt_idx].temp_sum != 0 || elap == 0)
t_avg = ce_data->tier_stats[vbatt_idx].temp_in;
if (t_avg == 0)
t_avg = 250;
/* average temperature in tier for charge tier index */
temp_idx = gbms_msc_temp_idx(profile, t_avg);
pr_debug("%s %d: temp_idx=%d t_avg=%ld sum=%ld elap=%d\n",
__func__, soc, temp_idx, t_avg,
ce_data->tier_stats[vbatt_idx].temp_sum,
elap);
if (temp_idx < 0)
return -EINVAL;
}
/* max tier demand for voltage tier at this temperature index */
cc_max = GBMS_CCCM_LIMITS(profile, temp_idx, vbatt_idx) / 1000;
/* statistical current demand for soc (<= cc_max) */
avg_cc = ttf_ref_cc(stats, soc);
if (avg_cc <= 0) {
/* default to cc_max if we have no data */
pr_debug("%s %d: demand use default avg_cc=%d->%d\n",
__func__, soc, avg_cc, cc_max);
avg_cc = cc_max;
}
/* statistical or reference max power demand for the tier at */
pr_debug("%s %d:%d,%d: avg_cc=%d cc_max=%d\n",
__func__, soc, temp_idx, vbatt_idx,
avg_cc, cc_max);
/* equivalent input current for adapter at vtier */
equiv_icl = ttf_pwr_equiv_icl(ce_data, vbatt_idx, soc);
if (equiv_icl <= 0) {
pr_debug("%s %d: negative, null act_icl=%d\n",
__func__, soc, equiv_icl);
return -EINVAL;
}
/* lower to cc_max if in HOT and COLD */
if (cc_max < equiv_icl) {
pr_debug("%s %d: reduce act_icl=%d to cc_max=%d\n",
__func__, soc, equiv_icl, cc_max);
equiv_icl = cc_max;
}
/*
* This is the trick that makes everything work:
* equiv_icl = min(act_icl, act_ibatt, cc_max)
*
* act_icl = adapter max or adapter actual icl (due to bad cable,
* AC enabled or temperature shift) scaled to vtier
* act_ibatt = measured for
* at reference temperature or actual < cc_max due to sysload
* cc_max = cc_max from profile (lower than ref for HOT or COLD)
*
*/
/* ratio for elap time: it doesn't work if reference is not maximal */
if (equiv_icl < avg_cc)
ratio = (avg_cc * 100) / equiv_icl;
else
ratio = 100;
pr_debug("%s %d: equiv_icl=%d, avg_cc=%d ratio=%d\n",
__func__, soc, equiv_icl, avg_cc, ratio);
return ratio;
}
/* SOC estimates --------------------------------------------------------- */
/* reference of current elap for a soc at max rate */
static int ttf_ref_elap(const struct batt_ttf_stats *stats, int soc)
{
ktime_t elap;
if (soc < 0 || soc >= 100)
return 0;
elap = stats->soc_stats.elap[soc];
if (elap == 0)
elap = stats->soc_ref.elap[soc];
return elap;
}
/* elap time for a single soc% */
static int ttf_elap(ktime_t *estimate, const struct batt_ttf_stats *stats,
const struct gbms_charging_event *ce_data,
int soc)
{
ktime_t elap;
int ratio;
/* cannot really return 0 elap unless the data is corrupted */
elap = ttf_ref_elap(stats, soc);
if (elap == 0) {
pr_debug("%s %d: zero elap\n", __func__, soc);
return -EINVAL;
}
ratio = ttf_pwr_ratio(stats, ce_data, soc);
if (ratio < 0) {
pr_debug("%s %d: negative ratio=%d\n", __func__, soc, ratio);
return -EINVAL;
}
*estimate = elap * ratio;
pr_debug("%s: soc=%d estimate=%lld elap=%lld ratio=%d\n",
__func__, soc, *estimate, elap, ratio);
return ratio;
}
/*
* time to full from SOC% using the actual stats
* NOTE: prediction is based stats and corrected with the ce_data
* NOTE: usually called with soc > ce_data->last_soc
*/
int ttf_soc_estimate(ktime_t *res, const struct batt_ttf_stats *stats,
const struct gbms_charging_event *ce_data,
qnum_t soc, qnum_t last)
{
const int ssoc_in = ce_data->charging_stats.ssoc_in;
ktime_t elap, estimate = 0;
int i = 0, ratio, frac, max_ratio = 0;
if (last > qnum_rconst(100) || last < soc)
return -EINVAL;
if (last == soc) {
*res = 0;
return 0;
}
/* FIRST: 100 - first 2 digits of the fractional part of soc if any */
frac = (int)qnum_nfracdgt(soc, 2);
if (frac) {
ratio = ttf_elap(&elap, stats, ce_data, qnum_toint(soc));
if (ratio >= 0)
estimate += (elap * (100 - frac)) / 100;
i += 1;
}
/* accumulate ttf_elap starting from i + 1 until end */
for (i += qnum_toint(soc); i < qnum_toint(last); i++) {
if (i >= ssoc_in && i < ce_data->last_soc) {
/* use real data if within charging event */
elap = ce_data->soc_stats.elap[i] * 100;
} else {
/* future (and soc before ssoc_in) */
ratio = ttf_elap(&elap, stats, ce_data, i);
if (ratio < 0)
return ratio;
if (ratio > max_ratio)
max_ratio = ratio;
}
estimate += elap;
}
/* LAST: first 2 digits of the fractional part of soc if any */
frac = (int)qnum_nfracdgt(last, 2);
if (frac) {
ratio = ttf_elap(&elap, stats, ce_data, qnum_toint(last));
if (ratio >= 0)
estimate += (elap * frac) / 100;
}
*res = estimate / 100;
return max_ratio;
}
int ttf_soc_cstr(char *buff, int size, const struct ttf_soc_stats *soc_stats,
int start, int end)
{
int i, len = 0, split = 100;
if (start < 0 || start >= GBMS_SOC_STATS_LEN ||
end < 0 || end >= GBMS_SOC_STATS_LEN ||
start > end)
return 0;
len += scnprintf(&buff[len], size - len, "\n");
/* only one way to print data @ 100 */
if (end == 100 && start != 100)
end = 99;
/* std newline every 10 entries */
if (start == 0 && end == 99)
split = 10;
/* dump elap time as T: */
for (i = start; i <= end; i++) {
if (i % split == 0 || i == start) {
len += scnprintf(&buff[len], size - len, "T");
if (split == 10)
len += scnprintf(&buff[len], size - len,
"%d", i / 10);
len += scnprintf(&buff[len], size - len, ":");
}
len += scnprintf(&buff[len], size - len, " %4ld",
soc_stats->elap[i]);
if (i != end && (i + 1) % split == 0)
len += scnprintf(&buff[len], size - len, "\n");
}
len += scnprintf(&buff[len], size - len, "\n");
/* dump coulumb count as C: */
for (i = start; i <= end; i++) {
if (i % split == 0 || i == start) {
len += scnprintf(&buff[len], size - len, "C");
if (split == 10)
len += scnprintf(&buff[len], size - len,
"%d", i / 10);
len += scnprintf(&buff[len], size - len, ":");
}
len += scnprintf(&buff[len], size - len, " %4d",
soc_stats->cc[i]);
if (i != end && (i + 1) % split == 0)
len += scnprintf(&buff[len], size - len, "\n");
}
len += scnprintf(&buff[len], size - len, "\n");
return len;
}
/* TODO: tune these values */
/* discard updates for adapters that have less than 80% of nominal */
#define TTF_SOC_QUAL_ELAP_RATIO_MAX 200
/* cap updates of cc to no more of +-*_CUR_ABS_MAX from previous */
#define TTF_SOC_QUAL_ELAP_DELTA_CUR_ABS_MAX 60
/* cap udpdates to cc max to no more of +-20% of reference */
#define TTF_SOC_QUAL_ELAP_DELTA_REF_PCT_MAX 20
/* return the weight to apply to this change */
static ktime_t ttf_soc_qual_elap(const struct batt_ttf_stats *stats,
const struct gbms_charging_event *ce_data,
int i)
{
const struct ttf_soc_stats *src = &ce_data->soc_stats;
const struct ttf_soc_stats *dst = &stats->soc_stats;
const int limit = TTF_SOC_QUAL_ELAP_RATIO_MAX;
const int max_elap = ((100 + TTF_SOC_QUAL_ELAP_DELTA_REF_PCT_MAX) *
stats->soc_ref.elap[i]) / 100;
const int min_elap = ((100 - TTF_SOC_QUAL_ELAP_DELTA_REF_PCT_MAX) *
stats->soc_ref.elap[i]) / 100;
ktime_t elap, elap_new, elap_cur;
int ratio;
if (!src->elap[i])
return 0;
/* weight the adapter, discard if ratio is too high (poor adapter) */
ratio = ttf_pwr_ratio(stats, ce_data, i);
if (ratio <= 0 || ratio > limit) {
pr_debug("%d: ratio=%d limit=%d\n", i, ratio, limit);
return 0;
}
elap_new = (src->elap[i] * 100) / ratio;
elap_cur = dst->elap[i];
if (!elap_cur)
elap_cur = stats->soc_ref.elap[i];
elap = (elap_cur + elap_new) / 2;
/* bounds check to previous */
if (elap > (elap_cur + TTF_SOC_QUAL_ELAP_DELTA_CUR_ABS_MAX))
elap = elap_cur + TTF_SOC_QUAL_ELAP_DELTA_CUR_ABS_MAX;
else if (elap < (elap_cur - TTF_SOC_QUAL_ELAP_DELTA_CUR_ABS_MAX))
elap = elap_cur - TTF_SOC_QUAL_ELAP_DELTA_CUR_ABS_MAX;
/* bounds check to reference */
if (elap > max_elap)
elap = max_elap;
else if (elap < min_elap)
elap = min_elap;
pr_debug("%d: dst->elap=%ld, ref_elap=%ld, elap=%ld, src_elap=%ld ratio=%d, min=%d max=%d\n",
i, dst->elap[i], stats->soc_ref.elap[i], elap, src->elap[i],
ratio, min_elap, max_elap);
return elap;
}
/* cap updates of cc to no more of +-*_CUR_ABS_MAX from previous */
#define TTF_SOC_QUAL_CC_DELTA_CUR_ABS_MAX 40
/* cap udpdates to cc max to no more of +-20% of reference */
#define TTF_SOC_QUAL_CC_DELTA_REF_PCT_MAX 20
static int ttf_soc_qual_cc(const struct batt_ttf_stats *stats,
const struct gbms_charging_event *ce_data,
int i)
{
const struct ttf_soc_stats *src = &ce_data->soc_stats;
const struct ttf_soc_stats *dst = &stats->soc_stats;
const int max_cc = ((100 + TTF_SOC_QUAL_CC_DELTA_REF_PCT_MAX) *
stats->soc_ref.cc[i]) / 100;
const int min_cc = ((100 - TTF_SOC_QUAL_CC_DELTA_REF_PCT_MAX) *
stats->soc_ref.cc[i]) / 100;
int cc, cc_cur;
if (!src->cc[i])
return 0;
cc_cur = dst->cc[i];
if (cc_cur <= 0)
cc_cur = stats->soc_ref.cc[i];
cc = (cc_cur + src->cc[i]) / 2;
/* bounds check to previous */
if (cc > cc_cur + TTF_SOC_QUAL_CC_DELTA_CUR_ABS_MAX)
cc = cc_cur + TTF_SOC_QUAL_CC_DELTA_CUR_ABS_MAX;
else if (cc < cc_cur -TTF_SOC_QUAL_CC_DELTA_CUR_ABS_MAX)
cc = cc_cur - TTF_SOC_QUAL_CC_DELTA_CUR_ABS_MAX;
/* bounds check to reference */
if (cc > max_cc)
cc = max_cc;
else if (cc < min_cc)
cc = min_cc;
pr_debug("%d: cc_cur=%d, ref_cc=%d src->cc=%d, cc=%d\n",
i, cc_cur, stats->soc_ref.cc[i], src->cc[i], cc);
return cc;
}
/* update soc_stats using the charging event
* NOTE: first_soc and last_soc are inclusive, will skip socs that have no
* elap and no cc.
*/
static void ttf_soc_update(struct batt_ttf_stats *stats,
const struct gbms_charging_event *ce_data,
int first_soc, int last_soc)
{
const struct ttf_soc_stats *src = &ce_data->soc_stats;
int i;
for (i = first_soc; i <= last_soc; i++) {
ktime_t elap;
int cc;
/* need to have data on both */
if (!src->elap[i] || !src->cc[i])
continue;
/* average the elap time at soc */
elap = ttf_soc_qual_elap(stats, ce_data, i);
if (elap)
stats->soc_stats.elap[i] = elap;
/* average the coulumb count at soc */
cc = ttf_soc_qual_cc(stats, ce_data, i);
if (cc)
stats->soc_stats.cc[i] = cc;
}
}
void ttf_soc_init(struct ttf_soc_stats *dst)
{
memset(dst, 0, sizeof(*dst));
}
/* Tier estimates --------------------------------------------------------- */
#define TTF_STATS_FMT "[%d,%d %d %ld]"
#define BATT_TTF_TS_VALID(ts) \
(ts->cc_total != 0 && ts->avg_time != 0)
/* TODO: adjust for adapter capability */
static ktime_t ttf_tier_accumulate(const struct ttf_tier_stat *ts,
int vbatt_idx,
const struct batt_ttf_stats *stats)
{
ktime_t estimate = 0;
if (vbatt_idx >= GBMS_STATS_TIER_COUNT)
return 0;
for (; vbatt_idx < GBMS_STATS_TIER_COUNT; vbatt_idx++) {
/* no data in this tier, sorry */
if (!BATT_TTF_TS_VALID(ts))
return -ENODATA;
estimate += ts[vbatt_idx].avg_time;
}
return estimate;
}
/* */
static int ttf_tier_sscan(struct batt_ttf_stats *stats,
const char *buff,
size_t size)
{
int j, len = 0;
memset(&stats->tier_stats, 0, sizeof(stats->tier_stats));
while (buff[len] != '[' && len < size)
len++;
for (j = 0; j < GBMS_STATS_TIER_COUNT; j++) {
sscanf(&buff[len], TTF_STATS_FMT, &stats->tier_stats[j].soc_in,
&stats->tier_stats[j].cc_in,
&stats->tier_stats[j].cc_total,
&stats->tier_stats[j].avg_time);
len += sizeof(TTF_STATS_FMT) - 1;
}
return 0;
}
int ttf_tier_cstr(char *buff, int size, const struct ttf_tier_stat *tier_stats)
{
int len = 0;
len += scnprintf(&buff[len], size - len,
TTF_STATS_FMT,
tier_stats->soc_in >> 8,
tier_stats->cc_in,
tier_stats->cc_total,
tier_stats->avg_time);
return len;
}
/* average soc_in, cc_in, cc_total an and avg time for charge tier */
static void ttf_tier_update_stats(struct ttf_tier_stat *ttf_ts,
const struct gbms_ce_tier_stats *chg_ts,
bool force)
{
int elap;
if (!force) {
if (chg_ts->cc_total == 0)
return;
/* TODO: check dsg, qualify with adapter? */
}
/* TODO: check with -1 */
if (ttf_ts->soc_in == 0)
ttf_ts->soc_in = chg_ts->soc_in;
ttf_ts->soc_in = (ttf_ts->soc_in + chg_ts->soc_in) / 2;
/* TODO: check with -1 */
if (ttf_ts->cc_in == 0)
ttf_ts->cc_in = chg_ts->cc_in;
ttf_ts->cc_in = (ttf_ts->cc_in + chg_ts->cc_in) / 2;
if (ttf_ts->cc_total == 0)
ttf_ts->cc_total = chg_ts->cc_total;
ttf_ts->cc_total = (ttf_ts->cc_total + chg_ts->cc_total) / 2;
/* */
elap = chg_ts->time_fast + chg_ts->time_taper + chg_ts->time_other;
if (ttf_ts->avg_time == 0)
ttf_ts->avg_time = elap;
/* qualify time with ratio */
ttf_ts->avg_time =(ttf_ts->avg_time + elap) / 2;
}
/* updated tier stats using the charging event
* NOTE: the ce has data from 1+ charging voltage and temperature tiers */
static void ttf_tier_update(struct batt_ttf_stats *stats,
const struct gbms_charging_event *data,
bool force)
{
int i;
for (i = 0; i < GBMS_STATS_TIER_COUNT; i++) {
const bool last_tier = i == (GBMS_STATS_TIER_COUNT - 1);
const struct gbms_ce_tier_stats *chg_ts = &data->tier_stats[i];
const struct gbms_ce_stats *chg_s = &data->charging_stats;
long elap;
/* skip data that has a temperature switch */
if (chg_ts->temp_idx == -1)
continue;
/* or entries that have no actual charging */
elap = chg_ts->time_fast + chg_ts->time_taper;
if (!elap)
continue;
/* update first tier stats only at low soc_in */
if (!force && i == 0 && (chg_ts->soc_in >> 8) > 1)
continue;
/* update last tier stats only at full */
if (!force && last_tier && ((chg_s->ssoc_out >> 8) != 100))
continue;
/* */
ttf_tier_update_stats(&stats->tier_stats[i], chg_ts, false);
}
}
/* tier estimates only, */
int ttf_tier_estimate(ktime_t *res, const struct batt_ttf_stats *stats,
int temp_idx, int vbatt_idx,
int capacity, int full_capacity)
{
ktime_t estimate = 0;
const struct ttf_tier_stat *ts;
/* tier estimates, only when in tier */
if (vbatt_idx == -1 && temp_idx == -1)
return -EINVAL;
ts = &stats->tier_stats[vbatt_idx];
if (!ts || !BATT_TTF_TS_VALID(ts))
return -ENODATA;
/* accumulate next tier */
estimate = ttf_tier_accumulate(ts, vbatt_idx + 1, stats);
if (estimate < 0)
return -ENODATA;
/* eyeball current tier
* estimate =
* (ts->cc_in + ts->cc_total - capacity) *
* rs->avg_time) / ts->cc_total
*/
/* TODO: adjust for crossing thermals? */
*res = estimate;
return 0;
}
/* ----------------------------------------------------------------------- */
/* QUAL DELTA >= 3 */
#define TTF_STATS_QUAL_DELTA_MIN 3
#define TTF_STATS_QUAL_DELTA TTF_STATS_QUAL_DELTA_MIN
static int ttf_soc_cstr_elap(char *buff, int size,
const struct ttf_soc_stats *soc_stats,
int start, int end)
{
int i, len = 0;
len += scnprintf(&buff[len], size - len, "T%d:", start);
for (i = start; i < end; i++)
len += scnprintf(&buff[len], size - len, " %4ld",
soc_stats->elap[i]);
return len;
}
static int ttf_soc_cstr_cc(char *buff, int size,
const struct ttf_soc_stats *soc_stats,
int start, int end)
{
int i, len = 0;
len += scnprintf(&buff[len], size - len, "C%d:", start);
for (i = start; i < end; i++)
len += scnprintf(&buff[len], size - len, " %4d",
soc_stats->cc[i]);
return len;
}
/* update ttf tier and soc stats using the charging event.
* call holding stats->lock
*/
void ttf_stats_update(struct batt_ttf_stats *stats,
struct gbms_charging_event *ce_data,
bool force)
{
int first_soc = ce_data->charging_stats.ssoc_in;
const int last_soc = ce_data->last_soc;
const int delta_soc = last_soc - first_soc;
const int limit = force ? TTF_STATS_QUAL_DELTA_MIN :
TTF_STATS_QUAL_DELTA;
const int tmp_size = PAGE_SIZE;
char *tmp;
/* skip data short periods */
if (delta_soc < limit) {
ttf_log(stats, "no updates delta_soc=%d, limit=%d, force=%d",
delta_soc, limit, force);
return;
}
/* ignore first nozero and last entry because they are partial */
for ( ; first_soc <= last_soc; first_soc++)
if (ce_data->soc_stats.elap[first_soc] != 0)
break;
ttf_soc_update(stats, ce_data, first_soc + 1, last_soc - 1);
ttf_tier_update(stats, ce_data, force);
/* dump update stats to logbuffer */
tmp = kzalloc(tmp_size, GFP_KERNEL);
if (tmp) {
const int split = 10;
int i;
for (i = first_soc + 1;i < last_soc - 1; i += split) {
int end_soc = i + split;
if (end_soc > last_soc - 1)
end_soc = last_soc - 1;
ttf_soc_cstr_elap(tmp, tmp_size, &stats->soc_stats,
i, end_soc);
ttf_log(stats, "%s", tmp);
ttf_soc_cstr_cc(tmp, tmp_size, &stats->soc_stats,
i, end_soc);
ttf_log(stats, "%s", tmp);
}
kfree(tmp);
}
}
static int ttf_init_soc_parse_dt(struct ttf_adapter_stats *as,
struct device *device)
{
int table_count;
int ret;
table_count = of_property_count_elems_of_size(device->of_node,
"google,ttf-soc-table",
sizeof(u32));
if (table_count <= 0)
return -EINVAL;
if (table_count % 2)
return -EINVAL;
as->soc_table = devm_kzalloc(device, table_count * 2 * sizeof(u32),
GFP_KERNEL);
if (!as->soc_table)
return -ENOMEM;
ret = of_property_read_u32_array(device->of_node,
"google,ttf-soc-table",
as->soc_table, table_count);
if (ret < 0) {
pr_err("cannot read google,ttf-soc-table %d\n", ret);
return ret;
}
as->elap_table = &as->soc_table[table_count];
ret = of_property_read_u32_array(device->of_node,
"google,ttf-elap-table",
as->elap_table, table_count);
if (ret < 0) {
pr_err("cannot read google,ttf-elap-table %d\n", ret);
return ret;
}
as->table_count = table_count;
return 0;
}
int ttf_stats_sscan(struct batt_ttf_stats *stats,
const char *buff,
size_t size)
{
/* TODO: scan ttf_soc_* data as well */
return ttf_tier_sscan(stats, buff, size);
}
static int ttf_as_default(struct ttf_adapter_stats *as, int i, int table_i)
{
while (i > as->soc_table[table_i] && table_i < as->table_count)
table_i++;
return table_i;
}
static int ttf_init_tier_parse_dt(struct batt_ttf_stats *stats,
struct device *device)
{
int i, count, ret;
u32 tier_table[GBMS_STATS_TIER_COUNT];
count = of_property_count_elems_of_size(device->of_node,
"google,ttf-tier-table",
sizeof(u32));
if (count != GBMS_STATS_TIER_COUNT)
return -EINVAL;
ret = of_property_read_u32_array(device->of_node,
"google,ttf-tier-table",
tier_table, count);
if (ret < 0) {
pr_err("cannot read google,ttf-tier-table %d\n", ret);
return ret;
}
for (i = 0; i < GBMS_STATS_TIER_COUNT; i++)
stats->tier_stats[i].soc_in = tier_table[i] << 8;
return 0;
}
/* clone and clear the stats */
struct batt_ttf_stats *ttf_stats_dup(struct batt_ttf_stats *dst,
const struct batt_ttf_stats *src)
{
memcpy(dst, src, sizeof(*dst));
memset(&dst->soc_stats, 0, sizeof(dst->soc_stats));
memset(&dst->tier_stats, 0, sizeof(dst->tier_stats));
return dst;
}
static void ttf_init_ref_table(struct batt_ttf_stats *stats,
struct ttf_adapter_stats *as,
int capacity_ma)
{
int i, table_i = 0;
const int cc = (capacity_ma * 100) / GBMS_SOC_STATS_LEN;
for (i = 0; i < GBMS_SOC_STATS_LEN; i++) {
table_i = ttf_as_default(as, i, table_i);
stats->soc_ref.elap[i] = as->elap_table[table_i];
/* assume same cc for each soc */
stats->soc_ref.cc[i] = (cc * i) / 100;
}
/* TODO: allocate as->soc_table witk kzalloc, free here */
}
/* must come after charge profile */
int ttf_stats_init(struct batt_ttf_stats *stats, struct device *device,
int capacity_ma)
{
struct ttf_adapter_stats as;
u32 value;
int ret;
memset(stats, 0, sizeof(*stats));
stats->ttf_fake = -1;
/* reference adapter */
ret = of_property_read_u32(device->of_node, "google,ttf-adapter",
&value);
if (ret < 0)
return ret;
stats->ref_watts = value;
/* reference temperature */
ret = of_property_read_u32(device->of_node, "google,ttf-temp-idx",
&value);
if (ret < 0)
return ret;
stats->ref_temp_idx = value;
/* reference soc estimates */
ret = ttf_init_soc_parse_dt(&as, device);
if (ret < 0)
return ret;
/* reference tier-based statistics */
ret = ttf_init_tier_parse_dt(stats, device);
if (ret < 0)
return ret;
/* initialize the reference stats for the reference soc estimates */
ttf_init_ref_table(stats, &as, capacity_ma);
/* TODO: use the soc stats to calculate cc_in */
stats->tier_stats[0].cc_in = 0;
stats->tier_stats[1].cc_in = (capacity_ma *
stats->tier_stats[1].soc_in) /
100;
stats->tier_stats[2].cc_in = (capacity_ma *
stats->tier_stats[2].soc_in) /
100;
/* TODO: use the soc stats to calculate cc_total */
stats->tier_stats[0].cc_total = 0;
stats->tier_stats[1].cc_total = (capacity_ma *
(stats->tier_stats[2].soc_in -
stats->tier_stats[1].soc_in)) /
100;
stats->tier_stats[2].cc_total = capacity_ma -
stats->tier_stats[2].cc_in;
return 0;
}
/* tier and soc details */
ssize_t ttf_dump_details(char *buf, int max_size,
const struct batt_ttf_stats *ttf_stats,
int last_soc)
{
int i, len = 0;
/* interleave tier with SOC data */
for (i = 0; i < GBMS_STATS_TIER_COUNT; i++) {
int next_soc_in;
len += scnprintf(&buf[len], max_size - len, "%d: ", i);
len += ttf_tier_cstr(&buf[len], max_size - len,
&ttf_stats->tier_stats[i]);
len += scnprintf(&buf[len], max_size - len, "\n");
/* continue only first */
if (ttf_stats->tier_stats[i].avg_time == 0)
continue;
if (i == GBMS_STATS_TIER_COUNT - 1) {
next_soc_in = -1;
} else {
next_soc_in = ttf_stats->tier_stats[i + 1].soc_in >> 8;
if (next_soc_in == 0)
next_soc_in = -1;
}
if (next_soc_in == -1)
next_soc_in = last_soc - 1;
len += ttf_soc_cstr(&buf[len], max_size - len,
&ttf_stats->soc_stats,
ttf_stats->tier_stats[i].soc_in >> 8,
next_soc_in);
}
return len;
}