| /* |
| * 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 <misc/logbuffer.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 |
| |
| /* 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_charging_event *ce_data, |
| int temp_idx, int vbatt_idx) |
| { |
| const struct gbms_ce_tier_stats *ts = &ce_data->tier_stats[vbatt_idx]; |
| int elap, amperage; |
| |
| elap = ts->time_fast + ts->time_taper; |
| if (elap <= ELAP_LIMIT_S) |
| amperage = ce_data->adapter_details.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 */ |
| static int ttf_pwr_ibatt(const struct gbms_charging_event *ce_data, |
| int temp_idx, int vbatt_idx) |
| { |
| const struct gbms_ce_tier_stats *ts = &ce_data->tier_stats[vbatt_idx]; |
| int avg_ibatt, elap, sign = 1; |
| |
| elap = ts->time_fast + ts->time_taper; |
| if (elap <= ELAP_LIMIT_S) { |
| pr_debug("%d,%d: fast=%d taper=%d other=%d limit=%d\n", |
| vbatt_idx, temp_idx, |
| ts->time_fast, ts->time_taper, ts->time_other, |
| ELAP_LIMIT_S); |
| return 0; |
| } |
| |
| /* actual */ |
| avg_ibatt = ts->ibatt_sum / (elap + ts->time_other); |
| if (avg_ibatt < 0) |
| sign = -1; |
| |
| pr_debug("%d,%d: fast=%d taper=%d other=%d avg_ibatt=%d\n", |
| vbatt_idx, temp_idx, |
| ts->time_fast, ts->time_taper, ts->time_other, |
| avg_ibatt * sign); |
| |
| return avg_ibatt * sign; |
| } |
| |
| /* nominal voltage tier index for this soc */ |
| static int ttf_pwr_tier(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; |
| } |
| |
| /* nominal average current demand for this tier at max rate |
| * NOTE: tier and soc stats keep track of aging (might not need) |
| */ |
| static int ttf_pwr_avg_cc(const struct batt_ttf_stats *stats, int soc) |
| { |
| const struct ttf_soc_stats *sstat = NULL; |
| int delta_cc; |
| |
| /* soc average current demand */ |
| if (stats->soc_stats.cc[soc] && stats->soc_stats.elap[soc]) |
| sstat = &stats->soc_stats; |
| else if (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]); |
| |
| return (delta_cc * 3600) / sstat->elap[soc]; |
| } |
| |
| /* 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) |
| { |
| int ratio; |
| int avg_cc, pwr_demand; |
| int act_icl, pwr_avail; |
| int act_ibatt, cc_max; |
| int vbatt_idx, temp_idx; |
| const struct gbms_chg_profile *profile = ce_data->chg_profile; |
| |
| vbatt_idx = ttf_pwr_tier(stats, soc); |
| if (vbatt_idx < 0) |
| return -1; |
| |
| /* TODO: compensate with average increase/decrease of temperature? */ |
| temp_idx = ce_data->tier_stats[vbatt_idx].temp_idx; |
| if (temp_idx == -1) { |
| 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; |
| int64_t t_avg = ce_data->tier_stats[vbatt_idx].temp_sum / elap; |
| |
| 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; |
| |
| temp_idx = gbms_msc_temp_idx(profile, t_avg); |
| |
| pr_debug("%d: temp_idx=%d t_avg=%ld sum=%ld elap=%d\n", |
| soc, temp_idx, t_avg, |
| ce_data->tier_stats[vbatt_idx].temp_sum, |
| elap); |
| |
| if (temp_idx < 0) |
| return -1; |
| } |
| |
| /* max tier demand at this temperature index */ |
| cc_max = GBMS_CCCM_LIMITS(profile, temp_idx, vbatt_idx) / 1000; |
| /* statistical demand for soc, account for taper */ |
| avg_cc = ttf_pwr_avg_cc(stats, soc); |
| if (avg_cc <= 0 || avg_cc > cc_max) { |
| pr_debug("%d: demand use default avg_cc=%d->%d\n", |
| soc, avg_cc, cc_max); |
| avg_cc = cc_max; |
| } |
| |
| /* actual battery power demand */ |
| pwr_demand = (profile->volt_limits[vbatt_idx] / 10000) * avg_cc; |
| pr_debug("%d:%d,%d: pwr_demand=%d avg_cc=%d cc_max=%d\n", |
| soc, temp_idx, vbatt_idx, pwr_demand, |
| avg_cc, cc_max); |
| |
| /* actual adapter current capabilities for this tier */ |
| act_icl = ttf_pwr_icl(ce_data, temp_idx, vbatt_idx); |
| if (act_icl <= 0) { |
| pr_debug("%d: negative, null act_icl=%d\n", soc, act_icl); |
| return -1; |
| } |
| |
| /* compensate for temperature (might not need) */ |
| if (temp_idx != stats->ref_temp_idx && cc_max < act_icl) { |
| pr_debug("%d: temp_idx=%d, reduce icl %d->%d\n", |
| soc, temp_idx, act_icl, cc_max); |
| act_icl = cc_max; |
| } |
| |
| /* compensate for system load |
| * NOTE: act_ibatt = 0 means no system load, need to fix later |
| */ |
| act_ibatt = ttf_pwr_ibatt(ce_data, temp_idx, vbatt_idx); |
| if (act_ibatt < 0) { |
| pr_debug("%d: ibatt=%d, discharging\n", soc, act_ibatt); |
| return -1; |
| } else if (act_ibatt > 0 && act_ibatt < act_icl) { |
| pr_debug("%d: sysload ibatt=%d, avg_cc=%d, reduce icl %d->%d\n", |
| soc, act_ibatt, avg_cc, act_icl, act_ibatt); |
| act_icl = act_ibatt; |
| } |
| |
| pwr_avail = (ce_data->adapter_details.ad_voltage * 10 ) * act_icl; |
| |
| /* TODO: scale for efficiency? */ |
| |
| if (pwr_avail < pwr_demand) |
| ratio = (stats->ref_watts * 100000) / pwr_avail; |
| else |
| ratio = 100; |
| |
| pr_debug("%d: pwr_avail=%d, pwr_demand=%d ratio=%d\n", |
| soc, pwr_avail, pwr_demand, ratio); |
| |
| return ratio; |
| } |
| |
| /* SOC estimates --------------------------------------------------------- */ |
| |
| static int ttf_elap(ktime_t *estimate, int i, const struct batt_ttf_stats *stats, |
| const struct gbms_charging_event *ce_data) |
| { |
| int ratio; |
| ktime_t elap; |
| |
| if (i < 0 || i >= 100) { |
| *estimate = 0; |
| return 0; |
| } |
| |
| elap = stats->soc_stats.elap[i]; |
| if (elap == 0) |
| elap = stats->soc_ref.elap[i]; |
| |
| ratio = ttf_pwr_ratio(stats, ce_data, i); |
| if (ratio < 0) { |
| pr_debug("%d: negative ratio=%d\n", i, ratio); |
| return -EINVAL; |
| } |
| |
| pr_debug("i=%d elap=%ld ratio=%d\n", i, elap, ratio); |
| *estimate = elap * ratio; |
| |
| return 0; |
| } |
| |
| /* time to full from SOC% |
| * 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; |
| const int end = qnum_toint(last); |
| const int frac = qnum_fracdgt(soc); |
| int i = qnum_toint(soc); |
| ktime_t estimate = 0; |
| int ret; |
| |
| if (end > 100) |
| return -EINVAL; |
| |
| ret = ttf_elap(&estimate, i, stats, ce_data); |
| if (ret < 0) |
| return ret; |
| |
| /* add ttf_elap starting from i + 1 */ |
| estimate = (estimate * (100 - frac)) / 100; |
| for (i += 1; i < end; i++) { |
| ktime_t elap; |
| |
| 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) */ |
| ret = ttf_elap(&elap, i, stats, ce_data); |
| if (ret < 0) |
| return ret; |
| } |
| |
| estimate += elap; |
| } |
| |
| *res = estimate / 100; |
| return 0; |
| } |
| |
| 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; |
| |
| /* 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; |
| |
| 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, " %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"); |
| |
| 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, " %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; |
| } |
| |
| /* 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; |
| struct ttf_soc_stats *dst = &stats->soc_stats; |
| int i; |
| |
| for (i = first_soc; i <= last_soc; i++) { |
| |
| /* TODO: qualify src->elap[i], src->cc[i] */ |
| /* TODO: bound the changes */ |
| |
| /* average the weighted time at soc */ |
| if (src->elap[i]) { |
| int ratio; |
| ktime_t elap; |
| |
| ratio = ttf_pwr_ratio(stats, ce_data, i); |
| if (ratio < 0) |
| continue; |
| |
| if (dst->elap[i] <= 0) |
| dst->elap[i] = stats->soc_ref.elap[i]; |
| |
| elap = (src->elap[i] * 100) / ratio; |
| |
| pr_debug("%d: dst->elap=%ld, ref_elap=%ld, elap=%ld, src_elap=%ld ratio=%d\n", |
| i, dst->elap[i], stats->soc_ref.elap[i], |
| elap, src->elap[i], ratio); |
| |
| dst->elap[i] = (dst->elap[i] + elap) / 2; |
| } |
| |
| /* average the coulumb count at soc entry */ |
| if (src->cc[i]) { |
| if (dst->cc[i] <= 0) |
| dst->cc[i] = stats->soc_ref.cc[i]; |
| |
| dst->cc[i] = (dst->cc[i] + src->cc[i]) / 2; |
| } |
| } |
| } |
| |
| 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)); |
| |
| 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, 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; |
| } |
| |
| /* tier statistics keep track of average capacity at entry of */ |
| 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; |
| } |
| |
| /* ----------------------------------------------------------------------- */ |
| |
| /* 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; |
| |
| /* ignore the fist non zero because it's partial */ |
| for ( ; first_soc <= ce_data->last_soc; first_soc++) |
| if (ce_data->soc_stats.elap[first_soc] != 0) |
| break; |
| |
| /* ignore fist and last entry because they are partial */ |
| ttf_soc_update(stats, ce_data, first_soc + 1, ce_data->last_soc - 1); |
| |
| ttf_tier_update(stats, ce_data, force); |
| } |
| |
| 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; |
| } |