Loading drivers/power/supply/qcom/fg-alg.c +531 −0 Original line number Diff line number Diff line Loading @@ -16,12 +16,38 @@ #include <linux/kernel.h> #include <linux/mutex.h> #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/sort.h> #include "fg-alg.h" #define FULL_SOC_RAW 255 #define FULL_BATT_SOC GENMASK(31, 0) #define CAPACITY_DELTA_DECIPCT 500 #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 */ /** Loading Loading @@ -670,3 +696,508 @@ int cap_learning_init(struct cap_learning *cl) mutex_init(&cl->lock); 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_time_to_full_locked(struct ttf *ttf, int *val) { 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, float_volt_uv = 0; s64 delta_ms; 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) { *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; } 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; } /* estimated battery current at the CC to CV transition */ switch (ttf->mode) { case TTF_MODE_NORMAL: 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; } 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_TAPER) 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; 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); iterm = max(100, abs(iterm) + ttf->iterm_delta); pr_debug("TTF: iterm=%d\n", iterm); if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) 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; } 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; 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; } if (charge_status != POWER_SUPPLY_STATUS_CHARGING && charge_status != POWER_SUPPLY_STATUS_DISCHARGING) 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 = 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) { /* 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; 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; 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; } drivers/power/supply/qcom/fg-alg.h +57 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ #define BUCKET_COUNT 8 #define BUCKET_SOC_PCT (256 / BUCKET_COUNT) #define MAX_CC_STEPS 20 #define MAX_TTF_SAMPLES 10 struct cycle_counter { void *data; Loading Loading @@ -58,6 +60,57 @@ struct cap_learning { int (*prime_cc_soc)(void *data, u32 cc_soc_sw); }; enum ttf_mode { TTF_MODE_NORMAL = 0, TTF_MODE_QNOVO, }; enum ttf_param { TTF_MSOC = 0, TTF_VBAT, TTF_IBAT, TTF_FCC, TTF_MODE, TTF_ITERM, TTF_RBATT, TTF_VFLOAT, TTF_CHG_TYPE, TTF_CHG_STATUS, }; struct ttf_circ_buf { int arr[MAX_TTF_SAMPLES]; int size; int head; }; struct ttf_cc_step_data { int arr[MAX_CC_STEPS]; int sel; }; struct ttf_pt { s32 x; s32 y; }; struct ttf { void *data; struct ttf_circ_buf ibatt; struct ttf_circ_buf vbatt; struct ttf_cc_step_data cc_step; struct mutex lock; int mode; int last_ttf; int input_present; int iterm_delta; int period_ms; s64 last_ms; struct delayed_work ttf_work; int (*get_ttf_param)(void *data, enum ttf_param, int *val); int (*awake_voter)(void *data, bool vote); }; int restore_cycle_count(struct cycle_counter *counter); void clear_cycle_count(struct cycle_counter *counter); void cycle_count_update(struct cycle_counter *counter, int batt_soc, Loading @@ -72,5 +125,9 @@ void cap_learning_update(struct cap_learning *cl, int batt_temp, int cap_learning_init(struct cap_learning *cl); int cap_learning_post_profile_init(struct cap_learning *cl, int64_t nom_cap_uah); void ttf_update(struct ttf *ttf, bool input_present); int ttf_get_time_to_empty(struct ttf *ttf, int *val); int ttf_get_time_to_full(struct ttf *ttf, int *val); int ttf_tte_init(struct ttf *ttf); #endif drivers/power/supply/qcom/qg-core.h +4 −0 Original line number Diff line number Diff line Loading @@ -118,6 +118,7 @@ struct qpnp_qg { bool charge_full; int charge_status; int charge_type; int chg_iterm_ma; int next_wakeup_ms; u32 fifo_done_count; u32 wa_flags; Loading @@ -135,6 +136,7 @@ struct qpnp_qg { int pon_soc; int batt_soc; int cc_soc; int full_soc; struct alarm alarm_timer; u32 sdam_data[SDAM_MAX]; Loading @@ -145,6 +147,8 @@ struct qpnp_qg { struct cap_learning *cl; /* charge counter */ struct cycle_counter *counter; /* ttf */ struct ttf *ttf; }; enum ocv_type { Loading drivers/power/supply/qcom/qg-defs.h +1 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ #define GOOD_OCV_VOTER "GOOD_OCV_VOTER" #define PROFILE_IRQ_DISABLE "NO_PROFILE_IRQ_DISABLE" #define QG_INIT_STATE_IRQ_DISABLE "QG_INIT_STATE_IRQ_DISABLE" #define TTF_AWAKE_VOTER "TTF_AWAKE_VOTER" #define V_RAW_TO_UV(V_RAW) div_u64(194637ULL * (u64)V_RAW, 1000) #define I_RAW_TO_UA(I_RAW) div_s64(152588LL * (s64)I_RAW, 1000) Loading drivers/power/supply/qcom/qpnp-qg.c +135 −0 Original line number Diff line number Diff line Loading @@ -221,6 +221,14 @@ static void qg_notify_charger(struct qpnp_qg *chip) } pr_debug("Notified charger on float voltage and FCC\n"); rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (rc < 0) { pr_err("Failed to get charge term current, rc=%d\n", rc); return; } chip->chg_iterm_ma = prop.intval; } static bool is_batt_available(struct qpnp_qg *chip) Loading Loading @@ -1014,6 +1022,9 @@ static void process_udata_work(struct work_struct *work) if (chip->udata.param[QG_BATT_SOC].valid) chip->batt_soc = chip->udata.param[QG_BATT_SOC].data; if (chip->udata.param[QG_FULL_SOC].valid) chip->full_soc = chip->udata.param[QG_FULL_SOC].data; if (chip->udata.param[QG_SOC].valid) { qg_dbg(chip, QG_DEBUG_SOC, "udata SOC=%d last SOC=%d\n", chip->udata.param[QG_SOC].data, chip->catch_up_soc); Loading Loading @@ -1044,6 +1055,8 @@ static void process_udata_work(struct work_struct *work) if (!chip->dt.esr_disable) qg_store_esr_params(chip); qg_dbg(chip, QG_DEBUG_STATUS, "udata update: batt_soc=%d cc_soc=%d full_soc=%d qg_esr=%d\n", chip->batt_soc, chip->cc_soc, chip->full_soc, chip->esr_last); vote(chip->awake_votable, UDATA_READY_VOTER, false, 0); } Loading Loading @@ -1570,6 +1583,87 @@ static int qg_get_battery_capacity(struct qpnp_qg *chip, int *soc) return 0; } static int qg_get_ttf_param(void *data, enum ttf_param param, int *val) { union power_supply_propval prop = {0, }; struct qpnp_qg *chip = data; int rc = 0; int64_t temp = 0; if (!chip) return -ENODEV; if (chip->battery_missing || !chip->profile_loaded) return -EPERM; switch (param) { case TTF_MSOC: rc = qg_get_battery_capacity(chip, val); break; case TTF_VBAT: rc = qg_get_battery_voltage(chip, val); break; case TTF_IBAT: rc = qg_get_battery_current(chip, val); break; case TTF_FCC: if (chip->qg_psy) { rc = power_supply_get_property(chip->qg_psy, POWER_SUPPLY_PROP_CHARGE_FULL, &prop); if (rc >= 0) { temp = div64_u64(prop.intval, 1000); *val = div64_u64(chip->full_soc * temp, QG_SOC_FULL); } } break; case TTF_MODE: *val = TTF_MODE_NORMAL; break; case TTF_ITERM: if (chip->chg_iterm_ma == INT_MIN) *val = 0; else *val = chip->chg_iterm_ma; break; case TTF_RBATT: rc = qg_sdam_read(SDAM_RBAT_MOHM, val); if (!rc) *val *= 1000; break; case TTF_VFLOAT: *val = chip->bp.float_volt_uv; break; case TTF_CHG_TYPE: *val = chip->charge_type; break; case TTF_CHG_STATUS: *val = chip->charge_status; break; default: pr_err("Unsupported property %d\n", param); rc = -EINVAL; break; } return rc; } static int qg_ttf_awake_voter(void *data, bool val) { struct qpnp_qg *chip = data; if (!chip) return -ENODEV; if (chip->battery_missing || !chip->profile_loaded) return -EPERM; vote(chip->awake_votable, TTF_AWAKE_VOTER, val, 0); return 0; } static int qg_psy_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *pval) Loading Loading @@ -1685,6 +1779,12 @@ static int qg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CYCLE_COUNT: rc = get_cycle_count(chip->counter, &pval->intval); break; case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: rc = ttf_get_time_to_full(chip->ttf, &pval->intval); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: rc = ttf_get_time_to_empty(chip->ttf, &pval->intval); break; default: pr_debug("Unsupported property %d\n", psp); break; Loading Loading @@ -1726,6 +1826,8 @@ static enum power_supply_property qg_psy_props[] = { POWER_SUPPLY_PROP_CYCLE_COUNTS, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, }; static const struct power_supply_desc qg_psy_desc = { Loading Loading @@ -1912,6 +2014,8 @@ static void qg_status_change_work(struct work_struct *work) rc = qg_charge_full_update(chip); if (rc < 0) pr_err("Failed in charge_full_update, rc=%d\n", rc); ttf_update(chip->ttf, chip->usb_present); out: pm_relax(chip->dev); } Loading Loading @@ -2688,10 +2792,12 @@ static int qg_request_irqs(struct qpnp_qg *chip) return 0; } #define QG_TTF_ITERM_DELTA_MA 1 static int qg_alg_init(struct qpnp_qg *chip) { struct cycle_counter *counter; struct cap_learning *cl; struct ttf *ttf; struct device_node *node = chip->dev->of_node; int rc; Loading @@ -2714,6 +2820,28 @@ static int qg_alg_init(struct qpnp_qg *chip) chip->counter = counter; ttf = devm_kzalloc(chip->dev, sizeof(*ttf), GFP_KERNEL); if (!ttf) return -ENOMEM; ttf->get_ttf_param = qg_get_ttf_param; ttf->awake_voter = qg_ttf_awake_voter; ttf->iterm_delta = QG_TTF_ITERM_DELTA_MA; ttf->data = chip; rc = ttf_tte_init(ttf); if (rc < 0) { dev_err(chip->dev, "Error in initializing ttf, rc:%d\n", rc); ttf->data = NULL; counter->data = NULL; devm_kfree(chip->dev, ttf); devm_kfree(chip->dev, counter); return rc; } chip->ttf = ttf; chip->dt.cl_disable = of_property_read_bool(node, "qcom,cl-disable"); Loading @@ -2738,6 +2866,7 @@ static int qg_alg_init(struct qpnp_qg *chip) counter->data = NULL; cl->data = NULL; devm_kfree(chip->dev, counter); devm_kfree(chip->dev, ttf); devm_kfree(chip->dev, cl); return rc; } Loading Loading @@ -3064,6 +3193,7 @@ static int process_suspend(struct qpnp_qg *chip) if (!chip->profile_loaded) return 0; cancel_delayed_work_sync(&chip->ttf->ttf_work); /* disable GOOD_OCV IRQ in sleep */ vote(chip->good_ocv_irq_disable_votable, QG_INIT_STATE_IRQ_DISABLE, true, 0); Loading Loading @@ -3196,6 +3326,8 @@ static int process_resume(struct qpnp_qg *chip) chip->suspend_data = false; } schedule_delayed_work(&chip->ttf->ttf_work, 0); return rc; } Loading Loading @@ -3273,6 +3405,8 @@ static int qpnp_qg_probe(struct platform_device *pdev) chip->maint_soc = -EINVAL; chip->batt_soc = INT_MIN; chip->cc_soc = INT_MIN; chip->full_soc = QG_SOC_FULL; chip->chg_iterm_ma = INT_MIN; rc = qg_alg_init(chip); if (rc < 0) { Loading Loading @@ -3338,6 +3472,7 @@ static int qpnp_qg_probe(struct platform_device *pdev) pr_err("Error in restoring cycle_count, rc=%d\n", rc); return rc; } schedule_delayed_work(&chip->ttf->ttf_work, 10000); } rc = qg_determine_pon_soc(chip); Loading Loading
drivers/power/supply/qcom/fg-alg.c +531 −0 Original line number Diff line number Diff line Loading @@ -16,12 +16,38 @@ #include <linux/kernel.h> #include <linux/mutex.h> #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/sort.h> #include "fg-alg.h" #define FULL_SOC_RAW 255 #define FULL_BATT_SOC GENMASK(31, 0) #define CAPACITY_DELTA_DECIPCT 500 #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 */ /** Loading Loading @@ -670,3 +696,508 @@ int cap_learning_init(struct cap_learning *cl) mutex_init(&cl->lock); 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_time_to_full_locked(struct ttf *ttf, int *val) { 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, float_volt_uv = 0; s64 delta_ms; 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) { *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; } 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; } /* estimated battery current at the CC to CV transition */ switch (ttf->mode) { case TTF_MODE_NORMAL: 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; } 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_TAPER) 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; 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); iterm = max(100, abs(iterm) + ttf->iterm_delta); pr_debug("TTF: iterm=%d\n", iterm); if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) 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; } 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; 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; } if (charge_status != POWER_SUPPLY_STATUS_CHARGING && charge_status != POWER_SUPPLY_STATUS_DISCHARGING) 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 = 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) { /* 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; 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; 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; }
drivers/power/supply/qcom/fg-alg.h +57 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ #define BUCKET_COUNT 8 #define BUCKET_SOC_PCT (256 / BUCKET_COUNT) #define MAX_CC_STEPS 20 #define MAX_TTF_SAMPLES 10 struct cycle_counter { void *data; Loading Loading @@ -58,6 +60,57 @@ struct cap_learning { int (*prime_cc_soc)(void *data, u32 cc_soc_sw); }; enum ttf_mode { TTF_MODE_NORMAL = 0, TTF_MODE_QNOVO, }; enum ttf_param { TTF_MSOC = 0, TTF_VBAT, TTF_IBAT, TTF_FCC, TTF_MODE, TTF_ITERM, TTF_RBATT, TTF_VFLOAT, TTF_CHG_TYPE, TTF_CHG_STATUS, }; struct ttf_circ_buf { int arr[MAX_TTF_SAMPLES]; int size; int head; }; struct ttf_cc_step_data { int arr[MAX_CC_STEPS]; int sel; }; struct ttf_pt { s32 x; s32 y; }; struct ttf { void *data; struct ttf_circ_buf ibatt; struct ttf_circ_buf vbatt; struct ttf_cc_step_data cc_step; struct mutex lock; int mode; int last_ttf; int input_present; int iterm_delta; int period_ms; s64 last_ms; struct delayed_work ttf_work; int (*get_ttf_param)(void *data, enum ttf_param, int *val); int (*awake_voter)(void *data, bool vote); }; int restore_cycle_count(struct cycle_counter *counter); void clear_cycle_count(struct cycle_counter *counter); void cycle_count_update(struct cycle_counter *counter, int batt_soc, Loading @@ -72,5 +125,9 @@ void cap_learning_update(struct cap_learning *cl, int batt_temp, int cap_learning_init(struct cap_learning *cl); int cap_learning_post_profile_init(struct cap_learning *cl, int64_t nom_cap_uah); void ttf_update(struct ttf *ttf, bool input_present); int ttf_get_time_to_empty(struct ttf *ttf, int *val); int ttf_get_time_to_full(struct ttf *ttf, int *val); int ttf_tte_init(struct ttf *ttf); #endif
drivers/power/supply/qcom/qg-core.h +4 −0 Original line number Diff line number Diff line Loading @@ -118,6 +118,7 @@ struct qpnp_qg { bool charge_full; int charge_status; int charge_type; int chg_iterm_ma; int next_wakeup_ms; u32 fifo_done_count; u32 wa_flags; Loading @@ -135,6 +136,7 @@ struct qpnp_qg { int pon_soc; int batt_soc; int cc_soc; int full_soc; struct alarm alarm_timer; u32 sdam_data[SDAM_MAX]; Loading @@ -145,6 +147,8 @@ struct qpnp_qg { struct cap_learning *cl; /* charge counter */ struct cycle_counter *counter; /* ttf */ struct ttf *ttf; }; enum ocv_type { Loading
drivers/power/supply/qcom/qg-defs.h +1 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ #define GOOD_OCV_VOTER "GOOD_OCV_VOTER" #define PROFILE_IRQ_DISABLE "NO_PROFILE_IRQ_DISABLE" #define QG_INIT_STATE_IRQ_DISABLE "QG_INIT_STATE_IRQ_DISABLE" #define TTF_AWAKE_VOTER "TTF_AWAKE_VOTER" #define V_RAW_TO_UV(V_RAW) div_u64(194637ULL * (u64)V_RAW, 1000) #define I_RAW_TO_UA(I_RAW) div_s64(152588LL * (s64)I_RAW, 1000) Loading
drivers/power/supply/qcom/qpnp-qg.c +135 −0 Original line number Diff line number Diff line Loading @@ -221,6 +221,14 @@ static void qg_notify_charger(struct qpnp_qg *chip) } pr_debug("Notified charger on float voltage and FCC\n"); rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (rc < 0) { pr_err("Failed to get charge term current, rc=%d\n", rc); return; } chip->chg_iterm_ma = prop.intval; } static bool is_batt_available(struct qpnp_qg *chip) Loading Loading @@ -1014,6 +1022,9 @@ static void process_udata_work(struct work_struct *work) if (chip->udata.param[QG_BATT_SOC].valid) chip->batt_soc = chip->udata.param[QG_BATT_SOC].data; if (chip->udata.param[QG_FULL_SOC].valid) chip->full_soc = chip->udata.param[QG_FULL_SOC].data; if (chip->udata.param[QG_SOC].valid) { qg_dbg(chip, QG_DEBUG_SOC, "udata SOC=%d last SOC=%d\n", chip->udata.param[QG_SOC].data, chip->catch_up_soc); Loading Loading @@ -1044,6 +1055,8 @@ static void process_udata_work(struct work_struct *work) if (!chip->dt.esr_disable) qg_store_esr_params(chip); qg_dbg(chip, QG_DEBUG_STATUS, "udata update: batt_soc=%d cc_soc=%d full_soc=%d qg_esr=%d\n", chip->batt_soc, chip->cc_soc, chip->full_soc, chip->esr_last); vote(chip->awake_votable, UDATA_READY_VOTER, false, 0); } Loading Loading @@ -1570,6 +1583,87 @@ static int qg_get_battery_capacity(struct qpnp_qg *chip, int *soc) return 0; } static int qg_get_ttf_param(void *data, enum ttf_param param, int *val) { union power_supply_propval prop = {0, }; struct qpnp_qg *chip = data; int rc = 0; int64_t temp = 0; if (!chip) return -ENODEV; if (chip->battery_missing || !chip->profile_loaded) return -EPERM; switch (param) { case TTF_MSOC: rc = qg_get_battery_capacity(chip, val); break; case TTF_VBAT: rc = qg_get_battery_voltage(chip, val); break; case TTF_IBAT: rc = qg_get_battery_current(chip, val); break; case TTF_FCC: if (chip->qg_psy) { rc = power_supply_get_property(chip->qg_psy, POWER_SUPPLY_PROP_CHARGE_FULL, &prop); if (rc >= 0) { temp = div64_u64(prop.intval, 1000); *val = div64_u64(chip->full_soc * temp, QG_SOC_FULL); } } break; case TTF_MODE: *val = TTF_MODE_NORMAL; break; case TTF_ITERM: if (chip->chg_iterm_ma == INT_MIN) *val = 0; else *val = chip->chg_iterm_ma; break; case TTF_RBATT: rc = qg_sdam_read(SDAM_RBAT_MOHM, val); if (!rc) *val *= 1000; break; case TTF_VFLOAT: *val = chip->bp.float_volt_uv; break; case TTF_CHG_TYPE: *val = chip->charge_type; break; case TTF_CHG_STATUS: *val = chip->charge_status; break; default: pr_err("Unsupported property %d\n", param); rc = -EINVAL; break; } return rc; } static int qg_ttf_awake_voter(void *data, bool val) { struct qpnp_qg *chip = data; if (!chip) return -ENODEV; if (chip->battery_missing || !chip->profile_loaded) return -EPERM; vote(chip->awake_votable, TTF_AWAKE_VOTER, val, 0); return 0; } static int qg_psy_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *pval) Loading Loading @@ -1685,6 +1779,12 @@ static int qg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CYCLE_COUNT: rc = get_cycle_count(chip->counter, &pval->intval); break; case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: rc = ttf_get_time_to_full(chip->ttf, &pval->intval); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: rc = ttf_get_time_to_empty(chip->ttf, &pval->intval); break; default: pr_debug("Unsupported property %d\n", psp); break; Loading Loading @@ -1726,6 +1826,8 @@ static enum power_supply_property qg_psy_props[] = { POWER_SUPPLY_PROP_CYCLE_COUNTS, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, }; static const struct power_supply_desc qg_psy_desc = { Loading Loading @@ -1912,6 +2014,8 @@ static void qg_status_change_work(struct work_struct *work) rc = qg_charge_full_update(chip); if (rc < 0) pr_err("Failed in charge_full_update, rc=%d\n", rc); ttf_update(chip->ttf, chip->usb_present); out: pm_relax(chip->dev); } Loading Loading @@ -2688,10 +2792,12 @@ static int qg_request_irqs(struct qpnp_qg *chip) return 0; } #define QG_TTF_ITERM_DELTA_MA 1 static int qg_alg_init(struct qpnp_qg *chip) { struct cycle_counter *counter; struct cap_learning *cl; struct ttf *ttf; struct device_node *node = chip->dev->of_node; int rc; Loading @@ -2714,6 +2820,28 @@ static int qg_alg_init(struct qpnp_qg *chip) chip->counter = counter; ttf = devm_kzalloc(chip->dev, sizeof(*ttf), GFP_KERNEL); if (!ttf) return -ENOMEM; ttf->get_ttf_param = qg_get_ttf_param; ttf->awake_voter = qg_ttf_awake_voter; ttf->iterm_delta = QG_TTF_ITERM_DELTA_MA; ttf->data = chip; rc = ttf_tte_init(ttf); if (rc < 0) { dev_err(chip->dev, "Error in initializing ttf, rc:%d\n", rc); ttf->data = NULL; counter->data = NULL; devm_kfree(chip->dev, ttf); devm_kfree(chip->dev, counter); return rc; } chip->ttf = ttf; chip->dt.cl_disable = of_property_read_bool(node, "qcom,cl-disable"); Loading @@ -2738,6 +2866,7 @@ static int qg_alg_init(struct qpnp_qg *chip) counter->data = NULL; cl->data = NULL; devm_kfree(chip->dev, counter); devm_kfree(chip->dev, ttf); devm_kfree(chip->dev, cl); return rc; } Loading Loading @@ -3064,6 +3193,7 @@ static int process_suspend(struct qpnp_qg *chip) if (!chip->profile_loaded) return 0; cancel_delayed_work_sync(&chip->ttf->ttf_work); /* disable GOOD_OCV IRQ in sleep */ vote(chip->good_ocv_irq_disable_votable, QG_INIT_STATE_IRQ_DISABLE, true, 0); Loading Loading @@ -3196,6 +3326,8 @@ static int process_resume(struct qpnp_qg *chip) chip->suspend_data = false; } schedule_delayed_work(&chip->ttf->ttf_work, 0); return rc; } Loading Loading @@ -3273,6 +3405,8 @@ static int qpnp_qg_probe(struct platform_device *pdev) chip->maint_soc = -EINVAL; chip->batt_soc = INT_MIN; chip->cc_soc = INT_MIN; chip->full_soc = QG_SOC_FULL; chip->chg_iterm_ma = INT_MIN; rc = qg_alg_init(chip); if (rc < 0) { Loading Loading @@ -3338,6 +3472,7 @@ static int qpnp_qg_probe(struct platform_device *pdev) pr_err("Error in restoring cycle_count, rc=%d\n", rc); return rc; } schedule_delayed_work(&chip->ttf->ttf_work, 10000); } rc = qg_determine_pon_soc(chip); Loading