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

關鍵詞: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+,接到max17040CELLIIC接到主控的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),//IIC地址;
		.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
	/* The module need to be update per hour (60*60)/3 = 1200 */
	if (g_TimeCount >= 1200) {
		handle_model(0);
		g_TimeCount = 0;
	}
	g_TimeCount++;
#endif

	old_online = chip->online;//(1)、保存老的電池信息,如電量、AC、USB是否插入;
	old_usb_online = chip->usb_online;
	old_vcell = chip->vcell;
	old_soc = chip->soc;
	max17040_get_online(chip->client);//(2)、讀取電池新的狀態信息
	max17040_get_vcell(chip->client);
	max17040_get_soc(chip->client);
	max17040_get_status(chip->client);

	if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {//(3)、如果電池信息有變化,就上報系統;
		/* printk(KERN_DEBUG "power_supply_changed for battery\n"); */
		power_supply_changed(&chip->battery);
	}

#if !defined(CONFIG_CHARGER_PM2301)//(4)、如果用PM2301充電IC,USB充電功能不用;
	if (old_usb_online != chip->usb_online) {
		/* printk(KERN_DEBUG "power_supply_changed for usb\n"); */

		power_supply_changed(&chip->usb);
	}
#endif

	if (old_online != chip->online) {//(5)、如果有DC插入,則更新充電狀態;
		/* printk(KERN_DEBUG "power_supply_changed for AC\n"); */
		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);//讀取是否有AC插入;
	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)) {
		/* printk(KERN_DEBUG "power_supply_changed for battery\n"); */
		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,	/* 1st field is start reg address, others are model parameters */
	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; //56224
unsigned char INI_OCVTest_Low_Byte = 0xA0;
unsigned char INI_SOCCheckA = 0x71;// 113
unsigned char INI_SOCCheckB = 0x73;//115
unsigned char INI_RCOMP = 0xa1;//161
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;
unsigned char original_OCV_2;

四、驅動分析

1Probe函數分析

上面我們簡單瞭解驅動中用到的主要知識點,後面我們把這些點串起來,驅動還是從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;//(1)、IIC 驅動部分client 申請;

	chip->client = client;
	chip->pdata = client->dev.platform_data;
	i2c_set_clientdata(client, chip);
	chip->battery.name		= "battery";//(2)、chip name;
	chip->battery.type		= POWER_SUPPLY_TYPE_BATTERY;
	chip->battery.get_property	= max17040_get_property;//(3)、獲取電池信息;
	chip->battery.properties	= max17040_battery_props;//(4)、電池各種信息;
	chip->battery.num_properties	= ARRAY_SIZE(max17040_battery_props);
	chip->battery.external_power_changed = NULL;
	ret = power_supply_register(&client->dev, &chip->battery);//(5)、battery加入power_supply
	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);//(6)、和battery相似,把ac加入power_supply
	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);//(7)、和battery相似,把usb加入power_supply
	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);//(8)、任務宏初始化,max17040加入chip->work隊列;
	schedule_delayed_work(&chip->work, MAX17040_DELAY);//(9)、通過定時器調度隊列;


	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;
}

1IIC 驅動部分client 申請;

2chip 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; /* You must never run Odrioid1 without Battery. */

		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_ONLINE,*/
			POWER_SUPPLY_PROP_VOLTAGE_NOW,
			POWER_SUPPLY_PROP_CAPACITY,
			POWER_SUPPLY_PROP_TECHNOLOGY,
			POWER_SUPPLY_PROP_HEALTH,
			POWER_SUPPLY_PROP_TEMP,
		};

5battery加入power_supply


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

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

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

前面已經分析;

(9)、通過定時器調度隊列;

前面已經分析;

2power_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
}
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);
}
發佈了68 篇原創文章 · 獲贊 135 · 訪問量 168萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章