#include #include #include #include #include #include #include #include #include #include "power_supply.h" #include "power_supply_charger.h" /* 98% of CV is considered as voltage to detect Full */ #define FULL_CV_MIN 98 /* Offset to exit from maintenance charging. In maintenance charging * if the volatge is less than the (maintenance_lower_threshold - * MAINT_EXIT_OFFSET) then system can switch to normal charging */ #define MAINT_EXIT_OFFSET 50 /* mV */ static int get_tempzone(struct ps_pse_mod_prof *pse_mod_bprof, int temp) { int i = 0; int temp_range_cnt = min_t(u16, pse_mod_bprof->temp_mon_ranges, BATT_TEMP_NR_RNG); if ((temp < pse_mod_bprof->temp_low_lim) || (temp > pse_mod_bprof->temp_mon_range[0].temp_up_lim)) return -EINVAL; for (i = 0; i < temp_range_cnt; ++i) if (temp > pse_mod_bprof->temp_mon_range[i].temp_up_lim) break; return i-1; } static inline bool __is_battery_full (long volt, long cur, long iterm, unsigned long cv) { pr_devel("%s:current=%d pse_mod_bprof->chrg_term_ma =%d voltage_now=%d full_cond=%d", __func__, cur, iterm, volt * 100, (FULL_CV_MIN * cv)); return ((cur > 0) && (cur <= iterm) && ((volt * 100) >= (FULL_CV_MIN * cv))); } static inline bool is_battery_full(struct batt_props bat_prop, struct ps_pse_mod_prof *pse_mod_bprof, unsigned long cv) { int i; /* Software full detection. Check the battery charge current to detect * battery Full. The voltage also verified to avoid false charge * full detection. */ pr_devel("%s:current=%d pse_mod_bprof->chrg_term_ma =%d bat_prop.voltage_now=%d full_cond=%d", __func__, bat_prop.current_now, (pse_mod_bprof->chrg_term_ma), bat_prop.voltage_now * 100, (FULL_CV_MIN * cv)); for (i = (MAX_CUR_VOLT_SAMPLES - 1); i >= 0; --i) { if (!(__is_battery_full(bat_prop.voltage_now_cache[i], bat_prop.current_now_cache[i], pse_mod_bprof->chrg_term_ma, cv))) return false; } return true; } static int pse_get_bat_thresholds(struct ps_batt_chg_prof bprof, struct psy_batt_thresholds *bat_thresh) { struct ps_pse_mod_prof *pse_mod_bprof = (struct ps_pse_mod_prof *) bprof.batt_prof; if ((bprof.chrg_prof_type != PSE_MOD_CHRG_PROF) || (!pse_mod_bprof)) return -EINVAL; bat_thresh->iterm = pse_mod_bprof->chrg_term_ma; bat_thresh->temp_min = pse_mod_bprof->temp_low_lim; bat_thresh->temp_max = pse_mod_bprof->temp_mon_range[0].temp_up_lim; return 0; } static enum psy_algo_stat pse_get_next_cc_cv(struct batt_props bat_prop, struct ps_batt_chg_prof bprof, unsigned long *cc, unsigned long *cv) { int tzone; struct ps_pse_mod_prof *pse_mod_bprof = (struct ps_pse_mod_prof *) bprof.batt_prof; enum psy_algo_stat algo_stat = bat_prop.algo_stat; int maint_exit_volt; *cc = *cv = 0; /* If STATUS is discharging, assume that charger is not connected. * If charger is not connected, no need to take any action. * If charge profile type is not PSE_MOD_CHRG_PROF or the charge profile * is not present, no need to take any action. */ pr_devel("%s:battery status = %d algo_status=%d\n", __func__, bat_prop.status, algo_stat); if ((bprof.chrg_prof_type != PSE_MOD_CHRG_PROF) || (!pse_mod_bprof)) return PSY_ALGO_STAT_NOT_CHARGE; tzone = get_tempzone(pse_mod_bprof, bat_prop.temperature); if (tzone < 0) return PSY_ALGO_STAT_NOT_CHARGE; /* Change the algo status to not charging, if battery is * not really charging or less than maintenance exit threshold. * This way algorithm can switch to normal * charging if current status is full/maintenace */ maint_exit_volt = pse_mod_bprof-> temp_mon_range[tzone].maint_chrg_vol_ll - MAINT_EXIT_OFFSET; if ((bat_prop.status == POWER_SUPPLY_STATUS_DISCHARGING) || (bat_prop.status == POWER_SUPPLY_STATUS_NOT_CHARGING) || bat_prop.voltage_now < maint_exit_volt) { algo_stat = PSY_ALGO_STAT_NOT_CHARGE; } /* read cc and cv based on temperature and algorithm status*/ if (algo_stat == PSY_ALGO_STAT_FULL || algo_stat == PSY_ALGO_STAT_MAINT) { /* if status is full and voltage is lower than maintenance lower * threshold change status to maintenenance */ if (algo_stat == PSY_ALGO_STAT_FULL) { *cv = pse_mod_bprof->temp_mon_range [tzone].full_chrg_vol; *cc = pse_mod_bprof->temp_mon_range [tzone].full_chrg_cur; } if (algo_stat == PSY_ALGO_STAT_FULL && (bat_prop.voltage_now <= pse_mod_bprof->temp_mon_range[tzone].maint_chrg_vol_ll)) algo_stat = PSY_ALGO_STAT_MAINT; /* Read maintenance CC and CV */ if (algo_stat == PSY_ALGO_STAT_MAINT) { *cv = pse_mod_bprof->temp_mon_range [tzone].maint_chrg_vol_ul; *cc = pse_mod_bprof->temp_mon_range [tzone].maint_chrg_cur; } } else { *cv = pse_mod_bprof->temp_mon_range[tzone].full_chrg_vol; *cc = pse_mod_bprof->temp_mon_range[tzone].full_chrg_cur; algo_stat = PSY_ALGO_STAT_CHARGE; } if (bat_prop.voltage_now > *cv) { algo_stat = PSY_ALGO_STAT_NOT_CHARGE; return algo_stat; } if (algo_stat == PSY_ALGO_STAT_FULL) return algo_stat; if (is_battery_full(bat_prop, pse_mod_bprof, *cv)) algo_stat = PSY_ALGO_STAT_FULL; return algo_stat; } static int __init pse_algo_init(void) { struct charging_algo pse_algo; pse_algo.chrg_prof_type = PSE_MOD_CHRG_PROF; pse_algo.name = "pse_algo"; pse_algo.get_next_cc_cv = pse_get_next_cc_cv; pse_algo.get_batt_thresholds = pse_get_bat_thresholds; power_supply_register_charging_algo(&pse_algo); return 0; } module_init(pse_algo_init);