天天看點

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

電池電量計,庫侖計,用max17040這顆電量IC去計量電池電量,這種方法比較合理。想起比較遙遠的年代,做samsung s5pc110/sp5v210的時候,計量電量用一個AD口加兩個分壓電阻就做了,低電量的時候系統一直判斷不準确,“低電關機”提示一會有,一會沒有,客戶那個郁悶呀,“到底是有電還是沒電?”。

如下圖,通過兩個分壓電阻,和一個AD腳去偵測VCC(電池)電壓。

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

一、MAX17040的工作原理

電量計MAX17040,他通過晶片去測量電池電量,晶片本身內建的電路比較複雜,同時可以通過軟體上的一些​​算法​​去實作一些處理,是測量出的電量更加準确。還有一個好處,就是他之接輸出數字量,通過IIC直接讀取,我們在電路設計、程式處理上更加的統一化。

如下圖所示,MAX17040和電池盒主要的關系,一個AD腳接到電池VBAT+,檢測到的電量資訊,通過IIC傳到主要。

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

下面是電路圖,電路接口比較簡單,VBAT+,接到max17040的CELL,IIC接到主要的IIC2接口,這個我們在程式中要配置。看這個器件比較簡單吧。

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

看下max17040的内部結構,其實這也是一個AD轉換的過程,單獨一顆晶片去實作,這樣看起來比較專業些。CELL接口,其實就是一個ADC轉換的引腳,我們可以看到晶片内部有自己的時鐘(time base),IIC控制器之類的,通過CELL采集到的模拟量,轉換成數字量,傳輸給主要。

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

通過上面的介紹Max17040的硬體、原理我們基本上都了解了,比較簡單,下面我們就重點去分析下驅動程式。

二、MAX17040 總體流程

電量計的工作流程比較簡單,max17040通過CELL ADC轉換引腳,把電池的相關資訊,實時讀取,存入max17040相應的寄存器,驅動申請一個定時器,記時結束,通過IIC去讀取電池狀态資訊,和老的電池資訊對比,如果用變化上報,然後重新計時;這樣循環操作,流程如下所示:

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

三、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驅動的注冊:

1 static const struct i2c_device_id max17040_id[] = {
 2     { "max17040", 0 },
 3     { }
 4 };
 5 MODULE_DEVICE_TABLE(i2c, max17040_id);
 6 
 7 static struct i2c_driver max17040_i2c_driver = {
 8     .driver    = {
 9         .name    = "max17040",
10     },
11     .probe        = max17040_probe,
12     .remove        = __devexit_p(max17040_remove),
13     .suspend    = max17040_suspend,
14     .resume        = max17040_resume,
15     .id_table    = max17040_id,
16 };
17 
18 static int __init max17040_init(void)
19 {
20     printk("MAX17040 max17040_init !!\n");
21     wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
22     return i2c_add_driver(&max17040_i2c_driver);
23 }
24      

(2)在arch/arm/mach-exynos/mach-smdk4x12.c中,IC平台驅動的注冊:

1 static struct i2c_board_info i2c_devs2[] __initdata = {
2 #if defined(CONFIG_BATTERY_MAX17040)
3     {
4         I2C_BOARD_INFO("max17040", 0x36),//IIC位址;
5         .platform_data = &max17040_platform_data,
6     },
7 #endif
8 ……………………
9      

下圖就是我們IIC驅動注冊生成的檔案;

/sys/bus/i2c/drivers/max17040 

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

2、linux 中定時器的使用

定時器,就是定一個時間, 比如:申請一個10秒定時器,linux系統開始計時,到10秒,請示器清零重新計時并發出信号告知系統計時完成,系統接到這個信号,做相應的處理;

1 #include <linux/delay.h>
2 #define      
android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

3、任務初始化宏

1     INIT_WORK(work,func);
2     INTI_DELAYED_WORK(work,func);
3      

任務結構體的初始化完成後,接下來要将任務安排進工作隊列。 可采用多種方法來完成這一操作。 首先,利用 queue_work 簡單地将任務安排進工作隊列(這将任務綁定到目前的 CPU)。 或者,可以通過 queue_work_on 來指定處理程式在哪個 CPU 上運作。 兩個附加的函數為延遲任務提供相同的功能(其結構體裝入結構體 work_struct 之中,并有一個 計時器用于任務延遲 )。

1     INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
2      

4、linux定時器排程隊列

1 INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);  
2 schedule_delayed_work(&chip->work, MAX17040_DELAY);  
3      

5、max17040測到電量後如何上傳到系統(這個電池系統中有簡要的分析);

       4中的定時器記時完成,就可以排程隊列,chip->work執行:max17040_work函數,把改讀取的資訊上傳,我們看下max17040_work函數的實作:

1 static void max17040_work(struct work_struct *work)
 2 {
 3     struct max17040_chip *chip;
 4     int old_usb_online, old_online, old_vcell, old_soc;
 5     chip = container_of(work, struct max17040_chip, work.work);
 6 
 7 #ifdef MAX17040_SUPPORT_CURVE
 8     /* The module need to be update per hour (60*60)/3 = 1200 */
 9     if (g_TimeCount >= 1200) {
10         handle_model(0);
11         g_TimeCount = 0;
12     }
13     g_TimeCount++;
14 #endif
15 
16     old_online = chip->online;//(1)、儲存老的電池資訊,如電量、AC、USB是否插入;
17     old_usb_online = chip->usb_online;
18     old_vcell = chip->vcell;
19     old_soc = chip->soc;
20     max17040_get_online(chip->client);//(2)、讀取電池新的狀态資訊
21     max17040_get_vcell(chip->client);
22     max17040_get_soc(chip->client);
23     max17040_get_status(chip->client);
24 
25     if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {//(3)、如果電池資訊有變化,就上報系統;
26         /* printk(KERN_DEBUG "power_supply_changed for battery\n"); */
27         power_supply_changed(&chip->battery);
28     }
29 
30 #if !defined(CONFIG_CHARGER_PM2301)//(4)、如果用PM2301充電IC,USB充電功能不用;
31     if (old_usb_online != chip->usb_online) {
32         /* printk(KERN_DEBUG "power_supply_changed for usb\n"); */
33 
34         power_supply_changed(&chip->usb);
35     }
36 #endif
37 
38     if (old_online != chip->online) {//(5)、如果有DC插入,則更新充電狀态;
39         /* printk(KERN_DEBUG "power_supply_changed for AC\n"); */
40         power_supply_changed(&chip->ac);
41     }
42 
43     schedule_delayed_work(&chip->work, MAX17040_DELAY);
44      

(1)、儲存老的電池資訊,如電量、AC、USB是否插入

1     old_online = chip->online;
2     old_usb_online = chip->usb_online;
3     old_vcell = chip->vcell;
4      

(2)、讀取電池新的狀态資訊

1     max17040_get_online(chip->client);//讀取是否有AC插入;
2     max17040_get_vcell(chip->client);//讀取電池電壓;(這個地方原來寫錯,多謝細心網友,更正!!)
3     max17040_get_soc(chip->client);//讀取電池電量;
4     max17040_get_status(chip->client);//讀取狀态;      

(3)、如果電池資訊有變化,就上報系統

1 if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {  
2     /* printk(KERN_DEBUG "power_supply_changed for battery\n"); */  
3     power_supply_changed(&chip->battery);  
4      

power_supply_changed這個函數比較重要, 我們後面分析;

(4)、如果用PM2301充電IC,USB充電功能不用

這個是由于我們的系統耗電比較大,用USB充電時,電流過小,是以出現越充越少的現象,是以這個功能給去掉了。

(5)、如果有DC插入,則跟新充電狀态

1      

6、AC、USB充電狀态怎麼更新到應用

如上面所說,通過power_supply_changed上報;

7、電池曲線的測量與加入

電池曲線,就是電池的沖放電資訊,就是用專業的裝置,對電池連續充放電幾天,測出一個比較平均的值。然後轉換成針對電量IC(如我們用的max17040)的數字量,填入一個數組中,如下圖所示:

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

下面資料時針對電池曲線的數字量,和相關參數。如上圖所示,為160小時的電池資訊,包括:不同顔色分别代表不同的曲線:如temperature ,reference SOC ,fuel gauge SOC,Vcell,Empty Voltage

資料表格如下:

1 Device=MAX17040
 2 Title = 1055_2_113012
 3 EmptyAdjustment = 0
 4 FullAdjustment= 100
 5 RCOMP0=161
 6 TempCoUp =0
 7 TempCoDown = -2
 8 OCVTest = 56224
 9 SOCCheckA = 113
10 SOCCheckB = 115
11 bits= 18
12 0xC2 0xE8 0x0D 0x37 0x51 0x5B 0x5E 0x62 
13 0x6A 0x88 0xA6 0xCB 0xF1 0x3C 0x99 0x1A 
14 0x60 0x0D 0x80 0x0D 0xA0 0x01 0xC0 0x0C 
15 0xF0 0x0F 0x30 0x0F 0x90 0x06 0x10 0x06 
16 
17 0xAC 0x20 0xAE 0x80 0xB0 0xD0 0xB3 0x70
18 0xB5 0x10 0xB5 0xB0 0xB5 0xE0 0xB6 0x20 
19 0xB6 0xA0 0xB8 0x80 0xBA 0x60 0xBC 0xB0 
20 0xBF 0x10 0xC3 0xC0 0xC9 0x90 0xD1 0xA0 
21 0x02 0x90 0x0E 0x00 0x0C 0x10 0x0E 0x20 
22 0x2C 0x60 0x4C 0xB0 0x39 0x80 0x39 0x80 
23 0x0C 0xD0 0x0C 0xD0 0x0A 0x10 0x09 0xC0 
24 0x08 0xF0 0x07 0xF0 0x05 0x60 0x05 0x60 
25 
26 0xC0 0x09 0xE0 0x00 0x00 0x01 0x30 0x02 
27 0x52 0x06 0x54 0x0B 0x53 0x080x63  0x08 
28 0x29 0xE0 0xC1 0xE2 0xC6 0xCB 0x98 0x98 
29 0xCD 0xCD 0xA1 0x9C 0x8F 0x7F 0x56 0x56      

加入驅動中的值:

/driver/power/max17040_common.c中

1 unsigned char model_data[65] = {
 2     0x40,    /* 1st field is start reg address, others are model parameters */
 3     0xAC, 0x20,0xAE, 0x80, 0xB0, 0xD0, 0xB3, 0x70,
 4     0xB5, 0x10, 0xB5, 0xB0,    0xB5, 0xE0,0xB6, 0x20,
 5     0xB6, 0xA0, 0xB8, 0x80, 0xBA, 0x60, 0xBC, 0xB0,
 6     0xBF, 0x10, 0xC3, 0xC0, 0xC9, 0x90, 0xD1, 0xA0,
 7     0x02, 0x90, 0x0E, 0x00, 0x0C, 0x10,0x0E, 0x20,
 8     0x2C, 0x60,0x4C, 0xB0, 0x39, 0x80, 0x39, 0x80,
 9     0x0C, 0xD0,0x0C, 0xD0,    0x0A, 0x10,0x09, 0xC0,
10     0x08, 0xF0, 0x07, 0xF0, 0x05, 0x60, 0x05, 0x60,
11 };
12 
13 unsigned char INI_OCVTest_High_Byte = 0xDB; //56224
14 unsigned char INI_OCVTest_Low_Byte = 0xA0;
15 unsigned char INI_SOCCheckA = 0x71;// 113
16 unsigned char INI_SOCCheckB = 0x73;//115
17 unsigned char INI_RCOMP = 0xa1;//161
18 unsigned char INI_bits = 18;
19 unsigned char original_OCV_1;
20 unsigned char original_OCV_2;
21 #elseunsigned char INI_RCOMP = 0x64;
22 unsigned char INI_bits = 19;
23 unsigned char original_OCV_1;
24 <strong>unsigned char      

四、驅動分析

1、Probe函數分析

上面我們簡單了解驅動中用到的主要知識點,後面我們把這些點串起來,驅動還是從probe說起;

1 static int __devinit max17040_probe(struct i2c_client *client,
 2                                     const struct i2c_device_id *id)
 3 {
 4     struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
 5     struct max17040_chip *chip;
 6     int ret;
 7     printk("MAX17040 probe !!\n");
 8 
 9     if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
10         return -EIO;
11 
12     chip = kzalloc(sizeof(*chip), GFP_KERNEL);
13 
14     if (!chip)
15         return -ENOMEM;
16 
17     g_chip = chip;
18     g_i2c_client = client;//(1)、IIC 驅動部分client 申請;
19 
20     chip->client = client;
21     chip->pdata = client->dev.platform_data;
22     i2c_set_clientdata(client, chip);
23     chip->battery.name        = "battery";//(2)、chip name;
24     chip->battery.type        = POWER_SUPPLY_TYPE_BATTERY;
25     chip->battery.get_property    = max17040_get_property;//(3)、擷取電池資訊;
26     chip->battery.properties    = max17040_battery_props;//(4)、電池各種資訊;
27     chip->battery.num_properties    = ARRAY_SIZE(max17040_battery_props);
28     chip->battery.external_power_changed = NULL;
29     ret = power_supply_register(&client->dev, &chip->battery);//(5)、battery加入power_supply
30     if (ret)
31         goto err_battery_failed;
32 
33 
34     chip->ac.name        = "ac"
35     chip->ac.type        = POWER_SUPPLY_TYPE_MAINS;
36     chip->ac.get_property    = adapter_get_property;
37     chip->ac.properties    = adapter_get_props;
38     chip->ac.num_properties    = ARRAY_SIZE(adapter_get_props);
39     chip->ac.external_power_changed = NULL;
40     ret = power_supply_register(&client->dev, &chip->ac);//(6)、和battery相似,把ac加入power_supply
41     if (ret)
42         goto err_ac_failed;
43 
44 
45 #if !defined(CONFIG_CHARGER_PM2301)
46     chip->usb.name        = "usb";
47     chip->usb.type        = POWER_SUPPLY_TYPE_USB;
48     chip->usb.get_property    = usb_get_property;
49     chip->usb.properties    = usb_get_props;
50     chip->usb.num_properties    = ARRAY_SIZE(usb_get_props);
51     chip->usb.external_power_changed = NULL;
52     ret = power_supply_register(&client->dev, &chip->usb);//(7)、和battery相似,把usb加入power_supply
53     if (ret)
54         goto err_usb_failed;
55 
56     if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {
57         dev_err(&client->dev, "hardware initial failed.\n");
58         goto err_hw_init_failed;
59     }
60 #endif
61 
62 #ifdef MAX17040_SUPPORT_CURVE
63      g_TimeCount = 0;
64      handle_model(0);
65 #endif
66     max17040_get_version(client);
67     battery_initial = 1;
68 
69     INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);//(8)、任務宏初始化,max17040加入chip->work隊列;
70     schedule_delayed_work(&chip->work, MAX17040_DELAY);//(9)、通過定時器排程隊列;
71 
72 
73     printk("MAX17040 probe success!!\n");
74     return 0;
75 
76 err_hw_init_failed:
77     power_supply_unregister(&chip->usb);
78 err_usb_failed:
79     power_supply_unregister(&chip->ac);
80 err_ac_failed:
81     power_supply_unregister(&chip->battery);
82 err_battery_failed:
83     dev_err(&client->dev, "failed: power supply register\n");
84     i2c_set_clientdata(client, NULL);
85     kfree(chip);
86     return ret;
87      

(1)、IIC 驅動部分client 申請;

(2)、chip name;

(3)、擷取電池資訊;

通過傳遞下來的參數,來讀取結構體中相應的狀态,這個函數實作比較簡單。

1 static int max17040_get_property(struct power_supply *psy,
 2                                  enum power_supply_property psp,
 3                                  union power_supply_propval *val)
 4 {
 5     struct max17040_chip *chip = container_of(psy,
 6                                  struct max17040_chip, battery);
 7 
 8     switch (psp) {
 9     case POWER_SUPPLY_PROP_STATUS:
10         val->intval = chip->status;
11         break;
12 
13     case POWER_SUPPLY_PROP_ONLINE:
14         val->intval = chip->online;
15         break;
16 
17     case POWER_SUPPLY_PROP_VOLTAGE_NOW:
18     case POWER_SUPPLY_PROP_PRESENT:
19         val->intval = chip->vcell;
20 
21         if (psp  == POWER_SUPPLY_PROP_PRESENT)
22             val->intval = 1; /* You must never run Odrioid1 without Battery. */
23 
24         break;
25 
26     case POWER_SUPPLY_PROP_CAPACITY:
27         val->intval = chip->soc;
28         break;
29 
30     case POWER_SUPPLY_PROP_TECHNOLOGY:
31         val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
32         break;
33 
34     case POWER_SUPPLY_PROP_HEALTH:
35         if (chip->vcell  < 2850)
36             val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
37         else
38             val->intval = POWER_SUPPLY_HEALTH_GOOD;
39 
40         break;
41 
42     case POWER_SUPPLY_PROP_TEMP:
43         val->intval = 365;
44         break;
45 
46     default:
47         return -EINVAL;
48     }
49 
50     return 0;
51      

(4)電池各種資訊

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】
1 static enum power_supply_property max17040_battery_props[] = {
 2             POWER_SUPPLY_PROP_PRESENT,
 3             POWER_SUPPLY_PROP_STATUS,
 4             /*POWER_SUPPLY_PROP_ONLINE,*/
 5             POWER_SUPPLY_PROP_VOLTAGE_NOW,
 6             POWER_SUPPLY_PROP_CAPACITY,
 7             POWER_SUPPLY_PROP_TECHNOLOGY,
 8             POWER_SUPPLY_PROP_HEALTH,
 9             POWER_SUPPLY_PROP_TEMP,
10      

(5)、battery加入power_supply;

android電池(四):電池 電量計(MAX17040)驅動分析篇【轉】

(6)、和battery相似,把ac加入power_supply;

(7)、和battery相似,把usb加入power_supply;

(8)、max17040加入chip->work隊列;

前面已經分析;

(9)、通過定時器排程隊列;

前面已經分析;

2、power_supply_changed簡要分析

如:把電池電量資訊上報:我們在max17040_work隊列排程函數中, 如果有電池資訊、狀态變化,則上用power_supply_changed上報。

1      

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
}
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);//uevent狀态
        spin_lock_irqsave(&psy->changed_lock, flags);
    }
    if (!psy->changed)
        wake_unlock(&psy->work_wake_lock);
    spin_unlock_irqrestore(&psy->changed_lock, flags);
}      

繼續閱讀