3.3.3. power_supply_class_init
5.5.2. Power supply子系统更新battery信息机制
6.2.3. pm860x_vbattery_handler
6.4.1. pm860x_set_charger_type
1. 引言
1.1. 编写目的
对TF303充电部分做一个整理,内容涉及到电池的基础知识、8607中断、power supply子系统、 电池和充电电器驱动、充电流程和电量计算等。
1.2. 环境
硬件环境:TF303
软件环境:svn://172.16.0.70/svn_android/PXA920/branch/Kernel/W1225.03_8390_Kernel 版本8538
2. 充电基础知识
2.1. 名词解释
恒流充电:保持恒定电流对电池进行充电。
恒压充电:保持恒定电压对电池进行充电。
热敏电阻:对热敏感的半导体电阻,其阻值随温度变化的曲线呈非线性或线性。
记忆效应:电池的记忆效应是指在下一次充电时所能充电的百分比。为了消除电池的记忆效应,在下一次充电之前,必须先完全放电,然后再充电。只有这样,才能百分之百的充满电池。
涓流充电: 以一微小的电流对电池充电, 涓流充电用来先对完全放电的电池单元进行预充(恢复性充电)。。
电池容量:电池容量的国标单位为库仑,常用单位为mAh 。1mAh=0.001安培*3600秒=3.6安培秒=3.6库仑,比如一颗900mAh的电池可以提供300mA恒流的持续3小时的供电能力.
2.2. 电池参数
电池的五个主要参数为:电池的容量、标称电压、内阻、放电终止电压和充电终止电压。
电池的容量:通常用mAh(毫安时)表示,1000mAh就是能在1000mA的电流下放电 1小时。
标称电压:通常指的是开路输出电压,也就是不接任何负载,没有电流输出的电压值。
2.3. 锂电池原理
电池由正极锂化合物、中间的电解质膜及负极碳组成。
当电池充电时,锂离子从正极中脱嵌,在负极中嵌入,放电时反之。一般采用嵌锂过渡金属氧化物做正极,如LiCoO2、LiNiO2、LiMn2O4。
2.4. 电量计算
常见的方法:ADC和库仑计
2.5. ADC法
锂离子电池有一个对电量计量很有用的特性,就是在放电的时候,电池电压随电量的流逝会逐渐降低,并且有相当大的斜率.这就提供给我们另外一种近似的电量计量途径.
用电压来估计电池的剩余容量有以下几个不稳定性:
1.同一个电池,在同等剩余容量的情况下,电压值因放电电流的大小而变化. 放电电流越大,电压越低.在没有电流的情况下,电压最高.
2.环境温度对电池电压的影响, 温度越低,同等容量电池电压越低.
3.循环对电池放电平台的影响, 随着循环的进行,锂离子电池的放电平台趋于恶化.放电平台降低.所以相同电压所代表的容量也相应变化了.
4.不同厂家,不同容量的锂离子电池,其放电的平台略有差异.
5.不同类型的电极材料的锂离子电池,放电平台有较大差异.钴锂和锰锂的放电平台就完全不同.
2.6. 库仑计法
通过统计流入和流出电池的电荷数,使用库仑计时需要通过其他方法获得电池在使用前的电量。
2.7. 锂电池充电
锂电池的充电过程:涓充---恒流---恒压---停止
充电曲线
2.8. TF303充电电路
(1)8067、8606和电池的连接图
(2)8607中断引脚
8607的PMIC_INTN引脚接到AP的PMIC_INT引脚
(3)i2c通信
8606和8607的SCL引脚连接到AP的GPIO_53,SDA连接到GPIO_53
3. Android 电池管理
在kernel中,驱动会在文件系统中生成battery相关的文件,包括电池电量、状态等,驱动更新硬件信息时会调用power_supply提供的接口,power_supply收到这个事件后通过uevent机制利用netlink将这个event上报给app层,app层收event后再去读sysfs中的相关文件获取数据。
3.1. 相关代码
(1)java代码:
frameworks/base/services/java/com/android/server/BatteryService.java
frameworks/base/services/java/com/android/server/ SystemServer.java
frameworks/base/core/java/android/os/UEventObserver.java
(2)JNI代码:
frameworks/base/services/jni/com_android_server_BatteryService.cpp
hardware/libhardware_legacy/uevent/uevent.c
android底层代码
hardware/libhardware_legacy/uevent/uevent.c
(3)krenel代码
与平台无关文件
kernel/drivers/power/power_supply_core.c
kernel/drivers/power/power_supply_sysfs.c
kernel/drivers/usb/gadget/mv_gadget.c
平台相关代码
kernel/drivers/power/88pm860x_battery.c
kernel/drivers/power/88pm860x_charger.c
kernel/drivers/usb/misc/88pm860x_vbus.c
kernel/drivers/mfd/88pm860x-core.c
kernel/drivers/mfd/88pm860x-i2c.c
3.2. power系统文件
/sys/class/power_supply/battery/
/sys/class/power_supply/ac/
/sys/class/power_supply/usb/
3.3. power supply子系统
3.3.1. 数据结构体
struct power_supply结构体,如下图
此结构体用于描述供电模块,系统中总过注册了三个power_supply,分别是:AC、USB host、battery
下面以battery的power_supply初始化对该结构体成员做简单的说明
如上图所示,其中的info->battery为一个struct power_supply结构体
const char *name;用于描述模块的名称
enum power_supply_type type;用于描述类型,类型如下
enum power_supply_property *properties;用于描述该供电模块有哪些属性
battery的属性如下
char **supplied_to; 供电给那个模块
int (*get_property)(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val); 此函数指针将指向获得该模块说属性的接口函数
struct work_struct changed_work;工作队列的初始化是在power_supply注册函数中初始化的(详细见后)。
3.3.2. 系统api
3.3.3. power_supply_class_init
初始化函数原型如下
Power驱动创建了一个叫struct class *power_supply_class的类容器, 并且初始化函数指针power_supply_class->dev_uevent 指向power_supply_uevent函数,供电模块的uevent环境变量的添加都是此函数中是实现的。
3.3.4. power_supply_register
此函数是对外提供的API,作用是注册power_supply供电模块,系统中需要调用此函数来注册Battery、AC、USB三个供电模块。函数的具体实现如下
此函数的主要操作有:
(1)把该设备的class指向power_supply_class
(2)调用device_add(dev);
(3)绑定函数power_supply_changed_work到 psy- >changed_work 工作队列。备注:BATTERY AC USB这三个供电模块的change_work都与power_supply_changed_work 函数绑定。
在调用device_add(dev)(定义在:/kernel/drivers/base/core.c中)时,会执行这一句代码:klist_add_tail(&dev- >knode_class, &dev->class- >p- >class_devices); 因为在上面的函数中dev->class = power_supply_class; ,所以device_add(dev) 会把dev- >knode_class 加入到power_supply_class 的klist链表中。也就是说会把usb、ac、charger三个的dev- >knode_class (备注:是个struct klist_node结构体)注册到这个power_supply_class 链表中,到时候可以通过class_for_each_device 这个函数通过这个类来找到这三个device,然后通过device在找到power_supply。
3.3.5. power_supply_uevent
power_supply_uevent 此函数的作用是通过调用uevent的接口函数add_uevent_var添加环境变量,为Battery、 AC、 USB准备好环境变量。
4. 中断
4.1. 8607中断
中断相关寄存器
8607中断状态寄存器0x03、0x04 、05; 中断使能寄存器 0x06、0x07、0x08
8607的PMIC_INTN引脚接到AP的PMIC_INT引脚
4.2. 8607总中断注册
kernel/arch/arm/mach-mmp/raho_tf302_hwv0.c
其中pm860x_irq为8607总中断的处理函数
4.3. 8607子中断注册
8607子中断的资源定义
举例88pm860x_battery.c
4.4. 中断的执行流程
当AP的PMIC_INTN引脚中断信号发生时,则调用8607的总中断处理函数pm860x_irq, 在此函数中通过i2c来读8607中断状态寄存器,遍历860中断状态寄存器各个bit为,判断出具体哪个中断发生,pm860x_irq函数具体实现如下:
handle_nested_irq函数说明:该函数用于实现一种中断共享机制,当多个中断共享某一根中断线时,我们可以把这个中断线作为父中断,共享该中断的各个设备作为子中断,在父中断的中断线程中决定和分发响应哪个设备的请求,在得出真正发出请求的子设备后,调用handle_nested_irq来响应子中断。
5. battery
5.1. 数据结构
电池d信息的数据结构
5.2. Battery init相关
5.2.1. Probe函数
函数的主要操作有:
电池初始化,将在后面介绍
初始化并注册电池的struct power_supply结构体
注册8607子中断PM8607_IRQ_CC的处理函数为pm860x_coulomb_handler
注册8607子中断PM8607_IRQ_BAT的处理函数为pm860x_batt_handler
初始化monitor_work和changed_work两个工作 ,其中monitor_work主要用于每隔30s刷新电池信息到用户空间,changed_work用于更新的电池的状态。
5.2.2. pm860x_init_battery
5.3. Battery measure api
5.3.1. 测量vbatt
int measure_vbatt(struct pm860x_battery_info *info, int state, int *data);
此函数通过measure_12bit_voltage 函数去读8607的vbatt寄存器PM8607_VBAT_MEAS1 (0x6D)。然后通过校验算法计算出vbatt的值。
5.3.2. 测量ibatt
int measure_current(struct pm860x_battery_info *info, int *data);
通过8607的0x68 0x6c计算出ibatt
5.3.3. 计算开路电压
static int calc_ocv(struct pm860x_battery_info *info, int *ocv);
开路电压值是通过测出的电压、电流和内阻计算而来
5.3.4. 库仑计计数
(1)数据结构
struct ccnt {
unsigned long long int pos;
unsigned long long int neg;
unsigned int spos;
unsigned int sneg;
int total_chg; /*充电统计*/
int total_dischg; /*放电统计*/
};
(2)库仑计计算
static int calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt);
此函数通过8607的0x47和0x95寄存器算出total_chg和total_dischg
5.3.5. 计算电量
接口函数:static int calc_capacity(struct pm860x_battery_info *info, int *cap);
函数的内部实现如下图
5.3.6. ADC计算电量
接口函数:static int calc_soc(struct pm860x_battery_info *info, int state, int *soc);
函数的流程为:通过ADC测出电压,然后再查array_soc 电量与电压的对应关系表,从而得出电量值
5.3.7. 充电完成时更新soc
接口函数:int pm860x_battery_update_soc(void)
函数功能:在充电彻底终止后,将库仑计清零,并将start_soc设为100
5.4. GET电池属性API
接口函数:static int pm860x_batt_get_prop(struct power_supply *psy, enum power_supply_property psp,
union power_supply_propval *val);
文件系统中,所有电池相关的设备文件都是通过此API来获取数据,此函数在pm860x_battery_probe 中注册info->battery.get_property = pm860x_batt_get_prop; 此函数会调用电池相关的测量函数。
5.5. Battery work
Power supply子系统通过uevent机制把信息传输到用户空间上去,当battery的状态发生改变的时候会向用户空间上报一个uevent,这样的话用户空间就可以知道什么时候去抓信息
5.5.1. Driver定时更新battery信息机制
在battery driver使用工作队列来定时更新电池信息,工作队列定义在struct pm860x_battery_info 的struct delayed_work monitor_work;成员。monitor_work初始化在如下函数中,相关代码如下:
static __devinit int pm860x_battery_probe(struct platform_device *pdev){
...
INIT_DELAYED_WORK_DEFERRABLE(&info->monitor_work, pm860x_battery_work);
queue_delayed_work(chip->monitor_wqueue, &info->monitor_work, MONITOR_INTERVAL);
...
}
绑定pm860x_battery_work 函数到monitor_work 工作上,延时30s后将 monitor_work 加入到 monitor_wqueue 工作队列中。
static void pm860x_battery_work(struct work_struct *work)
{
struct pm860x_battery_info *info = container_of(work,
struct pm860x_battery_info, monitor_work.work);
int cap, v, ocv, i, temp;
power_supply_changed(&info->battery);
queue_delayed_work(info->chip->monitor_wqueue, &info->monitor_work, MONITOR_INTERVAL);
}
函数说明:先调用power_supply_changed 函数,延时30s后将 monitor_work 再加入到 monitor_wqueue 工作队列中。 操作的结果就是每隔30s会调用一次pm860x_battery_work和power_supply_changed函数
5.5.2. Power supply子系统更新battery信息机制
void power_supply_changed(struct power_supply *psy)函数中通过schedule_work(&psy- >changed_work); 去执行changed_work绑定的函数, 此工作队是在如下函数中初始化。
int power_supply_register(struct device *parent, struct power_supply *psy){
INIT_WORK(&psy- >changed_work, power_supply_changed_work);
....
}
static void power_supply_changed_work(struct work_struct *work){
struct power_supply *psy = container_of(work, struct power_supply, changed_work);
kobject_uevent(&psy- >dev- >kobj, KOBJ_CHANGE);
…..
}
kobject_uevent 将调用kobject_uevent_env 函数将power_supply_uevent 函数中准备好的环境变量通过netlink发送的app层。
6. charger
6.1. 数据结构
6.2. 中断
中断 | 处理函数 | 功能 |
PM8607_IRQ_CHG | pm860x_charger_handler | charger detect |
PM8607_IRQ_CHG_DONE | pm860x_done_handler | charging done |
PM8607_IRQ_CHG_FAIL | pm860x_exception_handler | charging timeout |
PM8607_IRQ_CHG_FAULT | pm860x_exception_handler | charging fault |
PM8607_IRQ_GPADC1 | pm860x_temp_handler | battery temperature |
PM8607_IRQ_VBAT | pm860x_vbattery_handler | battery voltage |
PM8607_IRQ_VCHG | pm860x_vchg_handler | vchg voltage |
6.2.1. pm860x_charger_handler
插入充电器会执行此中断
6.2.2. pm860x_done_handler
当充电彻底终止后,此中断会发生。
6.2.3. pm860x_vbattery_handler
VBATT_INT中断处理函数
6.2.4. pm860x_vchg_handler
VCHG_INT中断处理函数
6.3. charger init
6.3.1. probe函数
6.3.2. pm860x_init_charger
6.4. charger api
6.4.1. pm860x_set_charger_type
接口函数:void pm860x_set_charger_type(enum enum_charger_type type);
函数功能:设置充电器类型
函数入参为要设置的充电器的类型有:
USB_CHARGER
AC_STANDARD_CHARGER
AC_OTHER_CHARGER
函数的内部实现如下图
6.4.2. set_vchg_threshold
函数原型:static void set_vchg_threshold(struct pm860x_charger_info *info, int min, int max)
函数功能:设置VCHG_INT中断触发条件,即pm860x_vchg_handler中断函数的触发条件。
6.4.3. set_vbatt_threshold
函数原型:static void set_vbatt_threshold(struct pm860x_charger_info *info,
int min, int max)
函数功能:设置VBAT_INT中断触发条件, 即pm860x_vbattery_handler中断函数的触发条件。
内部实现如下:
6.4.4. set_charging_fsm
函数原型:static int set_charging_fsm(struct pm860x_charger_info *info)
函数功能:维护充电状态机
函数的内部实现如下图:
将程序流程图整理成fsm如下图
6.5. 充电的步骤
6.5.1. 预充
当电池的电压太低,比如过放保护的电池,需要通过预充一段时间后,再转入快充。
预充的实现函数是:static int start_precharge(struct pm860x_charger_info *info)
函数的内部实现如下图:
6.6. 快充
快充主要有两个过程先恒流再恒压,通过设置8606和8607相关寄存器来控制恒流和恒压的过程。函数实现:int start_fastcharge(struct pm860x_charger_info *info)
函数的内部实现如下图:
6.7. 停充
接口函数是:static ssize_t stop_charging(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
7. 关于电量计算