blob: 84accb8a6ffaf330a0dc6573676ef458eebe74e5 [file] [log] [blame]
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include <linux/thermal.h>
#include <linux/power/battery_id.h>
#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 */
/* Offset to max allowed voltage before stopping charging. (in mV) */
#define VBATT_MAX_OFFSET 50
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 + VBATT_MAX_OFFSET)) {
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);