展讯库仑计驱动

编程

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

",

__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!

");

return -ENODEV;

}

sprdfgu_data.fgu_pdata->fgu_type = (enum fgu_type)of_id->data;

SPRD_FGU_DEBUG("fgu_type =%d

",

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

",

__func__);

irq = platform_get_irq(pdev, 0);

if (unlikely(irq <= 0))

dev_err(&pdev->dev, "no irq resource specified

");

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

",

__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!

");

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

");

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

");

return ret;

}

其中这里有三个工作队列:

  1. sprdfgu_debug_works工作队列
  2. sprdfgu_qmax_works工作队列,电量自学习工作队列
  3. 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

",

__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 .

");

sprdfgu_soc_adjust(0);

} else if (cur_ocv <= sprdfgu_data.pdata->alm_vol) {

SPRD_FGU_DEBUG("alm_vol %d.

", 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

");

}

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

",

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

",

(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

", 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...

");

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...

");

capcity_delta = 10;

sprdfgu_soc_adjust(capcity_delta);

} else if (cur_ocv < sprdfgu_data.shutdown_vol) {

SPRD_FGU_DEBUG("vol 0...

");

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...

");

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...

");

capcity_delta = 0;

sprdfgu_soc_adjust(capcity_delta);

}

mutex_unlock(&sprdfgu_data.lock);

return capcity_delta;

}

  1. 首先通过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则是算出上述数据的函数;

  1. sprdfgu_qmax_update_monitor则是电池自学习的过程,所以不再叙述;

  2. sprdfgu_clbcnt_get获取记录电流积分数据;

  3. 获取当前 Coulomb Counter 计数量下次轮询调用的时候可以计算出两 次 Coulomb Counter 统计的差值从而得到库仑量变化 cc_delta 。DIV_ROUND_CLOSEST(cc_delta, (3600 * 2));

  4. DIV_ROUND_CLOSEST(mah * 100, sprdfgu_data.pdata->cnom);通过变化的 mah 计算出与总容量 cnom 的比值,从而得到变化百分比。

  5. capcity_delta += sprdfgu_data.init_cap;计算出这次电量加上初始化的电量;

  6. sprdfgu_read_batcurrent读取现在电池vbat的容量;

  7. sprdfgu_read_vbat_ocv读取开路电压的ocv;

  8. 对一些ocv的条件判定,具体看代码:

    //开路电压ocv大于电池满电的状态并且充电电流小于充电截止电流

if ((cur_ocv >= sprdfgu_data.bat_full_vol)

&& (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {

SPRD_FGU_DEBUG("cur_ocv %d

", 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...

");

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...

");

capcity_delta = 10;

sprdfgu_soc_adjust(capcity_delta);

} else if (cur_ocv < sprdfgu_data.shutdown_vol) {

SPRD_FGU_DEBUG("vol 0...

");

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...

");

sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));

capcity_delta = sprdfgu_vol2capacity(cur_ocv);

}

}

if (capcity_delta < 0)

capcity_delta = 0;

  1. sprdfgu_record_rawsoc记录soc上raw;

另外:

  • sprdfgu_load_uicap读取ui的电量,本身由rtc记录。
  • sprdfgu_load_rawsoc则是记录实际电量;
  • sprdfgu_soc_adjust调整电量。输入为电量值

以上是 展讯库仑计驱动 的全部内容, 来源链接: utcz.com/z/515175.html

回到顶部