Chip:CW2015
SoC:RK3288
Platform:Android 5.1
PSY
一般power supply分为三种:DC,USB,battery
本文着重分析battery,并介绍CW2015电量计调试的相关经验
probe()函数
分析驱动毫无疑问从probe()函数开始,函数原型:
static int cw_bat_probe(struct i2c_client *client,
const struct i2c_device_id *id)
进入probe()后,首先需要获取电量计相关gpio,调用函数cw2015_parse_dt(),目的:
- 获取电池信息
- DC检测脚
- 低电量中断脚
- 是否支持DC充电
- 是否支持USB充电
然后调用cw_bat_gpio_init()初始化各gpio的值。随后,进入电量计的初始化函数cw_init()。
cw_init()
cw2015的初始化流程可参考datasheet,其操作流程如下:
1. WAKE UP使其退出上电默认的sleep状态
2. 设置容量门限值ATHD
3. 检查UPDATE_FLAG标志位
- 如果UPDATE_FLAG有置位,检查电池信息是否一致
- 如果没有置位,写入电池信息,并置位UPDATE_FLAG
4. 判断SOC是否合法,不合法cw2015进入sleep状态
流程图如下
CW2015初始化成功后,填充struct power_supply各字段,注册三种power_supply设备,这里只关注battery部分:
cw_bat->rk_bat.name = "rk-bat";
cw_bat->rk_bat.type = POWER_SUPPLY_TYPE_BATTERY;
cw_bat->rk_bat.properties = rk_battery_properties;
cw_bat->rk_bat.num_properties = ARRAY_SIZE(rk_battery_properties);
cw_bat->rk_bat.get_property = rk_battery_get_property;
ret = power_supply_register(&client->dev, &cw_bat->rk_bat);
if (ret < 0) {
dev_err(&cw_bat->client->dev,
"power supply register rk_bat error\n");
goto rk_bat_register_fail;
}
get_property:get_property方法提供了sys用户接口获取电池信息,调用rk_battery_get_property。该方法通过power_supply_register注册进power supply core,用户层读取时再回调。
get_property的sys接口可参考power supply sys
power_supply_register:初始化电池uevent change工作队列,注册power supply设备。
INIT_WORK(&psy->changed_work, power_supply_changed_work);
注册完power supply设备,各个功能就可以正常工作了,然后通过工作队列更新电池信息。
电池相关处理
电池信息相关处理主要由几个工作队列和中断来完成:
- cw_bat_work-更新电池信息
- dc_detect_do_wakeup-DC状态中断函数
- bat_low_detect_do_wakeup-低电压处理
本文只分析cw_bat_work()电池信息更新部分。
cw_bat_work
首先会判断是否支持DC充电和USB充电(dts配置),如下:
if (cw_bat->plat_data.is_dc_charge == 1) { //支持DC充电
ret = rk_ac_update_online(cw_bat);
if (ret == 1)
power_supply_changed(&cw_bat->rk_ac);
}
if (cw_bat->plat_data.is_usb_charge == 1) { //支持usb充电
ret = rk_usb_update_online(cw_bat);
if (ret == 1) {
power_supply_changed(&cw_bat->rk_usb);
power_supply_changed(&cw_bat->rk_ac);
}
}
支持DC充电时(usb充电暂不分析),调用rk_ac_update_online(),进行状态的切换,如下:
/*判断dc_det_pin是否有效*/
if (!gpio_is_valid(cw_bat->plat_data.dc_det_pin)) {
cw_bat->dc_online = 0;
pr_info("%s dc charger without dc_det_pin\n", __func__);
return 0;
}
/*
判断dc是否插入,接入DC时该脚拉低,只接电池或OTG时该脚为高电平
dc_online只判断一次,也就是DC插拔时电池status只切换一次
*/
if (gpio_get_value(cw_bat->plat_data.dc_det_pin) == //为0表示接入了DC,DC充电模式
cw_bat->plat_data.dc_det_level) { //dc_det_level为GPIO_ACTIVE_LOW
if (cw_bat->dc_online != 1) {
cw_update_time_member_charge_start(cw_bat);
cw_bat->dc_online = 1;
if (cw_bat->charger_mode != AC_CHARGER_MODE)
cw_bat->charger_mode = AC_CHARGER_MODE;
ret = 1;
}
} else { //为1表示未接入DC,bat放电模式
if (cw_bat->dc_online != 0) {
cw_update_time_member_charge_start(cw_bat);
cw_bat->dc_online = 0;
if (cw_bat->usb_online == 0)
cw_bat->charger_mode = 0;
ret = 1;
}
}
然后会调用rk_bat_update_status()更新电池状态:充电,充满,放电
if (cw_bat->charger_mode > 0) {
if (cw_bat->capacity >= 100)
status = POWER_SUPPLY_STATUS_FULL; //充满
else
status = POWER_SUPPLY_STATUS_CHARGING; //充电
} else {
status = POWER_SUPPLY_STATUS_NOT_CHARGING; //放电
}
if (cw_bat->status != status) {
cw_bat->status = status;
cw_bat->bat_change = 1;
}
如果电池状态发生改变时,调用power_supply_changed(),该函数中调度changed_work工作队列,实际任务函数为power_supply_changed_work(),在注册power supply时初始化。
调用rk_bat_update_capacity()更新电池容量SOC,电池容量的获取主要是读取CW2015的0x4,0x5寄存器。实现逻辑为函数cw_get_capacity()。
调用rk_bat_update_vol()更新电池电压VCELL,实现逻辑为函数cw_get_vol()。
调用rk_bat_update_time_to_empty()更新系统可运行时间RRT,实现逻辑为函数cw_get_time_to_empty()。
最后判断电池状态是否改变,若电池状态改变则调度changed_work工作队列。
power_supply_changed_work
每当电池状态或电池容量SOC,电压VCELL等发生改变时,最终都会调用power_supply_changed_work()通知uevent事件给上层。Android HAL由healthd负责监听。函数如下:
static void power_supply_changed_work(struct work_struct *work)
{
unsigned long flags;
struct power_supply *psy = container_of(work, struct power_supply,
changed_work);
dev_dbg(psy->dev, "%s\n", __func__);
spin_lock_irqsave(&psy->changed_lock, flags);
if (psy->changed) {
psy->changed = false;
spin_unlock_irqrestore(&psy->changed_lock, flags);
class_for_each_device(power_supply_class, NULL, psy,
__power_supply_changed_work);
power_supply_update_leds(psy);
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
spin_lock_irqsave(&psy->changed_lock, flags);
}
if (!psy->changed)
pm_relax(psy->dev);
spin_unlock_irqrestore(&psy->changed_lock, flags);
}
至此,CW2015驱动主要部分已分析完。但是在调试时需要注意:CW2015电量计需要配置电池信息,电池信息bat_config_info需由原厂配合电量计调校得出正确的电池信息,并在初始化时将电池信息写入CW2015,否则重启后获取到的电池容量SOC不准确。
SYS节点
CW201X驱动提供了以下几个字段来获取battery状态,实际上HAL uevent也是获取这几个接口的值,接口如下:
ls /sys/class/power_supply/rk-bat/
capacity
device
health
power
present
status
subsystem
technology
time_to_empty_now
type
uevent
voltage_now
Capacity:电池容量百分比,该值会上报Android,设置中打开时,状态栏即显示该值。
Health:电池健康情况,平台默认为POWER_SUPPLY_HEALTH_GOOD,即返回Good。
Status:电池充电状态。
Technology:电池采用的技术,平台默认为POWER_SUPPLY_TECHNOLOGY_LION,即返回Li-ion。
Time_to_empty_now:电池电压。
Type:电池充电类型。
以上值除了平台默认的,其余的均从struct cw_battery中获取,电池信息的工作队列会一直更新struct cw_battery中的相关值。
问题
- 系统重启后电量计显示不准确
写入CW2015中的电池信息bat_config_info不正确 - 拔掉电池与DC,再重新接上电池上电,电量计获取的值不准确。
拔掉电池后PMIC完全掉电,此时再重新上电只能根据bat_config_info反推电量,会进行一次重新校准,和关机前有一定误差。如果希望电池电量和重新上电之前一致,则只能通过软件处理,比如在关机时将电池电量保存至文件中,开机时再去读。
转载自原文链接, 如需删除请联系管理员。
原文链接:CW2015电量计驱动分析,转载请注明来源!