关键词:android 电池 电量计 MAX17040 任务初始化宏 power_supply
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:samsung exynos 4210、exynos 4412 、exynos 5250
作者:xubin341719(欢迎转载,请注明作者)
完整驱动代码&规格书下载:MAX17040_PL2301
android 电池(一):锂电池基本原理篇
android 电池(二):android关机充电流程、充电画面显示
android 电池(三):android电池系统
android电池(四):电池 电量计(MAX17040)驱动分析篇
android电池(五):电池 充电IC(PM2301)驱动分析篇
电池电量计,库仑计,用max17040这颗电量IC去计量电池电量,这种方法比较合理。想起比较遥远的年代,做samsung s5pc110/sp5v210的时候,计量电量用一个AD口加两个分压电阻就做了,低电量的时候系统一直判断不准确,“低电关机”提示一会有,一会没有,客户那个郁闷呀,“到底是有电还是没电?”。
如下图,通过两个分压电阻,和一个AD脚去侦测VCC(电池)电压。
一、MAX17040的工作原理
电量计MAX17040,他通过芯片去测量电池电量,芯片本身集成的电路比较复杂,同时可以通过软件上的一些算法去实现一些处理,是测量出的电量更加准确。还有一个好处,就是他之接输出数字量,通过IIC直接读取,我们在电路设计、程序处理上更加的统一化。
如下图所示,MAX17040和电池盒主控的关系,一个AD脚接到电池VBAT+,检测到的电量信息,通过IIC传到主控。
下面是电路图,电路接口比较简单,VBAT+,接到max17040的CELL,IIC接到主控的IIC2接口,这个我们在程序中要配置。看这个器件比较简单吧。
看下max17040的内部结构,其实这也是一个AD转换的过程,单独一颗芯片去实现,这样看起来比较专业些。CELL接口,其实就是一个ADC转换的引脚,我们可以看到芯片内部有自己的时钟(time base),IIC控制器之类的,通过CELL采集到的模拟量,转换成数字量,传输给主控。
通过上面的介绍Max17040的硬件、原理我们基本上都了解了,比较简单,下面我们就重点去分析下驱动程序。
二、MAX17040 总体流程
电量计的工作流程比较简单,max17040通过CELL ADC转换引脚,把电池的相关信息,实时读取,存入max17040相应的寄存器,驱动申请一个定时器,记时结束,通过IIC去读取电池状态信息,和老的电池信息对比,如果用变化上报,然后重新计时;这样循环操作,流程如下所示:
三、MAX17040这个电量计驱动,我们主要用到以下知识点
1、IIC的注册(这个在TP、CAMERA中都有分析);
2、linux 中定时器的使用;
3、任务初始化宏;
4、linux定时器调度队列;
5、max17040测到电量后如何上传到系统(这个电池系统中有简要的分析);
6、AC、USB充电状态的上报,这个和电池电量是一种方法。
7、电池曲线的测量与加入;
1、IIC的注册
IIC这个总线,在工作中用的比较多,TP、CAMERA、电量计、充电IC、音频芯片、电源管理芯片、基本所有的传感器,所以这大家要仔细看下,后面有时间的话单独列一片介绍下IIC,从单片机时代都用的比较多,看来条总线的生命力很强,像C语言一样,很难被同类的东西替代到,至少现在应该是这样的。
看下他结构体的初始化与驱动的申请,这个比较统一,这里就不想想解释了。
(1)、IIC驱动的注册:
- static const struct i2c_device_id max17040_id[] = {
- { "max17040", 0 },
- { }
- };
- MODULE_DEVICE_TABLE(i2c, max17040_id);
-
- static struct i2c_driver max17040_i2c_driver = {
- .driver = {
- .name = "max17040",
- },
- .probe = max17040_probe,
- .remove = __devexit_p(max17040_remove),
- .suspend = max17040_suspend,
- .resume = max17040_resume,
- .id_table = max17040_id,
- };
-
- static int __init max17040_init(void)
- {
- printk("MAX17040 max17040_init !!\n");
- wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
- return i2c_add_driver(&max17040_i2c_driver);
- }
- module_init(max17040_init);
(2)在arch/arm/mach-exynos/mach-smdk4x12.c中,IC平台驱动的注册:
- static struct i2c_board_info i2c_devs2[] __initdata = {
- #if defined(CONFIG_BATTERY_MAX17040)
- {
- I2C_BOARD_INFO("max17040", 0x36),
- .platform_data = &max17040_platform_data,
- },
- #endif
- ……………………
- };
下图就是我们IIC驱动注册生成的文件;
/sys/bus/i2c/drivers/max17040
2、linux 中定时器的使用
定时器,就是定一个时间, 比如:申请一个10秒定时器,linux系统开始计时,到10秒,请示器清零重新计时并发出信号告知系统计时完成,系统接到这个信号,做相应的处理;
- #include <linux/delay.h>
- #define MAX17040_DELAY msecs_to_jiffies(5000)
3、任务初始化宏
- INIT_WORK(work,func);
- INTI_DELAYED_WORK(work,func);
- INIT_DELAYED_WORK_DEFERRABLE(work,func);
任务结构体的初始化完成后,接下来要将任务安排进工作队列。 可采用多种方法来完成这一操作。 首先,利用 queue_work 简单地将任务安排进工作队列(这将任务绑定到当前的 CPU)。 或者,可以通过 queue_work_on 来指定处理程序在哪个 CPU 上运行。 两个附加的函数为延迟任务提供相同的功能(其结构体装入结构体 work_struct 之中,并有一个 计时器用于任务延迟 )。
- INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
- 调度函数 max17040_work加入chip->work队列;
4、linux定时器调度队列
- INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
- schedule_delayed_work(&chip->work, MAX17040_DELAY);
- 通过定时器调度队列;
5、max17040测到电量后如何上传到系统(这个电池系统中有简要的分析);
4中的定时器记时完成,就可以调度队列,chip->work执行:max17040_work函数,把改读取的信息上传,我们看下max17040_work函数的实现:
- static void max17040_work(struct work_struct *work)
- {
- struct max17040_chip *chip;
- int old_usb_online, old_online, old_vcell, old_soc;
- chip = container_of(work, struct max17040_chip, work.work);
-
- #ifdef MAX17040_SUPPORT_CURVE
-
- if (g_TimeCount >= 1200) {
- handle_model(0);
- g_TimeCount = 0;
- }
- g_TimeCount++;
- #endif
-
- old_online = chip->online;
- old_usb_online = chip->usb_online;
- old_vcell = chip->vcell;
- old_soc = chip->soc;
- max17040_get_online(chip->client);
- max17040_get_vcell(chip->client);
- max17040_get_soc(chip->client);
- max17040_get_status(chip->client);
-
- if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {
-
- power_supply_changed(&chip->battery);
- }
-
- #if !defined(CONFIG_CHARGER_PM2301)//(4)、如果用PM2301充电IC,USB充电功能不用;
- if (old_usb_online != chip->usb_online) {
-
-
- power_supply_changed(&chip->usb);
- }
- #endif
-
- if (old_online != chip->online) {
-
- power_supply_changed(&chip->ac);
- }
-
- schedule_delayed_work(&chip->work, MAX17040_DELAY);
- }
(1)、保存老的电池信息,如电量、AC、USB是否插入
- old_online = chip->online;
- old_usb_online = chip->usb_online;
- old_vcell = chip->vcell;
- old_soc = chip->soc;
(2)、读取电池新的状态信息
- max17040_get_online(chip->client);
- max17040_get_vcell(chip->client);
- max17040_get_soc(chip->client);
- max17040_get_status(chip->client);
(3)、如果电池信息有变化,就上报系统
- if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {
-
- power_supply_changed(&chip->battery);
- }
power_supply_changed这个函数比较重要, 我们后面分析;
(4)、如果用PM2301充电IC,USB充电功能不用
这个是由于我们的系统耗电比较大,用USB充电时,电流过小,所以出现越充越少的现象,所以这个功能给去掉了。
(5)、如果有DC插入,则跟新充电状态
- power_supply_changed(&chip->ac);
6、AC、USB充电状态怎么更新到应用
如上面所说,通过power_supply_changed上报;
7、电池曲线的测量与加入
电池曲线,就是电池的冲放电信息,就是用专业的设备,对电池连续充放电几天,测出一个比较平均的值。然后转换成针对电量IC(如我们用的max17040)的数字量,填入一个数组中,如下图所示:
下面数据时针对电池曲线的数字量,和相关参数。如上图所示,为160小时的电池信息,包括:不同颜色分别代表不同的曲线:如temperature ,reference SOC ,fuel gauge SOC,Vcell,Empty Voltage
数据表格如下:
- Device=MAX17040
- Title = 1055_2_113012
- EmptyAdjustment = 0
- FullAdjustment= 100
- RCOMP0=161
- TempCoUp =0
- TempCoDown = -2
- OCVTest = 56224
- SOCCheckA = 113
- SOCCheckB = 115
- bits= 18
- 0xC2 0xE8 0x0D 0x37 0x51 0x5B 0x5E 0x62
- 0x6A 0x88 0xA6 0xCB 0xF1 0x3C 0x99 0x1A
- 0x60 0x0D 0x80 0x0D 0xA0 0x01 0xC0 0x0C
- 0xF0 0x0F 0x30 0x0F 0x90 0x06 0x10 0x06
-
- 0xAC 0x20 0xAE 0x80 0xB0 0xD0 0xB3 0x70
- 0xB5 0x10 0xB5 0xB0 0xB5 0xE0 0xB6 0x20
- 0xB6 0xA0 0xB8 0x80 0xBA 0x60 0xBC 0xB0
- 0xBF 0x10 0xC3 0xC0 0xC9 0x90 0xD1 0xA0
- 0x02 0x90 0x0E 0x00 0x0C 0x10 0x0E 0x20
- 0x2C 0x60 0x4C 0xB0 0x39 0x80 0x39 0x80
- 0x0C 0xD0 0x0C 0xD0 0x0A 0x10 0x09 0xC0
- 0x08 0xF0 0x07 0xF0 0x05 0x60 0x05 0x60
-
- 0xC0 0x09 0xE0 0x00 0x00 0x01 0x30 0x02
- 0x52 0x06 0x54 0x0B 0x53 0x080x63 0x08
- 0x29 0xE0 0xC1 0xE2 0xC6 0xCB 0x98 0x98
- 0xCD 0xCD 0xA1 0x9C 0x8F 0x7F 0x56 0x56
加入驱动中的值:
/driver/power/max17040_common.c中
- unsigned char model_data[65] = {
- 0x40,
- 0xAC, 0x20,0xAE, 0x80, 0xB0, 0xD0, 0xB3, 0x70,
- 0xB5, 0x10, 0xB5, 0xB0, 0xB5, 0xE0,0xB6, 0x20,
- 0xB6, 0xA0, 0xB8, 0x80, 0xBA, 0x60, 0xBC, 0xB0,
- 0xBF, 0x10, 0xC3, 0xC0, 0xC9, 0x90, 0xD1, 0xA0,
- 0x02, 0x90, 0x0E, 0x00, 0x0C, 0x10,0x0E, 0x20,
- 0x2C, 0x60,0x4C, 0xB0, 0x39, 0x80, 0x39, 0x80,
- 0x0C, 0xD0,0x0C, 0xD0, 0x0A, 0x10,0x09, 0xC0,
- 0x08, 0xF0, 0x07, 0xF0, 0x05, 0x60, 0x05, 0x60,
- };
-
- unsigned char INI_OCVTest_High_Byte = 0xDB;
- unsigned char INI_OCVTest_Low_Byte = 0xA0;
- unsigned char INI_SOCCheckA = 0x71;
- unsigned char INI_SOCCheckB = 0x73;
- unsigned char INI_RCOMP = 0xa1;
- unsigned char INI_bits = 18;
- unsigned char original_OCV_1;
- unsigned char original_OCV_2;
- #elseunsigned char INI_RCOMP = 0x64;
- unsigned char INI_bits = 19;
- unsigned char original_OCV_1;
- <strong>unsigned char original_OCV_2;</strong>
四、驱动分析
1、Probe函数分析
上面我们简单了解驱动中用到的主要知识点,后面我们把这些点串起来,驱动还是从probe说起;
- static int __devinit max17040_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
- {
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
- struct max17040_chip *chip;
- int ret;
- printk("MAX17040 probe !!\n");
-
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
- return -EIO;
-
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
-
- if (!chip)
- return -ENOMEM;
-
- g_chip = chip;
- g_i2c_client = client;
-
- chip->client = client;
- chip->pdata = client->dev.platform_data;
- i2c_set_clientdata(client, chip);
- chip->battery.name = "battery";
- chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
- chip->battery.get_property = max17040_get_property;
- chip->battery.properties = max17040_battery_props;
- chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props);
- chip->battery.external_power_changed = NULL;
- ret = power_supply_register(&client->dev, &chip->battery);
- if (ret)
- goto err_battery_failed;
-
-
- chip->ac.name = "ac"
- chip->ac.type = POWER_SUPPLY_TYPE_MAINS;
- chip->ac.get_property = adapter_get_property;
- chip->ac.properties = adapter_get_props;
- chip->ac.num_properties = ARRAY_SIZE(adapter_get_props);
- chip->ac.external_power_changed = NULL;
- ret = power_supply_register(&client->dev, &chip->ac);
- if (ret)
- goto err_ac_failed;
-
-
- #if !defined(CONFIG_CHARGER_PM2301)
- chip->usb.name = "usb";
- chip->usb.type = POWER_SUPPLY_TYPE_USB;
- chip->usb.get_property = usb_get_property;
- chip->usb.properties = usb_get_props;
- chip->usb.num_properties = ARRAY_SIZE(usb_get_props);
- chip->usb.external_power_changed = NULL;
- ret = power_supply_register(&client->dev, &chip->usb);
- if (ret)
- goto err_usb_failed;
-
- if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {
- dev_err(&client->dev, "hardware initial failed.\n");
- goto err_hw_init_failed;
- }
- #endif
-
- #ifdef MAX17040_SUPPORT_CURVE
- g_TimeCount = 0;
- handle_model(0);
- #endif
- max17040_get_version(client);
- battery_initial = 1;
-
- INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
- schedule_delayed_work(&chip->work, MAX17040_DELAY);
-
-
- printk("MAX17040 probe success!!\n");
- return 0;
-
- err_hw_init_failed:
- power_supply_unregister(&chip->usb);
- err_usb_failed:
- power_supply_unregister(&chip->ac);
- err_ac_failed:
- power_supply_unregister(&chip->battery);
- err_battery_failed:
- dev_err(&client->dev, "failed: power supply register\n");
- i2c_set_clientdata(client, NULL);
- kfree(chip);
- return ret;
- }
(1)、IIC 驱动部分client 申请;
(2)、chip name;
(3)、获取电池信息;
通过传递下来的参数,来读取结构体中相应的状态,这个函数实现比较简单。
- static int max17040_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
- {
- struct max17040_chip *chip = container_of(psy,
- struct max17040_chip, battery);
-
- switch (psp) {
- case POWER_SUPPLY_PROP_STATUS:
- val->intval = chip->status;
- break;
-
- case POWER_SUPPLY_PROP_ONLINE:
- val->intval = chip->online;
- break;
-
- case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- case POWER_SUPPLY_PROP_PRESENT:
- val->intval = chip->vcell;
-
- if (psp == POWER_SUPPLY_PROP_PRESENT)
- val->intval = 1;
-
- break;
-
- case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = chip->soc;
- break;
-
- case POWER_SUPPLY_PROP_TECHNOLOGY:
- val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
- break;
-
- case POWER_SUPPLY_PROP_HEALTH:
- if (chip->vcell < 2850)
- val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
- else
- val->intval = POWER_SUPPLY_HEALTH_GOOD;
-
- break;
-
- case POWER_SUPPLY_PROP_TEMP:
- val->intval = 365;
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
- }
(4)电池各种信息
- static enum power_supply_property max17040_battery_props[] = {
- POWER_SUPPLY_PROP_PRESENT,
- POWER_SUPPLY_PROP_STATUS,
-
- POWER_SUPPLY_PROP_VOLTAGE_NOW,
- POWER_SUPPLY_PROP_CAPACITY,
- POWER_SUPPLY_PROP_TECHNOLOGY,
- POWER_SUPPLY_PROP_HEALTH,
- POWER_SUPPLY_PROP_TEMP,
- };
(5)、battery加入power_supply;
(6)、和battery相似,把ac加入power_supply;
(7)、和battery相似,把usb加入power_supply;
(8)、max17040加入chip->work队列;
前面已经分析;
(9)、通过定时器调度队列;
前面已经分析;
2、power_supply_changed简要分析
如:把电池电量信息上报:我们在max17040_work队列调度函数中, 如果有电池信息、状态变化,则上用power_supply_changed上报。
- power_supply_changed(&chip->battery);
Kernel/drivers/power/power_supply_core.c中:
- void power_supply_changed(struct power_supply *psy)
- {
- unsigned long flags;
-
- dev_dbg(psy->dev, "%s\n", __func__);
-
- spin_lock_irqsave(&psy->changed_lock, flags);
- psy->changed = true;
- wake_lock(&psy->work_wake_lock);
- spin_unlock_irqrestore(&psy->changed_lock, flags);
- schedule_work(&psy->changed_work);
- }
- Psy->changed_work的执行函数:
- 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)
- wake_unlock(&psy->work_wake_lock);
- spin_unlock_irqrestore(&psy->changed_lock, flags);
- }