12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
- */
- #define pr_fmt(fmt) "ALG: %s: " fmt, __func__
- #include <linux/err.h>
- #include <linux/iio/iio.h>
- #include <linux/kernel.h>
- #include <linux/mutex.h>
- #include <linux/power_supply.h>
- #include <linux/qti_power_supply.h>
- #include <linux/slab.h>
- #include <linux/sort.h>
- #include <dt-bindings/iio/qti_power_supply_iio.h>
- #include "fg-alg.h"
- #define FULL_SOC_RAW 255
- #define CAPACITY_DELTA_DECIPCT 500
- #define CENTI_FULL_SOC 10000
- #define CENTI_ICORRECT_C0 105
- #define CENTI_ICORRECT_C1 20
- #define HOURS_TO_SECONDS 3600
- #define OCV_SLOPE_UV 10869
- #define MILLI_UNIT 1000
- #define MICRO_UNIT 1000000
- #define NANO_UNIT 1000000000
- #define DEFAULT_TTF_RUN_PERIOD_MS 10000
- #define DEFAULT_TTF_ITERM_DELTA_MA 200
- static const struct ttf_pt ttf_ln_table[] = {
- { 1000, 0 },
- { 2000, 693 },
- { 4000, 1386 },
- { 6000, 1792 },
- { 8000, 2079 },
- { 16000, 2773 },
- { 32000, 3466 },
- { 64000, 4159 },
- { 128000, 4852 },
- };
- /* Cycle counter APIs */
- /**
- * restore_cycle_count -
- * @counter: Cycle counter object
- *
- * Restores all the counters back from FG/QG during boot
- *
- */
- int restore_cycle_count(struct cycle_counter *counter)
- {
- int rc = 0;
- if (!counter)
- return -ENODEV;
- mutex_lock(&counter->lock);
- rc = counter->restore_count(counter->data, counter->count,
- BUCKET_COUNT);
- if (rc < 0)
- pr_err("failed to restore cycle counter rc=%d\n", rc);
- mutex_unlock(&counter->lock);
- return rc;
- }
- /**
- * clear_cycle_count -
- * @counter: Cycle counter object
- *
- * Clears all the counters stored by FG/QG when a battery is inserted
- * or the profile is re-loaded.
- *
- */
- void clear_cycle_count(struct cycle_counter *counter)
- {
- int rc = 0, i;
- if (!counter)
- return;
- mutex_lock(&counter->lock);
- memset(counter->count, 0, sizeof(counter->count));
- for (i = 0; i < BUCKET_COUNT; i++) {
- counter->started[i] = false;
- counter->last_soc[i] = 0;
- }
- rc = counter->store_count(counter->data, counter->count, 0,
- BUCKET_COUNT * 2);
- if (rc < 0)
- pr_err("failed to clear cycle counter rc=%d\n", rc);
- mutex_unlock(&counter->lock);
- }
- /**
- * store_cycle_count -
- * @counter: Cycle counter object
- * @id: Cycle counter bucket id
- *
- * Stores the cycle counter for a bucket in FG/QG.
- *
- */
- static int store_cycle_count(struct cycle_counter *counter, int id)
- {
- int rc = 0;
- u16 cyc_count;
- if (!counter)
- return -ENODEV;
- if (id < 0 || (id > BUCKET_COUNT - 1)) {
- pr_err("Invalid id %d\n", id);
- return -EINVAL;
- }
- cyc_count = counter->count[id];
- cyc_count++;
- rc = counter->store_count(counter->data, &cyc_count, id, 2);
- if (rc < 0) {
- pr_err("failed to write cycle_count[%d] rc=%d\n",
- id, rc);
- return rc;
- }
- counter->count[id] = cyc_count;
- pr_debug("Stored count %d in id %d\n", cyc_count, id);
- return rc;
- }
- /**
- * cycle_count_update -
- * @counter: Cycle counter object
- * @batt_soc: Battery State of Charge (SOC)
- * @charge_status: Charging status from power supply
- * @charge_done: Indicator for charge termination
- * @input_present: Indicator for input presence
- *
- * Called by FG/QG whenever there is a state change (Charging status, SOC)
- *
- */
- void cycle_count_update(struct cycle_counter *counter, int batt_soc,
- int charge_status, bool charge_done, bool input_present)
- {
- int rc = 0, id, i, soc_thresh;
- if (!counter)
- return;
- mutex_lock(&counter->lock);
- /* Find out which id the SOC falls in */
- id = batt_soc / BUCKET_SOC_PCT;
- if (charge_status == POWER_SUPPLY_STATUS_CHARGING) {
- if (!counter->started[id] && id != counter->last_bucket) {
- counter->started[id] = true;
- counter->last_soc[id] = batt_soc;
- }
- } else if (charge_done || !input_present) {
- for (i = 0; i < BUCKET_COUNT; i++) {
- soc_thresh = counter->last_soc[i] + BUCKET_SOC_PCT / 2;
- if (counter->started[i] && batt_soc > soc_thresh) {
- rc = store_cycle_count(counter, i);
- if (rc < 0)
- pr_err("Error in storing cycle_ctr rc: %d\n",
- rc);
- counter->last_soc[i] = 0;
- counter->started[i] = false;
- counter->last_bucket = i;
- }
- }
- }
- pr_debug("batt_soc: %d id: %d chg_status: %d\n", batt_soc, id,
- charge_status);
- mutex_unlock(&counter->lock);
- }
- /**
- * get_bucket_cycle_count -
- * @counter: Cycle counter object
- *
- * Returns the cycle counter for a SOC bucket.
- *
- */
- static int get_bucket_cycle_count(struct cycle_counter *counter)
- {
- int count;
- if (!counter)
- return 0;
- if ((counter->id <= 0) || (counter->id > BUCKET_COUNT))
- return -EINVAL;
- mutex_lock(&counter->lock);
- count = counter->count[counter->id - 1];
- mutex_unlock(&counter->lock);
- return count;
- }
- /**
- * get_cycle_count -
- * @counter: Cycle counter object
- * @count: Average cycle count returned to the caller
- *
- * Get average cycle count for all buckets
- *
- */
- int get_cycle_count(struct cycle_counter *counter, int *count)
- {
- int i, rc, temp = 0;
- for (i = 1; i <= BUCKET_COUNT; i++) {
- counter->id = i;
- rc = get_bucket_cycle_count(counter);
- if (rc < 0) {
- pr_err("Couldn't get cycle count rc=%d\n", rc);
- return rc;
- }
- temp += rc;
- }
- /*
- * Normalize the counter across each bucket so that we can get
- * the overall charge cycle count.
- */
- *count = temp / BUCKET_COUNT;
- return 0;
- }
- /**
- * get_cycle_counts -
- * @counter: Cycle counter object
- * @buf: Bucket cycle counts formatted in a string returned to the caller
- *
- * Get cycle count for all buckets in a string format
- *
- */
- int get_cycle_counts(struct cycle_counter *counter, const char **buf)
- {
- int i, rc, len = 0;
- for (i = 1; i <= BUCKET_COUNT; i++) {
- counter->id = i;
- rc = get_bucket_cycle_count(counter);
- if (rc < 0) {
- pr_err("Couldn't get cycle count rc=%d\n", rc);
- return rc;
- }
- if (sizeof(counter->str_buf) - len < 8) {
- pr_err("Invalid length %d\n", len);
- return -EINVAL;
- }
- len += scnprintf(counter->str_buf + len, 8, "%d ", rc);
- }
- counter->str_buf[len] = '\0';
- *buf = counter->str_buf;
- return 0;
- }
- /**
- * cycle_count_init -
- * @counter: Cycle counter object
- *
- * FG/QG have to call this during driver probe to validate the required
- * parameters after allocating cycle_counter object.
- *
- */
- int cycle_count_init(struct cycle_counter *counter)
- {
- if (!counter)
- return -ENODEV;
- if (!counter->data || !counter->restore_count ||
- !counter->store_count) {
- pr_err("Invalid parameters for using cycle counter\n");
- return -EINVAL;
- }
- mutex_init(&counter->lock);
- counter->last_bucket = -1;
- return 0;
- }
- /* Capacity learning algorithm APIs */
- /**
- * cap_learning_post_process -
- * @cl: Capacity learning object
- *
- * Does post processing on the learnt capacity based on the user specified
- * or default parameters for the capacity learning algorithm.
- *
- */
- static void cap_learning_post_process(struct cap_learning *cl)
- {
- int64_t max_inc_val, min_dec_val, old_cap;
- int rc;
- if (cl->dt.skew_decipct) {
- pr_debug("applying skew %d on current learnt capacity %lld\n",
- cl->dt.skew_decipct, cl->final_cap_uah);
- cl->final_cap_uah = cl->final_cap_uah *
- (1000 + cl->dt.skew_decipct);
- cl->final_cap_uah = div64_u64(cl->final_cap_uah, 1000);
- }
- max_inc_val = cl->learned_cap_uah * (1000 + cl->dt.max_cap_inc);
- max_inc_val = div64_u64(max_inc_val, 1000);
- min_dec_val = cl->learned_cap_uah * (1000 - cl->dt.max_cap_dec);
- min_dec_val = div64_u64(min_dec_val, 1000);
- old_cap = cl->learned_cap_uah;
- if (cl->final_cap_uah > max_inc_val)
- cl->learned_cap_uah = max_inc_val;
- else if (cl->final_cap_uah < min_dec_val)
- cl->learned_cap_uah = min_dec_val;
- else
- cl->learned_cap_uah = cl->final_cap_uah;
- if (cl->dt.max_cap_limit) {
- max_inc_val = (int64_t)cl->nom_cap_uah * (1000 +
- cl->dt.max_cap_limit);
- max_inc_val = div64_u64(max_inc_val, 1000);
- if (cl->final_cap_uah > max_inc_val) {
- pr_debug("learning capacity %lld goes above max limit %lld\n",
- cl->final_cap_uah, max_inc_val);
- cl->learned_cap_uah = max_inc_val;
- }
- }
- if (cl->dt.min_cap_limit) {
- min_dec_val = (int64_t)cl->nom_cap_uah * (1000 -
- cl->dt.min_cap_limit);
- min_dec_val = div64_u64(min_dec_val, 1000);
- if (cl->final_cap_uah < min_dec_val) {
- pr_debug("learning capacity %lld goes below min limit %lld\n",
- cl->final_cap_uah, min_dec_val);
- cl->learned_cap_uah = min_dec_val;
- }
- }
- if (cl->store_learned_capacity) {
- rc = cl->store_learned_capacity(cl->data, cl->learned_cap_uah);
- if (rc < 0)
- pr_err("Error in storing learned_cap_uah, rc=%d\n", rc);
- }
- pr_debug("final cap_uah = %lld, learned capacity %lld -> %lld uah\n",
- cl->final_cap_uah, old_cap, cl->learned_cap_uah);
- }
- /**
- * cap_wt_learning_process_full_data -
- * @cl: Capacity learning object
- * @delta_batt_soc_pct: percentage change in battery State of Charge
- * @batt_soc_cp: Battery State of Charge in centi-percentage
- *
- * Calculates the final learnt capacity when
- * weighted capacity learning is enabled.
- *
- */
- static int cap_wt_learning_process_full_data(struct cap_learning *cl,
- int delta_batt_soc_pct,
- int batt_soc_cp)
- {
- int64_t del_cap_uah, total_cap_uah,
- res_cap_uah, wt_learnt_cap_uah;
- int delta_batt_soc_cp, res_batt_soc_cp;
- /* If the delta is < 10%, then skip processing full data */
- if (delta_batt_soc_pct < cl->dt.min_delta_batt_soc) {
- pr_debug("batt_soc_delta_pct: %d\n", delta_batt_soc_pct);
- return -ERANGE;
- }
- delta_batt_soc_cp = batt_soc_cp - cl->init_batt_soc_cp;
- res_batt_soc_cp = CENTI_FULL_SOC - batt_soc_cp;
- /* Learnt Capacity from end Battery SOC to CENTI_FULL_SOC */
- res_cap_uah = div64_s64(cl->learned_cap_uah *
- res_batt_soc_cp, CENTI_FULL_SOC);
- total_cap_uah = cl->init_cap_uah + cl->delta_cap_uah + res_cap_uah;
- /*
- * difference in capacity learnt in this
- * charge cycle and previous learnt capacity
- */
- del_cap_uah = total_cap_uah - cl->learned_cap_uah;
- /* Applying weight based on change in battery SOC MSB */
- wt_learnt_cap_uah = div64_s64(del_cap_uah * delta_batt_soc_cp,
- CENTI_FULL_SOC);
- cl->final_cap_uah = cl->learned_cap_uah + wt_learnt_cap_uah;
- pr_debug("wt_learnt_cap_uah=%lld, del_cap_uah=%lld\n",
- wt_learnt_cap_uah, del_cap_uah);
- pr_debug("init_cap_uah=%lld, total_cap_uah=%lld, res_cap_uah=%lld, delta_cap_uah=%lld\n",
- cl->init_cap_uah, cl->final_cap_uah,
- res_cap_uah, cl->delta_cap_uah);
- return 0;
- }
- /**
- * cap_learning_process_full_data -
- * @cl: Capacity learning object
- * @batt_soc_cp: Battery State of Charge in centi-percentage
- *
- * Processes the coulomb counter during charge termination and calculates the
- * delta w.r.to the coulomb counter obtained earlier when the learning begun.
- *
- */
- static int cap_learning_process_full_data(struct cap_learning *cl,
- int batt_soc_cp)
- {
- int rc, cc_soc_sw, cc_soc_delta_pct, delta_batt_soc_pct, batt_soc_pct,
- cc_soc_fraction;
- int64_t cc_soc_cap_uah, cc_soc_fraction_uah;
- rc = cl->get_cc_soc(cl->data, &cc_soc_sw);
- if (rc < 0) {
- pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
- return rc;
- }
- batt_soc_pct = DIV_ROUND_CLOSEST(batt_soc_cp, 100);
- delta_batt_soc_pct = batt_soc_pct - cl->init_batt_soc;
- cc_soc_delta_pct =
- div_s64_rem((int64_t)(cc_soc_sw - cl->init_cc_soc_sw) * 100,
- cl->cc_soc_max, &cc_soc_fraction);
- cc_soc_fraction_uah = div64_s64(cl->learned_cap_uah *
- cc_soc_fraction, (int64_t)cl->cc_soc_max * 100);
- cc_soc_cap_uah = div64_s64(cl->learned_cap_uah * cc_soc_delta_pct, 100);
- cl->delta_cap_uah = cc_soc_cap_uah + cc_soc_fraction_uah;
- pr_debug("cc_soc_delta_pct=%d, cc_soc_cap_uah=%lld, cc_soc_fraction_uah=%lld\n",
- cc_soc_delta_pct, cc_soc_cap_uah, cc_soc_fraction_uah);
- if (cl->dt.cl_wt_enable) {
- rc = cap_wt_learning_process_full_data(cl, delta_batt_soc_pct,
- batt_soc_cp);
- return rc;
- }
- /* If the delta is < 50%, then skip processing full data */
- if (cc_soc_delta_pct < 50) {
- pr_err("cc_soc_delta_pct: %d\n", cc_soc_delta_pct);
- return -ERANGE;
- }
- cl->final_cap_uah = cl->init_cap_uah + cl->delta_cap_uah;
- pr_debug("Current cc_soc=%d cc_soc_delta_pct=%d total_cap_uah=%lld\n",
- cc_soc_sw, cc_soc_delta_pct, cl->final_cap_uah);
- return 0;
- }
- /**
- * cap_learning_begin -
- * @cl: Capacity learning object
- * @batt_soc_cp: Battery State of Charge in centi-percentage
- *
- * Gets the coulomb counter from FG/QG when the conditions are suitable for
- * beginning capacity learning. Also, primes the coulomb counter based on
- * battery SOC if required.
- *
- */
- #define BATT_SOC_32BIT GENMASK(31, 0)
- static int cap_learning_begin(struct cap_learning *cl, u32 batt_soc_cp)
- {
- int rc, cc_soc_sw, batt_soc_pct;
- u32 batt_soc_prime;
- if (cl->ok_to_begin && !cl->ok_to_begin(cl->data)) {
- pr_debug("Not OK to begin\n");
- return -EINVAL;
- }
- batt_soc_pct = DIV_ROUND_CLOSEST(batt_soc_cp, 100);
- if ((cl->dt.max_start_soc != -EINVAL &&
- batt_soc_pct > cl->dt.max_start_soc) ||
- (cl->dt.min_start_soc != -EINVAL &&
- batt_soc_pct < cl->dt.min_start_soc)) {
- pr_debug("Battery SOC %d is high/low, not starting\n",
- batt_soc_pct);
- return -EINVAL;
- }
- cl->init_cap_uah = div64_s64(cl->learned_cap_uah * batt_soc_cp,
- CENTI_FULL_SOC);
- if (cl->prime_cc_soc) {
- /*
- * Prime cc_soc_sw with battery SOC when capacity learning
- * begins.
- */
- batt_soc_prime = div64_u64(
- (uint64_t)batt_soc_cp * BATT_SOC_32BIT,
- CENTI_FULL_SOC);
- rc = cl->prime_cc_soc(cl->data, batt_soc_prime);
- if (rc < 0) {
- pr_err("Error in writing cc_soc_sw, rc=%d\n", rc);
- goto out;
- }
- }
- rc = cl->get_cc_soc(cl->data, &cc_soc_sw);
- if (rc < 0) {
- pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
- goto out;
- }
- cl->init_cc_soc_sw = cc_soc_sw;
- cl->init_batt_soc = batt_soc_pct;
- cl->init_batt_soc_cp = batt_soc_cp;
- pr_debug("Capacity learning started @ battery SOC %d init_cc_soc_sw:%d\n",
- batt_soc_cp, cl->init_cc_soc_sw);
- out:
- return rc;
- }
- /**
- * cap_learning_done -
- * @cl: Capacity learning object
- * @batt_soc_cp: Battery State of Charge in centi-percentage
- *
- * Top level function for getting coulomb counter and post processing the
- * data once the capacity learning is complete after charge termination.
- *
- */
- static int cap_learning_done(struct cap_learning *cl, int batt_soc_cp)
- {
- int rc;
- rc = cap_learning_process_full_data(cl, batt_soc_cp);
- if (rc < 0) {
- pr_debug("Error in processing cap learning full data, rc=%d\n",
- rc);
- goto out;
- }
- if (cl->prime_cc_soc) {
- /* Write a FULL value to cc_soc_sw */
- rc = cl->prime_cc_soc(cl->data, cl->cc_soc_max);
- if (rc < 0) {
- pr_err("Error in writing cc_soc_sw, rc=%d\n", rc);
- goto out;
- }
- }
- cap_learning_post_process(cl);
- out:
- return rc;
- }
- /**
- * cap_wt_learning_update -
- * @cl: Capacity learning object
- * @batt_soc_cp: Battery State of Charge in centi-percentage
- * @input_present: Indicator for input presence
- *
- * Called by cap_learning_update when weighted learning is enabled
- *
- */
- static void cap_wt_learning_update(struct cap_learning *cl, int batt_soc_cp,
- bool input_present)
- {
- int rc;
- if (!input_present) {
- rc = cap_learning_done(cl, batt_soc_cp);
- if (rc < 0)
- pr_debug("Error in completing capacity learning, rc=%d\n",
- rc);
- cl->active = false;
- cl->init_cap_uah = 0;
- }
- }
- /**
- * cap_learning_update -
- * @cl: Capacity learning object
- * @batt_temp - Battery temperature
- * @batt_soc: Battery State of Charge (SOC)
- * @charge_status: Charging status from power supply
- * @charge_done: Indicator for charge termination
- * @input_present: Indicator for input presence
- * @qnovo_en: Indicator for Qnovo enable status
- *
- * Called by FG/QG driver when there is a state change (Charging status, SOC)
- *
- */
- void cap_learning_update(struct cap_learning *cl, int batt_temp,
- int batt_soc_cp, int charge_status, bool charge_done,
- bool input_present, bool qnovo_en)
- {
- int rc;
- u32 batt_soc_prime;
- bool prime_cc = false;
- if (!cl)
- return;
- mutex_lock(&cl->lock);
- if (batt_temp > cl->dt.max_temp || batt_temp < cl->dt.min_temp ||
- !cl->learned_cap_uah) {
- cl->active = false;
- cl->init_cap_uah = 0;
- goto out;
- }
- pr_debug("Charge_status: %d active: %d batt_soc: %d\n",
- charge_status, cl->active, batt_soc_cp);
- if (cl->active && cl->dt.cl_wt_enable)
- cap_wt_learning_update(cl, batt_soc_cp, input_present);
- /* Initialize the starting point of learning capacity */
- if (!cl->active) {
- if (charge_status == POWER_SUPPLY_STATUS_CHARGING) {
- rc = cap_learning_begin(cl, batt_soc_cp);
- cl->active = (rc == 0);
- } else {
- if (charge_status == POWER_SUPPLY_STATUS_DISCHARGING ||
- charge_done)
- prime_cc = true;
- }
- } else {
- if (charge_done) {
- rc = cap_learning_done(cl, batt_soc_cp);
- if (rc < 0)
- pr_err("Error in completing capacity learning, rc=%d\n",
- rc);
- cl->active = false;
- cl->init_cap_uah = 0;
- }
- if (charge_status == POWER_SUPPLY_STATUS_DISCHARGING &&
- !input_present) {
- pr_debug("Capacity learning aborted @ battery SOC %d\n",
- batt_soc_cp);
- cl->active = false;
- cl->init_cap_uah = 0;
- prime_cc = true;
- }
- if (charge_status == POWER_SUPPLY_STATUS_NOT_CHARGING &&
- !cl->dt.cl_wt_enable) {
- if (qnovo_en && input_present) {
- /*
- * Don't abort the capacity learning when qnovo
- * is enabled and input is present where the
- * charging status can go to "not charging"
- * intermittently.
- */
- } else {
- pr_debug("Capacity learning aborted @ battery SOC %d\n",
- batt_soc_cp);
- cl->active = false;
- cl->init_cap_uah = 0;
- prime_cc = true;
- }
- }
- }
- /*
- * Prime CC_SOC_SW when the device is not charging or during charge
- * termination when the capacity learning is not active.
- */
- if (prime_cc && cl->prime_cc_soc) {
- /* pass 32-bit batt_soc to the priming logic */
- if (charge_done)
- batt_soc_prime = cl->cc_soc_max;
- else
- batt_soc_prime = div64_u64(
- (uint64_t)batt_soc_cp * BATT_SOC_32BIT,
- CENTI_FULL_SOC);
- rc = cl->prime_cc_soc(cl->data, batt_soc_prime);
- if (rc < 0)
- pr_err("Error in writing cc_soc_sw, rc=%d\n",
- rc);
- }
- out:
- mutex_unlock(&cl->lock);
- }
- /**
- * cap_learning_abort -
- * @cl: Capacity learning object
- *
- * Aborts the capacity learning and initializes variables
- *
- */
- void cap_learning_abort(struct cap_learning *cl)
- {
- if (!cl)
- return;
- mutex_lock(&cl->lock);
- pr_debug("Aborting cap_learning\n");
- cl->active = false;
- cl->init_cap_uah = 0;
- mutex_unlock(&cl->lock);
- }
- /**
- * cap_learning_post_profile_init -
- * @cl: Capacity learning object
- * @nom_cap_uah: Nominal capacity of battery in uAh
- *
- * Called by FG/QG once the profile load is complete and nominal capacity
- * of battery is known. This also gets the last learned capacity back from
- * FG/QG to feed back to the algorithm.
- *
- */
- int cap_learning_post_profile_init(struct cap_learning *cl, int64_t nom_cap_uah)
- {
- int64_t delta_cap_uah, pct_nom_cap_uah;
- int rc;
- if (!cl || !cl->data)
- return -EINVAL;
- mutex_lock(&cl->lock);
- cl->nom_cap_uah = nom_cap_uah;
- rc = cl->get_learned_capacity(cl->data, &cl->learned_cap_uah);
- if (rc < 0) {
- pr_err("Couldn't get learned capacity, rc=%d\n", rc);
- goto out;
- }
- if (cl->learned_cap_uah != cl->nom_cap_uah) {
- if (cl->learned_cap_uah == 0)
- cl->learned_cap_uah = cl->nom_cap_uah;
- delta_cap_uah = abs(cl->learned_cap_uah - cl->nom_cap_uah);
- pct_nom_cap_uah = div64_s64((int64_t)cl->nom_cap_uah *
- CAPACITY_DELTA_DECIPCT, 1000);
- /*
- * If the learned capacity is out of range by 50% from the
- * nominal capacity, then overwrite the learned capacity with
- * the nominal capacity.
- */
- if (cl->nom_cap_uah && delta_cap_uah > pct_nom_cap_uah) {
- pr_debug("learned_cap_uah: %lld is higher than expected, capping it to nominal: %lld\n",
- cl->learned_cap_uah, cl->nom_cap_uah);
- cl->learned_cap_uah = cl->nom_cap_uah;
- }
- rc = cl->store_learned_capacity(cl->data, cl->learned_cap_uah);
- if (rc < 0)
- pr_err("Error in storing learned_cap_uah, rc=%d\n", rc);
- }
- out:
- mutex_unlock(&cl->lock);
- return rc;
- }
- /**
- * cap_learning_init -
- * @cl: Capacity learning object
- *
- * FG/QG have to call this during driver probe to validate the required
- * parameters after allocating cap_learning object.
- *
- */
- int cap_learning_init(struct cap_learning *cl)
- {
- if (!cl)
- return -ENODEV;
- if (!cl->get_learned_capacity || !cl->store_learned_capacity ||
- !cl->get_cc_soc) {
- pr_err("Insufficient functions for supporting capacity learning\n");
- return -EINVAL;
- }
- if (!cl->cc_soc_max) {
- pr_err("Insufficient parameters for supporting capacity learning\n");
- return -EINVAL;
- }
- mutex_init(&cl->lock);
- return 0;
- }
- /* SOH based profile loading */
- static int write_int_iio_chan(struct iio_channel *iio_chan_list,
- int chan_id, int val)
- {
- do {
- if (iio_chan_list->channel->channel == chan_id)
- return iio_write_channel_raw(iio_chan_list,
- val);
- } while (iio_chan_list++);
- return -ENOENT;
- }
- /**
- * soh_get_batt_age_level -
- * @sp: SOH profile object
- * @soh: SOH level
- * @batt_age_level: Battery age level if exists for the SOH passed
- *
- */
- static int soh_get_batt_age_level(struct soh_profile *sp, int soh,
- int *batt_age_level)
- {
- struct soh_range *range = sp->soh_data;
- int i;
- for (i = 0; i < sp->profile_count; i++) {
- if (is_between(range[i].soh_min, range[i].soh_max, soh)) {
- *batt_age_level = range[i].batt_age_level;
- return 0;
- }
- }
- return -ENOENT;
- }
- /**
- * soh_profile_update -
- * @sp: SOH profile object
- * @new_soh: SOH level that is updated and notified to FG/QG driver
- *
- * FG/QG have to call this whenever SOH is notified by the userspace.
- *
- */
- int soh_profile_update(struct soh_profile *sp, int new_soh)
- {
- int rc, batt_age_level = 0;
- if (!sp || !sp->bms_psy || !sp->iio_chan_list)
- return -ENODEV;
- if (new_soh <= 0)
- return 0;
- if (sp->last_soh <= 0)
- pr_debug("SOH initialized to %d\n", new_soh);
- else if (new_soh != sp->last_soh)
- pr_debug("SOH changed from %d to %d\n", sp->last_soh, new_soh);
- sp->last_soh = new_soh;
- rc = soh_get_batt_age_level(sp, new_soh, &batt_age_level);
- if (rc < 0)
- return rc;
- if (batt_age_level != sp->last_batt_age_level) {
- rc = write_int_iio_chan(sp->iio_chan_list,
- PSY_IIO_BATT_AGE_LEVEL, batt_age_level);
- if (rc < 0) {
- pr_err("Couldn't set batt_age_level rc=%d\n", rc);
- return rc;
- }
- sp->last_batt_age_level = batt_age_level;
- pr_info("Batt_age_level set to %d for SOH %d\n",
- batt_age_level, new_soh);
- }
- return 0;
- }
- /**
- * soh_profile_init -
- * @dev: Device node of FG/QG
- * @sp: SOH profile object
- *
- * FG/QG have to call this after parsing battery profile node and multiple
- * profile load feature is enabled. SOH profile object should have at least
- * the power supply of FG/QG and battery profile node. SOH specific range
- * data is allocated by this function.
- *
- */
- int soh_profile_init(struct device *dev, struct soh_profile *sp)
- {
- int rc, profile_count = 0;
- if (!dev || !sp || !sp->bp_node || !sp->bms_psy ||
- !sp->iio_chan_list)
- return -ENODEV;
- rc = of_batterydata_get_aged_profile_count(sp->bp_node,
- sp->batt_id_kohms, &profile_count);
- if (rc < 0) {
- pr_err("Couldn't get profile count rc=%d\n", rc);
- return rc;
- }
- sp->soh_data = devm_kcalloc(dev, profile_count, sizeof(*sp->soh_data),
- GFP_KERNEL);
- if (!sp->soh_data)
- return -ENOMEM;
- rc = of_batterydata_read_soh_aged_profiles(sp->bp_node,
- sp->batt_id_kohms, sp->soh_data);
- if (rc < 0) {
- pr_err("Couldn't read SOH data for profile loading, rc=%d\n",
- rc);
- return rc;
- }
- sp->profile_count = profile_count;
- sp->last_soh = -EINVAL;
- sp->initialized = true;
- return 0;
- }
- /* Time to full/empty algorithm helper functions */
- static void ttf_circ_buf_add(struct ttf_circ_buf *buf, int val)
- {
- buf->arr[buf->head] = val;
- buf->head = (buf->head + 1) % ARRAY_SIZE(buf->arr);
- buf->size = min(++buf->size, (int)ARRAY_SIZE(buf->arr));
- }
- static void ttf_circ_buf_clr(struct ttf_circ_buf *buf)
- {
- buf->size = 0;
- buf->head = 0;
- memset(buf->arr, 0, sizeof(buf->arr));
- }
- static int cmp_int(const void *a, const void *b)
- {
- return *(int *)a - *(int *)b;
- }
- static int ttf_circ_buf_median(struct ttf_circ_buf *buf, int *median)
- {
- int *temp;
- if (buf->size == 0)
- return -ENODATA;
- if (buf->size == 1) {
- *median = buf->arr[0];
- return 0;
- }
- temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL);
- if (!temp)
- return -ENOMEM;
- memcpy(temp, buf->arr, buf->size * sizeof(*temp));
- sort(temp, buf->size, sizeof(*temp), cmp_int, NULL);
- if (buf->size % 2)
- *median = temp[buf->size / 2];
- else
- *median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2;
- kfree(temp);
- return 0;
- }
- static int ttf_lerp(const struct ttf_pt *pts, size_t tablesize,
- s32 input, s32 *output)
- {
- int i;
- s64 temp;
- if (pts == NULL) {
- pr_err("Table is NULL\n");
- return -EINVAL;
- }
- if (tablesize < 1) {
- pr_err("Table has no entries\n");
- return -ENOENT;
- }
- if (tablesize == 1) {
- *output = pts[0].y;
- return 0;
- }
- if (pts[0].x > pts[1].x) {
- pr_err("Table is not in acending order\n");
- return -EINVAL;
- }
- if (input <= pts[0].x) {
- *output = pts[0].y;
- return 0;
- }
- if (input >= pts[tablesize - 1].x) {
- *output = pts[tablesize - 1].y;
- return 0;
- }
- for (i = 1; i < tablesize; i++) {
- if (input >= pts[i].x)
- continue;
- temp = ((s64)pts[i].y - pts[i - 1].y) *
- ((s64)input - pts[i - 1].x);
- temp = div_s64(temp, pts[i].x - pts[i - 1].x);
- *output = temp + pts[i - 1].y;
- return 0;
- }
- return -EINVAL;
- }
- static int get_step_chg_current_window(struct ttf *ttf)
- {
- struct range_data *step_chg_cfg = ttf->step_chg_cfg;
- int i, rc, curr_window, vbatt;
- if (ttf->mode == TTF_MODE_VBAT_STEP_CHG) {
- rc = ttf->get_ttf_param(ttf->data, TTF_VBAT, &vbatt);
- if (rc < 0) {
- pr_err("failed to get battery voltage, rc=%d\n", rc);
- return rc;
- }
- } else {
- rc = ttf->get_ttf_param(ttf->data, TTF_OCV, &vbatt);
- if (rc < 0) {
- pr_err("failed to get battery OCV, rc=%d\n", rc);
- return rc;
- }
- }
- curr_window = ttf->step_chg_num_params - 1;
- for (i = 0; i < ttf->step_chg_num_params; i++) {
- if (is_between(step_chg_cfg[i].low_threshold,
- step_chg_cfg[i].high_threshold,
- vbatt))
- curr_window = i;
- }
- return curr_window;
- }
- static int get_cc2cv_current(struct ttf *ttf, int ibatt_avg, int vbatt_avg,
- int float_volt_uv)
- {
- int i_cc2cv = 0;
- switch (ttf->mode) {
- case TTF_MODE_NORMAL:
- case TTF_MODE_VBAT_STEP_CHG:
- case TTF_MODE_OCV_STEP_CHG:
- i_cc2cv = ibatt_avg * vbatt_avg /
- max(MILLI_UNIT, float_volt_uv / MILLI_UNIT);
- break;
- case TTF_MODE_QNOVO:
- i_cc2cv = min(
- ttf->cc_step.arr[MAX_CC_STEPS - 1] / MILLI_UNIT,
- ibatt_avg * vbatt_avg /
- max(MILLI_UNIT, float_volt_uv / MILLI_UNIT));
- break;
- default:
- pr_err("TTF mode %d is not supported\n", ttf->mode);
- break;
- }
- return i_cc2cv;
- }
- static int get_time_to_full_locked(struct ttf *ttf, int *val)
- {
- struct step_chg_data *step_chg_data = ttf->step_chg_data;
- struct range_data *step_chg_cfg = ttf->step_chg_cfg;
- int rc, ibatt_avg, vbatt_avg, rbatt = 0, msoc = 0, act_cap_mah = 0,
- i_cc2cv = 0, soc_cc2cv, tau, divisor, iterm = 0, ttf_mode = 0,
- i, soc_per_step, msoc_this_step, msoc_next_step,
- ibatt_this_step, t_predicted_this_step, ttf_slope,
- t_predicted_cv, t_predicted = 0, charge_type = 0, i_step,
- float_volt_uv = 0, valid = 0, charge_status = 0;
- int multiplier, curr_window = 0, pbatt_avg;
- bool power_approx = false;
- s64 delta_ms;
- rc = ttf->get_ttf_param(ttf->data, TTF_TTE_VALID, &valid);
- if (rc < 0) {
- pr_err("failed to get ttf_tte_valid rc=%d\n", rc);
- return rc;
- }
- if (!valid) {
- *val = -1;
- return 0;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_CHG_STATUS, &charge_status);
- if (rc < 0) {
- pr_err("failed to get charge-status rc=%d\n", rc);
- return rc;
- }
- if (charge_status != POWER_SUPPLY_STATUS_CHARGING) {
- *val = -1;
- return 0;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_MSOC, &msoc);
- if (rc < 0) {
- pr_err("failed to get msoc rc=%d\n", rc);
- return rc;
- }
- pr_debug("TTF: msoc=%d\n", msoc);
- /* the battery is considered full if the SOC is 100% */
- if (msoc >= 100) {
- *val = 0;
- return 0;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_MODE, &ttf_mode);
- /* when switching TTF algorithms the TTF needs to be reset */
- if (ttf->mode != ttf_mode) {
- ttf_circ_buf_clr(&ttf->ibatt);
- ttf_circ_buf_clr(&ttf->vbatt);
- ttf->last_ttf = 0;
- ttf->last_ms = 0;
- ttf->mode = ttf_mode;
- }
- /* at least 10 samples are required to produce a stable IBATT */
- if (ttf->ibatt.size < MAX_TTF_SAMPLES) {
- if (ttf->clear_ibatt)
- *val = ttf->last_ttf;
- else
- *val = -1;
- return 0;
- }
- rc = ttf_circ_buf_median(&ttf->ibatt, &ibatt_avg);
- if (rc < 0) {
- pr_err("failed to get IBATT AVG rc=%d\n", rc);
- return rc;
- }
- rc = ttf_circ_buf_median(&ttf->vbatt, &vbatt_avg);
- if (rc < 0) {
- pr_err("failed to get VBATT AVG rc=%d\n", rc);
- return rc;
- }
- ttf->clear_ibatt = false;
- ibatt_avg = -ibatt_avg / MILLI_UNIT;
- vbatt_avg /= MILLI_UNIT;
- rc = ttf->get_ttf_param(ttf->data, TTF_ITERM, &iterm);
- if (rc < 0) {
- pr_err("failed to get iterm rc=%d\n", rc);
- return rc;
- }
- /* clamp ibatt_avg to iterm */
- if (ibatt_avg < abs(iterm))
- ibatt_avg = abs(iterm);
- rc = ttf->get_ttf_param(ttf->data, TTF_RBATT, &rbatt);
- if (rc < 0) {
- pr_err("failed to get battery resistance rc=%d\n", rc);
- return rc;
- }
- rbatt /= MILLI_UNIT;
- rc = ttf->get_ttf_param(ttf->data, TTF_FCC, &act_cap_mah);
- if (rc < 0) {
- pr_err("failed to get ACT_BATT_CAP rc=%d\n", rc);
- return rc;
- }
- pr_debug("TTF: ibatt_avg=%d vbatt_avg=%d rbatt=%d act_cap_mah=%d\n",
- ibatt_avg, vbatt_avg, rbatt, act_cap_mah);
- rc = ttf->get_ttf_param(ttf->data, TTF_VFLOAT, &float_volt_uv);
- if (rc < 0) {
- pr_err("failed to get float_volt_uv rc=%d\n", rc);
- return rc;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_CHG_TYPE, &charge_type);
- if (rc < 0) {
- pr_err("failed to get charge_type rc=%d\n", rc);
- return rc;
- }
- pr_debug("TTF: mode: %d\n", ttf->mode);
- /* estimated battery current at the CC to CV transition */
- i_cc2cv = get_cc2cv_current(ttf, ibatt_avg, vbatt_avg, float_volt_uv);
- pr_debug("TTF: i_cc2cv=%d\n", i_cc2cv);
- /* if we are already in CV state then we can skip estimating CC */
- if (charge_type == POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE)
- goto cv_estimate;
- /* estimated SOC at the CC to CV transition */
- soc_cc2cv = DIV_ROUND_CLOSEST(rbatt * i_cc2cv, OCV_SLOPE_UV);
- soc_cc2cv = 100 - soc_cc2cv;
- pr_debug("TTF: soc_cc2cv=%d\n", soc_cc2cv);
- switch (ttf->mode) {
- case TTF_MODE_NORMAL:
- if (soc_cc2cv - msoc <= 0)
- goto cv_estimate;
- divisor = max(100, (ibatt_avg + i_cc2cv) / 2 * 100);
- t_predicted = div_s64((s64)act_cap_mah * (soc_cc2cv - msoc) *
- HOURS_TO_SECONDS, divisor);
- break;
- case TTF_MODE_QNOVO:
- soc_per_step = 100 / MAX_CC_STEPS;
- for (i = msoc / soc_per_step; i < MAX_CC_STEPS - 1; ++i) {
- msoc_next_step = (i + 1) * soc_per_step;
- if (i == msoc / soc_per_step)
- msoc_this_step = msoc;
- else
- msoc_this_step = i * soc_per_step;
- /* scale ibatt by 85% to account for discharge pulses */
- ibatt_this_step = min(
- ttf->cc_step.arr[i] / MILLI_UNIT,
- ibatt_avg) * 85 / 100;
- divisor = max(100, ibatt_this_step * 100);
- t_predicted_this_step = div_s64((s64)act_cap_mah *
- (msoc_next_step - msoc_this_step) *
- HOURS_TO_SECONDS, divisor);
- t_predicted += t_predicted_this_step;
- pr_debug("TTF: [%d, %d] ma=%d t=%d\n",
- msoc_this_step, msoc_next_step,
- ibatt_this_step, t_predicted_this_step);
- }
- break;
- case TTF_MODE_VBAT_STEP_CHG:
- case TTF_MODE_OCV_STEP_CHG:
- if (!step_chg_data || !step_chg_cfg)
- break;
- pbatt_avg = vbatt_avg * ibatt_avg;
- curr_window = get_step_chg_current_window(ttf);
- if (curr_window < 0) {
- pr_err("Failed to get step charging window\n");
- return curr_window;
- }
- pr_debug("TTF: curr_window: %d pbatt_avg: %d\n", curr_window,
- pbatt_avg);
- t_predicted_this_step = 0;
- for (i = 0; i < ttf->step_chg_num_params; i++) {
- /*
- * If Ibatt_avg differs by step charging threshold by
- * more than 100 mA, then use power approximation to
- * get charging current step.
- */
- if (step_chg_cfg[i].value - ibatt_avg > 100)
- power_approx = true;
- /* Calculate OCV for each window */
- if (power_approx) {
- i_step = pbatt_avg / max(MILLI_UNIT,
- (step_chg_cfg[i].high_threshold /
- MILLI_UNIT));
- } else {
- if (i == curr_window)
- i_step = ((step_chg_cfg[i].value /
- MILLI_UNIT) +
- ibatt_avg) / 2;
- else
- i_step = (step_chg_cfg[i].value /
- MILLI_UNIT);
- }
- if (ttf->mode == TTF_MODE_VBAT_STEP_CHG)
- step_chg_data[i].ocv =
- step_chg_cfg[i].high_threshold -
- (rbatt * i_step);
- else
- step_chg_data[i].ocv =
- step_chg_cfg[i].high_threshold;
- /* Calculate SOC for each window */
- step_chg_data[i].soc = (float_volt_uv -
- step_chg_data[i].ocv) / OCV_SLOPE_UV;
- step_chg_data[i].soc = 100 - step_chg_data[i].soc;
- /* Calculate CC time for each window */
- multiplier = act_cap_mah * HOURS_TO_SECONDS;
- if (curr_window > 0 && i < curr_window)
- t_predicted_this_step = 0;
- else if (i == curr_window)
- t_predicted_this_step =
- div_s64((s64)multiplier *
- (step_chg_data[i].soc - msoc),
- i_step);
- else if (i > 0)
- t_predicted_this_step =
- div_s64((s64)multiplier *
- (step_chg_data[i].soc -
- step_chg_data[i - 1].soc),
- i_step);
- if (t_predicted_this_step < 0)
- t_predicted_this_step = 0;
- t_predicted_this_step =
- DIV_ROUND_CLOSEST(t_predicted_this_step, 100);
- pr_debug("TTF: step: %d i_step: %d OCV: %d SOC: %d t_pred: %d\n",
- i, i_step, step_chg_data[i].ocv,
- step_chg_data[i].soc, t_predicted_this_step);
- t_predicted += t_predicted_this_step;
- }
- break;
- default:
- pr_err("TTF mode %d is not supported\n", ttf->mode);
- break;
- }
- cv_estimate:
- pr_debug("TTF: t_predicted_cc=%d\n", t_predicted);
- if (charge_type == POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE)
- iterm = max(100, abs(iterm));
- else
- iterm = max(100, abs(iterm) + ttf->iterm_delta);
- pr_debug("TTF: iterm=%d\n", iterm);
- if (charge_type == POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE)
- tau = max(MILLI_UNIT, ibatt_avg * MILLI_UNIT / iterm);
- else
- tau = max(MILLI_UNIT, i_cc2cv * MILLI_UNIT / iterm);
- rc = ttf_lerp(ttf_ln_table, ARRAY_SIZE(ttf_ln_table), tau, &tau);
- if (rc < 0) {
- pr_err("failed to interpolate tau rc=%d\n", rc);
- return rc;
- }
- /* tau is scaled linearly from 95% to 100% SOC */
- if (msoc >= 95)
- tau = tau * 2 * (100 - msoc) / 10;
- pr_debug("TTF: tau=%d\n", tau);
- t_predicted_cv = div_s64((s64)act_cap_mah * rbatt * tau *
- HOURS_TO_SECONDS, NANO_UNIT);
- pr_debug("TTF: t_predicted_cv=%d\n", t_predicted_cv);
- t_predicted += t_predicted_cv;
- pr_debug("TTF: t_predicted_prefilter=%d\n", t_predicted);
- if (ttf->last_ms != 0) {
- delta_ms = ktime_ms_delta(ktime_get_boottime(),
- ms_to_ktime(ttf->last_ms));
- if (delta_ms > 10000) {
- ttf_slope = div64_s64(
- ((s64)t_predicted - ttf->last_ttf) *
- MICRO_UNIT, delta_ms);
- if (ttf_slope > -100)
- ttf_slope = -100;
- else if (ttf_slope < -2000)
- ttf_slope = -2000;
- t_predicted = div_s64(
- (s64)ttf_slope * delta_ms, MICRO_UNIT) +
- ttf->last_ttf;
- pr_debug("TTF: ttf_slope=%d\n", ttf_slope);
- } else {
- t_predicted = ttf->last_ttf;
- }
- }
- /* clamp the ttf to 0 */
- if (t_predicted < 0)
- t_predicted = 0;
- pr_debug("TTF: t_predicted_postfilter=%d\n", t_predicted);
- *val = t_predicted;
- return 0;
- }
- /**
- * ttf_get_time_to_full -
- * @ttf: ttf object
- * @val: Average time to full returned to the caller
- *
- * Get Average time to full the battery based on current soc, rbatt
- * battery voltage and charge current etc.
- */
- int ttf_get_time_to_full(struct ttf *ttf, int *val)
- {
- int rc;
- mutex_lock(&ttf->lock);
- rc = get_time_to_full_locked(ttf, val);
- mutex_unlock(&ttf->lock);
- return rc;
- }
- #define DELTA_TTF_IBATT_UA 500000
- static void ttf_work(struct work_struct *work)
- {
- struct ttf *ttf = container_of(work,
- struct ttf, ttf_work.work);
- int rc, ibatt_now, vbatt_now, ttf_now, charge_status, ibatt_avg,
- msoc = 0, charge_done;
- ktime_t ktime_now;
- mutex_lock(&ttf->lock);
- rc = ttf->get_ttf_param(ttf->data, TTF_CHG_STATUS, &charge_status);
- if (rc < 0) {
- pr_err("failed to get charge_status rc=%d\n", rc);
- goto end_work;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_CHG_DONE, &charge_done);
- if (rc < 0) {
- pr_err("failed to get charge_done rc=%d\n", rc);
- goto end_work;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_MSOC, &msoc);
- if (rc < 0) {
- pr_err("failed to get msoc, rc=%d\n", rc);
- goto end_work;
- }
- pr_debug("TTF: charge_status:%d charge_done:%d msoc:%d\n",
- charge_status, charge_done, msoc);
- /* Do not schedule ttf work if SOC is 100% or charge teminated. */
- if (charge_done ||
- ((msoc == 100) &&
- (charge_status == POWER_SUPPLY_STATUS_CHARGING)))
- goto end_work;
- rc = ttf->get_ttf_param(ttf->data, TTF_IBAT, &ibatt_now);
- if (rc < 0) {
- pr_err("failed to get battery current, rc=%d\n", rc);
- goto end_work;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_VBAT, &vbatt_now);
- if (rc < 0) {
- pr_err("failed to get battery voltage, rc=%d\n", rc);
- goto end_work;
- }
- ttf_circ_buf_add(&ttf->ibatt, ibatt_now);
- ttf_circ_buf_add(&ttf->vbatt, vbatt_now);
- if (charge_status == POWER_SUPPLY_STATUS_CHARGING) {
- rc = ttf_circ_buf_median(&ttf->ibatt, &ibatt_avg);
- if (rc < 0) {
- pr_err("failed to get IBATT AVG rc=%d\n", rc);
- goto end_work;
- }
- /*
- * While Charging, if Ibatt_now differ from Ibatt_avg by 500mA,
- * clear Ibatt buffer and refill with settled Ibatt values, to
- * calculate accurate TTF
- */
- if (ibatt_now < 0 && (abs(ibatt_now -
- ibatt_avg) >= DELTA_TTF_IBATT_UA)) {
- pr_debug("Clear Ibatt buffer, Ibatt_avg=%d Ibatt_now=%d\n",
- ibatt_avg, ibatt_now);
- ttf_circ_buf_clr(&ttf->ibatt);
- ttf->clear_ibatt = true;
- }
- rc = get_time_to_full_locked(ttf, &ttf_now);
- if (rc < 0) {
- pr_err("failed to get ttf, rc=%d\n", rc);
- goto end_work;
- }
- /* keep the wake lock and prime the IBATT and VBATT buffers */
- if (ttf_now < 0 || ttf->clear_ibatt) {
- /* delay for one FG cycle */
- schedule_delayed_work(&ttf->ttf_work,
- msecs_to_jiffies(1000));
- mutex_unlock(&ttf->lock);
- return;
- }
- /* update the TTF reference point every minute */
- ktime_now = ktime_get_boottime();
- if (ktime_ms_delta(ktime_now,
- ms_to_ktime(ttf->last_ms)) > 60000 ||
- ttf->last_ms == 0) {
- ttf->last_ttf = ttf_now;
- ttf->last_ms = ktime_to_ms(ktime_now);
- }
- }
- /* recurse every 10 seconds */
- schedule_delayed_work(&ttf->ttf_work, msecs_to_jiffies(ttf->period_ms));
- end_work:
- ttf->awake_voter(ttf->data, false);
- mutex_unlock(&ttf->lock);
- }
- /**
- * ttf_get_time_to_empty -
- * @ttf: ttf object
- * @val: Average time to empty returned to the caller
- *
- * Get Average time to empty the battery based on current soc
- * and average battery current.
- */
- int ttf_get_time_to_empty(struct ttf *ttf, int *val)
- {
- int rc, ibatt_avg, msoc, act_cap_mah, divisor, valid = 0,
- charge_status = 0;
- rc = ttf->get_ttf_param(ttf->data, TTF_TTE_VALID, &valid);
- if (rc < 0) {
- pr_err("failed to get ttf_tte_valid rc=%d\n", rc);
- return rc;
- }
- if (!valid) {
- *val = -1;
- return 0;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_CHG_STATUS, &charge_status);
- if (rc < 0) {
- pr_err("failed to get charge-status rc=%d\n", rc);
- return rc;
- }
- if (charge_status == POWER_SUPPLY_STATUS_CHARGING) {
- *val = -1;
- return 0;
- }
- rc = ttf_circ_buf_median(&ttf->ibatt, &ibatt_avg);
- if (rc < 0) {
- /* try to get instantaneous current */
- rc = ttf->get_ttf_param(ttf->data, TTF_IBAT, &ibatt_avg);
- if (rc < 0) {
- pr_err("failed to get battery current, rc=%d\n", rc);
- return rc;
- }
- }
- ibatt_avg /= MILLI_UNIT;
- /* clamp ibatt_avg to 100mA */
- if (ibatt_avg < 100)
- ibatt_avg = 100;
- rc = ttf->get_ttf_param(ttf->data, TTF_MSOC, &msoc);
- if (rc < 0) {
- pr_err("Error in getting capacity, rc=%d\n", rc);
- return rc;
- }
- rc = ttf->get_ttf_param(ttf->data, TTF_FCC, &act_cap_mah);
- if (rc < 0) {
- pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
- return rc;
- }
- divisor = CENTI_ICORRECT_C0 * 100 + CENTI_ICORRECT_C1 * msoc;
- divisor = ibatt_avg * divisor / 100;
- divisor = max(100, divisor);
- *val = act_cap_mah * msoc * HOURS_TO_SECONDS / divisor;
- pr_debug("TTF: ibatt_avg=%d msoc=%d act_cap_mah=%d TTE=%d\n",
- ibatt_avg, msoc, act_cap_mah, *val);
- return 0;
- }
- /**
- * ttf_update -
- * @ttf: ttf object
- * @input_present: Indicator for input presence
- *
- * Called by FG/QG driver when there is a state change (Charging status, SOC)
- *
- */
- void ttf_update(struct ttf *ttf, bool input_present)
- {
- int delay_ms;
- if (ttf->input_present == input_present)
- return;
- ttf->input_present = input_present;
- if (input_present)
- /* wait 35 seconds for the input to settle */
- delay_ms = 35000;
- else
- /* wait 5 seconds for current to settle during discharge */
- delay_ms = 5000;
- ttf->awake_voter(ttf->data, true);
- cancel_delayed_work_sync(&ttf->ttf_work);
- mutex_lock(&ttf->lock);
- ttf_circ_buf_clr(&ttf->ibatt);
- ttf_circ_buf_clr(&ttf->vbatt);
- ttf->last_ttf = 0;
- ttf->last_ms = 0;
- mutex_unlock(&ttf->lock);
- schedule_delayed_work(&ttf->ttf_work, msecs_to_jiffies(delay_ms));
- }
- /**
- * ttf_tte_init -
- * @ttf: Time to full object
- *
- * FG/QG have to call this during driver probe to validate the required
- * parameters after allocating ttf object.
- *
- */
- int ttf_tte_init(struct ttf *ttf)
- {
- if (!ttf)
- return -ENODEV;
- if (!ttf->awake_voter || !ttf->get_ttf_param) {
- pr_err("Insufficient functions for supporting ttf\n");
- return -EINVAL;
- }
- if (!ttf->iterm_delta)
- ttf->iterm_delta = DEFAULT_TTF_ITERM_DELTA_MA;
- if (!ttf->period_ms)
- ttf->period_ms = DEFAULT_TTF_RUN_PERIOD_MS;
- mutex_init(&ttf->lock);
- INIT_DELAYED_WORK(&ttf->ttf_work, ttf_work);
- return 0;
- }
|