sprd_27xx_fgu.c
就是展讯SL8541E 库仑计驱动,用来统计电量的;
还是一样,从static int sprdfgu_2723_probe(struct platform_device *pdev)
分析开始:
static int sprdfgu_2723_probe(struct platform_device *pdev)
{
int ret = 0, irq = 0;
u32 value = 0;
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id;
struct power_supply *ret_ptr = NULL;
struct power_supply_desc *sprdfgu_desc = NULL;
reg_map = dev_get_regmap(pdev->dev.parent, NULL);
if (!reg_map)
dev_err(&pdev->dev, "%s :NULL regmap for fgu 2723\n",
__func__);
sprdfgu_data.fgu_pdata = devm_kzalloc(&pdev->dev,
sizeof(struct sprdfgu_platform_data),
GFP_KERNEL);
of_id = of_match_node(sprdfgu_2723_of_match,
pdev->dev.of_node);
if (!of_id) {
pr_err("get 27xx fgu of device id failed!\n");
return -ENODEV;
}
sprdfgu_data.fgu_pdata->fgu_type = (enum fgu_type)of_id->data;
SPRD_FGU_DEBUG("fgu_type =%d\n",
sprdfgu_data.fgu_pdata->fgu_type);
//ocv-type 为0 时,会使用ocv表来统计开机的初始化电量
ret = of_property_read_u32(np, "ocv-type",
&sprdfgu_data.fgu_pdata->ocv_type);
if (ret)
dev_err(&pdev->dev, "%s :no ocv-type\n",
__func__);
irq = platform_get_irq(pdev, 0);
if (unlikely(irq <= 0))
dev_err(&pdev->dev, "no irq resource specified\n");
sprdfgu_data.fgu_pdata->fgu_irq = irq;
//reg是指库仑计使用的时候的偏移地址
ret = of_property_read_u32(np, "reg", &value);
if (ret)
dev_err(&pdev->dev, "%s :no reg of property for pmic fgu\n",
__func__);
sprdfgu_data.fgu_pdata->base_addr = (unsigned long)value;
sprdfgu_desc = devm_kzalloc(&pdev->dev,
sizeof(struct power_supply_desc), GFP_KERNEL);
if (sprdfgu_desc == NULL)
return -ENOMEM;
//注册power_supply节点
sprdfgu_desc->name = "sprdfgu";
sprdfgu_desc->get_property = sprdfgu_power_get_property;
ret_ptr = power_supply_register(&pdev->dev, sprdfgu_desc, NULL);
if (IS_ERR(ret_ptr))
pr_err("register power supply error!\n");
else
sprdfgu_data.sprdfgu = ret_ptr;
sprdfgu_data.dev = &pdev->dev;
platform_set_drvdata(pdev, &sprdfgu_data);
ret = sysfs_create_group(&sprdfgu_data.sprdfgu->dev.kobj,
&sprd_fgu_group);
if (ret)
pr_err("failed to create sprd_fgu sysfs device attributes\n");
INIT_DELAYED_WORK(&sprdfgu_debug_work, sprdfgu_debug_works);
mutex_init(&sprdfgu_data.lock);
mutex_init(&sprdfgu_data.track_lock);
wake_lock_init(&(sprdfgu_data.low_power_lock), WAKE_LOCK_SUSPEND,
"sprdfgu_powerlow_lock");
sprdfgu_data.is_track = 1;
INIT_DELAYED_WORK(&sprdfgu_data.fgu_qmax_work,
sprdfgu_qmax_works);
sprdfgu_data.fgu_wqueue =
create_workqueue("sprdfgu_monitor");
if (!sprdfgu_data.fgu_wqueue)
sprdfgu_data.is_track = 0;
sprdfgu_data.track.vol_buff = devm_kzalloc(&pdev->dev,
sizeof(int) * CUR_VOL_BUFF_LEN,
GFP_KERNEL);
if (!sprdfgu_data.track.vol_buff)
sprdfgu_data.is_track = 0;
sprdfgu_data.track.cur_buff = devm_kzalloc(&pdev->dev,
sizeof(int) * CUR_VOL_BUFF_LEN,
GFP_KERNEL);
if (!sprdfgu_data.track.cur_buff)
sprdfgu_data.is_track = 0;
SPRD_FGU_DEBUG("sprdfgu_2723_probe ok\n");
return ret;
}
其中这里有三个工作队列:
- sprdfgu_debug_works工作队列
- sprdfgu_qmax_works工作队列,电量自学习工作队列
- 在
sprdfgu_int_init
函数中的sprdfgu_irq_works函数
1、 函数为空函数,则不予以处理和分析
2、电容自适应函数:
主要在sprdfgu_read_soc
函数中读取:
并且在sprdfgu_qmax_update_monitor
函数中当
当电池工作电流足够小的时候电池工作电压近似等于电池开路电压,如果有准确的电量表我们就能准确的定位电池容量我们记录为 C1,将这时库仑计计数值容量记录为Q1。充电满后电池真实容量接近 100%,这里我们将库仑计计数值记录为 Q2。根据以上几个数据我们就可以得到电池真实有效容量:
Qmax = (Q2 – Q1) * 100/(100 –C1)
我们一般采用低电量开始作为初始采集点,并严格规定了环境温度即电流、电压条件,只有符合规定的容量自学习我们才认为是有效的自学习。 自学习功能被抽象为 qmax,结构如下, 各种限制条件需要在初始化的时候写入。
struct sprdfgu_qmax {
int state; //自学习状态
int cur_cnom; //当前容量
int s_clbcnt; //开始自学习的库仑计计数
int s_soc; //自学习初始点百分比
int s_vol; //自学习初始电压限制
int s_cur; //自学习初始电流限制
int e_vol; //自学习结束电压条件
int e_cur; //自学习结束电流条件
int force_s_flag; //应用层控制启动标志位
int force_s_soc; //应用层直接写入容量值
uint32_t timeout; //设置的超时门限
__s64 s_time; //开始自学习的时间点
};
2、 电量保存与回读
自学习状态 State 有 5 种, DONE 状态是学习完成状态, 在 DONE 状态会将学习完成的容量值写入/productinfo/.battery_file 这个文件。 productinfo 分区掉电不擦除,重启后再 INIT状态会回读这个数据作为容量基数进行电量积分,并将自学习状态切换到 IDLE 状态。 如果满足了自学习条件就进入了 QMAX_UPDATING, UPDATING 状态下会检查自学习后容量, 如果容量检查失效则恢复 IDLE状态,需要重新开始学习。 如果手机单体没有完成过自学习则使用默认的容量配置。
enum QMAX_STATE {
QMAX_INIT, //开机默认状态
QMAX_IDLE, //自学习准备状态
QMAX_UPDATING, //电量需要更新
QMAX_DONE, //自学习完成
QMAX_ERR, //异常
};
sys/class/power_supply/sprdfgu/ qmax_force_start
应用层主动开启自学习功能
sys/class/power_supply/sprdfgu/ qmax_force_s_soc
应用层设置开启自学习时的容量
sys/class/power_supply/sprdfgu/ qmax_state
读取当前自学习状态
sys/class/power_supply/sprdfgu/ qmax_s_vol
设置开启自学习的电压条件
sys/class/power_supply/sprdfgu/ qmax_s_cur10
设置开启自学习的电流条件
sys/class/power_supply/sprdfgu/ qmax_e_vol
设置自学习停止电压条件
sys/class/power_supply/sprdfgu/ qmax_e_cur
设置自学习停止电流条件
sys/class/power_supply/sprdfgu/ cnom
读取当前积分容量基数
sys/class/power_supply/sprdfgu/ saved_cnom
应用层直接写数保存容量
3. 本质上就是用sprdfgu_irq_works计算电量的
static void sprdfgu_irq_works(struct work_struct *work)
{
uint32_t cur_ocv;
uint32_t adc;
int cur_soc;
wake_lock_timeout(&(sprdfgu_data.low_power_lock), 2 * HZ);
SPRD_FGU_DEBUG("%s......0x%x.cur vol = %d\n",
__func__, sprdfgu_data.int_status,
sprdfgu_read_vbat_vol());
if (sprdfgu_data.int_status & BIT_VOLT_HIGH_INT)
power_supply_changed(sprdfgu_data.sprdfgu);
if (sprdfgu_data.int_status & BIT_VOLT_LOW_INT) {
cur_soc = sprdfgu_read_soc();
mutex_lock(&sprdfgu_data.lock);
cur_ocv = sprdfgu_read_vbat_ocv();
if (cur_ocv <= sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("shutdown_vol .\n");
sprdfgu_soc_adjust(0);
} else if (cur_ocv <= sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("alm_vol %d.\n", cur_soc);
if (cur_soc > sprdfgu_data.warning_cap) {
sprdfgu_soc_adjust(sprdfgu_data.warning_cap);
} else if (cur_soc <= 0) {
sprdfgu_soc_adjust(sprdfgu_vol2capacity
(cur_ocv));
}
if (!sprdfgu_data.adp_status) {
adc = sprdfgu_vol2adc_mv
(sprdfgu_data.shutdown_vol);
fgu_adi_write(REG_FGU_LOW_OVER, adc & 0xFFFF,
~0);
}
} else {
SPRD_FGU_DEBUG("need add\n");
}
mutex_unlock(&sprdfgu_data.lock);
}
}
首先第一个函数sprdfgu_read_vbat_vol
:
读取vbat上的电压:
uint32_t sprdfgu_read_vbat_vol(void)
{
u32 cur_vol_raw;
uint32_t temp;
cur_vol_raw = sprdfgu_reg_get(REG_FGU_VOLT_VAL);
temp = sprdfgu_adc2vol_mv(cur_vol_raw);
return temp;
}
第二个函数sprdfgu_read_soc
函数,也就是电量计的核心:
int sprdfgu_read_soc(void)
{
int cur_cc, cc_delta, capcity_delta, temp;
uint32_t cur_ocv;
int cur;
union power_supply_propval val;
sprdfgu_track();
mutex_lock(&sprdfgu_data.lock);
sprdfgu_qmax_update_monitor();
cur_cc = sprdfgu_clbcnt_get();
cc_delta = cur_cc - sprdfgu_data.init_clbcnt;
/* 0.1mah */
temp = DIV_ROUND_CLOSEST(cc_delta,
(360 * FGU_CUR_SAMPLE_HZ));
temp = sprdfgu_adc2cur_ma(temp);
capcity_delta = DIV_ROUND_CLOSEST(temp * 100,
sprdfgu_data.cur_cnom);
SPRD_FGU_DEBUG("d cap %d,cnom %d,g %dmAh(0.1mA),init_cc:%d\n",
capcity_delta, sprdfgu_data.cur_cnom,
temp, sprdfgu_data.init_clbcnt);
capcity_delta += sprdfgu_data.init_cap;
SPRD_FGU_DEBUG("soc %d,init_cap %d,cap:%d\n",
(capcity_delta + 9) / 10, sprdfgu_data.init_cap,
capcity_delta);
cur = sprdfgu_read_batcurrent();
cur_ocv = sprdfgu_read_vbat_ocv();
if ((cur_ocv >= sprdfgu_data.bat_full_vol)
&& (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
if (capcity_delta < FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
}
if (capcity_delta > FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
sprdpsy_get_property("battery",
POWER_SUPPLY_PROP_STATUS,
&val);
if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
if (capcity_delta <= sprdfgu_data.warning_cap
&& cur_ocv > sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc low...\n");
capcity_delta = sprdfgu_data.warning_cap + 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta <= 0
&& cur_ocv > sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("soc 0...\n");
capcity_delta = 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("vol 0...\n");
capcity_delta = 0;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta > sprdfgu_data.warning_cap
&& cur_ocv < sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc high...\n");
sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
capcity_delta = sprdfgu_vol2capacity(cur_ocv);
}
}
if (capcity_delta < 0)
capcity_delta = 0;
sprdfgu_record_rawsoc(capcity_delta);
#ifdef SPRDFGU_TEMP_COMP_SOC
{
temp = sprdbat_read_temp();
capcity_delta =
sprdfgu_temp_comp_soc(capcity_delta, temp / 10);
}
#endif
if (sprdfgu_read_vbat_vol() < sprdfgu_data.pdata->soft_vbat_uvlo) {
SPRD_FGU_DEBUG("trigger soft uvlo vol 0...\n");
capcity_delta = 0;
sprdfgu_soc_adjust(capcity_delta);
}
mutex_unlock(&sprdfgu_data.lock);
return capcity_delta;
}
- 首先通过
sprdfgu_track
函数来定位电池开路电压;
这里首先介绍电池内阻自适应:
电池开路电压 OCV 是表征电池容量的最关键因素,在电量显示、电池容量自学习的时候都可能用到电池开路电压来定位百分比。因此我们必须找到一个方法定位电池开路电压。
FGU 可以获取到电池工作状态下的电流 I 和电压 VBAT,根据欧姆定律只要我们得到电池内阻 R 就可以计算出电池开路电压了: OCV = VBAT – I *R。 电池内阻一般都是动态变化的,不同容量不同温度都会有不同的电池内阻,所以需要一个动态调整内阻的算法来保证内阻的准确性, 平台提供了一套电池内阻自适应的算法:提取硬件 buff 中的一组电流电压值(共 8 个)并将这 8 个数据按大小排序,找到最大的压降 DeltaV 和电流变化DeltaI,根据欧姆定律 R =DeltaV/DeltaI;使用计算出来的 R 就能得到实时 OCV 了。
电池内阻自学习通过 sprdfgu_tracking 来记录学习状态,成员包括自学习时的温度、平均电流、平均电压、电流 buff、电压 buff、最小电流、 最小电压等等。 Chargework 会调用 static void sprdfgu_track(void)来更新 sprdfgu_data.track,使用的时候通过接口 static struct sprdfgu_tracking sprdfgu_get_track_data(void);获取到实时的电池工作状态信息sprdfgu_data.track。
struct sprdfgu_tracking {
__s64 r_time;
__s64 query_time; //结构更新时间间隔
int rint; //电池内阻自适应出来的内阻
int r_temp; //获取电池内阻时候判断采样温度
int c_temp; //容量自适应的时候判断采样的温度
int r_vol; //
int relax_vol; //最接近电池开路电压
int relax_cur; //最接近零点的电流
int ave_vol; //平均电压
int ave_cur; //平均电流
int *cur_buff; //电流 buff
int *vol_buff; //电压 buff
};
sprdfgu_track则是算出上述数据的函数;
-
sprdfgu_qmax_update_monitor
则是电池自学习的过程,所以不再叙述; -
sprdfgu_clbcnt_get
获取记录电流积分数据; -
获取当前 Coulomb Counter 计数量下次轮询调用的时候可以计算出两 次 Coulomb Counter 统计的差值从而得到库仑量变化 cc_delta 。DIV_ROUND_CLOSEST(cc_delta, (3600 * 2));
-
DIV_ROUND_CLOSEST(mah * 100, sprdfgu_data.pdata->cnom);通过变化的 mah 计算出与总容量 cnom 的比值,从而得到变化百分比。
-
capcity_delta += sprdfgu_data.init_cap;
计算出这次电量加上初始化的电量; -
sprdfgu_read_batcurrent
读取现在电池vbat的容量; -
sprdfgu_read_vbat_ocv
读取开路电压的ocv; -
对一些ocv的条件判定,具体看代码:
//开路电压ocv大于电池满电的状态并且充电电流小于充电截止电流
if ((cur_ocv >= sprdfgu_data.bat_full_vol)
&& (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
if (capcity_delta < FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
}
//大于100,电量则设置100
if (capcity_delta > FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
sprdpsy_get_property("battery",
POWER_SUPPLY_PROP_STATUS,
&val);
//不在充电状态的话,
if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
if (capcity_delta <= sprdfgu_data.warning_cap
&& cur_ocv > sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc low...\n");
capcity_delta = sprdfgu_data.warning_cap + 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta <= 0
&& cur_ocv > sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("soc 0...\n");
capcity_delta = 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("vol 0...\n");
capcity_delta = 0;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta > sprdfgu_data.warning_cap
&& cur_ocv < sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc high...\n");
sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
capcity_delta = sprdfgu_vol2capacity(cur_ocv);
}
}
if (capcity_delta < 0)
capcity_delta = 0;
sprdfgu_record_rawsoc
记录soc上raw;
另外:
sprdfgu_load_uicap
读取ui的电量,本身由rtc记录。sprdfgu_load_rawsoc
则是记录实际电量;sprdfgu_soc_adjust
调整电量。输入为电量值