android電池(五):電池 充電IC(PM2301)驅動分析篇

關鍵詞:android 電池  電量計  PL2301任務初始化宏 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)驅動分析篇

android充電這塊,有的電源管理芯片內部包含充電管理,如s5pv210上常用的AT8937。我們這次用的max77686沒有充電控制這塊,所以我們加入一個充電IC來控制,選用PM2301.

一、PM2301和主控、電池的邏輯

如下圖所示:


1、藍色部分:IIC控制接口這個說得太多了,好多外圍器件都是通過IIC控制的,這個一定要熟悉、熟悉、熟爛了,然後可以完成比較多的工作。

2、黃色部分:中斷、使能控制腳,CHG_STATUSIRQ)、 DC_IN_INTWAKE_UP) 、 PM2301_LPLPN)、CHARGER_EN(ENN)控制引腳;

IRQ:充電IC的狀態,如果有動作通知主控;

WAKE_UP:如果有DC插入,產生中斷通知主控;

LPN

ENN:充電IC使能;

3PM2301 、電池、系統電壓的大致邏輯

標號1:系統電壓有PM2301提供;

標號2PM2301給電池充電;

標號3:系統電壓有電池提供;

標號:1和標號:3不同時提供電壓給系統,中間有一個MOS管切換;分兩種情況:

(1)、不插充電器時,有電池提供電壓給系統,走通道標號:3給系統供電;

(2)、插入DC後,系統偵測到DC插入,把3的通道關閉,打開1給系統供電,同時有2給電池充電;

二、PM2301硬件電路

如下所示:


Q5這個MOS管,就是控制系統供電的,沒有充電時,VBATTVBAT+提供,充電時,VBATTSENSE_COMM提供。

控制腳對應主控的引腳:

IIC 

IIC ID 2

CHG_STATUSIRQ

 EXYNOS4_GPX1(3)

DC_IN_INTWAKE_UP

EXYNOS4_GPX0(7)

PM2301_LPLPN

EXYNOS4_GPX1(7)

CHARGER_EN(ENN)

EXYNOS4_GPL2(0)

下圖爲PM2301的參考電路解法,同樣看到P1控制VSYSTEM電源部分的切換控制。


下圖爲整個電池充電的過程控制:

Trickle modeConstant current mode (CC mode or fast charge mode)Constant voltage mode (CV mode) End of charge feature


三、PL2301驅動部分

PL2301的硬件、工作原理做簡單的解釋,接下來我們分析驅動程序:

驅動用到知識點:

IIC的註冊;

      任務初始化宏(在上一篇我們簡單提過);

中斷線程化;

1、IIC的註冊

這個和上一篇所說的電量計相似;

1)、pm2301驅動部分

static const struct i2c_device_id pm2301_id[] = {
	{ "pm2301", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, pm2301_id);

static struct i2c_driver pm2301_i2c_driver = {
	.driver	= {
		.name	= "pm2301",
	},
	.probe		= pm2301_probe,
	.remove		= __devexit_p(pm2301_remove),
	.suspend	= pm2301_suspend,
	.resume		= pm2301_resume,
	.id_table	= pm2301_id,
};

static int __init pm2301_init(void)
{
	printk(KERN_INFO "pm2301_init !!\n");
	return i2c_add_driver(&pm2301_i2c_driver);
}
module_init(pm2301_init);

(2)、平臺驅動部分

arch/arm/mach-exynos/mach-smdk4x12.c

static struct i2c_board_info i2c_devs1[] __initdata = {

…………
#ifdef CONFIG_CHARGER_PM2301
	{
		I2C_BOARD_INFO("pm2301", 0x2c),
		.platform_data	= &pm2301_platform_data,
	},
#endif
…………
};

下圖就是我們IIC驅動註冊生成的文件;

/sys/bus/i2c/drivers/pm2301

2、關於:pm2301_platform_data這個結構體

static struct pm2301_platform_data pm2301_platform_data = {
	.hw_init = pm2301_hw_init,//(1)、硬件接口初始化化;
	.gpio_lpn = GPIO_PM2301_LP,//(2)、結構體初始化;
	.gpio_irq = GPIO_CHARGER_STATUS,
	.gpio_enn = GPIO_CHARGER_ENABLE,
	.gpio_wakeup = GPIO_CHARGER_ONLINE,
};

arch/arm/mach-exynos/mach-smdk4x12.c

1)、硬件接口初始化

static int pm2301_hw_init(void)
{
	printk("pm2301_hw_init !!\n");

	if (gpio_request(GPIO_CHARGER_ONLINE, "GPIO_CHARGER_ONLINE"))	{
		printk(KERN_ERR "%s :GPIO_CHARGER_ONLINE request port error!\n", __func__);
		goto err_gpio_failed;
	} else {
		s3c_gpio_setpull(GPIO_CHARGER_ONLINE, S3C_GPIO_PULL_NONE);
		s3c_gpio_cfgpin(GPIO_CHARGER_ONLINE, S3C_GPIO_SFN(0));
		gpio_direction_input(GPIO_CHARGER_ONLINE);
		gpio_free(GPIO_CHARGER_ONLINE);
	}

	if (gpio_request(GPIO_CHARGER_STATUS, "GPIO_CHARGER_STATUS"))	{
		printk(KERN_ERR "%s :GPIO_CHARGER_STATUS request port error!\n", __func__);
		goto err_gpio_failed;
	} else {
		s3c_gpio_setpull(GPIO_CHARGER_STATUS, S3C_GPIO_PULL_NONE);
		s3c_gpio_cfgpin(GPIO_CHARGER_STATUS, S3C_GPIO_SFN(0));
		gpio_direction_input(GPIO_CHARGER_STATUS);
		gpio_free(GPIO_CHARGER_STATUS);
	}


	if (gpio_request(GPIO_CHARGER_ENABLE, "GPIO_CHARGER_ENABLE"))	{
		printk(KERN_ERR "%s :GPIO_CHARGER_ENABLE request port error!\n", __func__);
		goto err_gpio_failed;
	} else {
		s3c_gpio_setpull(GPIO_CHARGER_ENABLE, S3C_GPIO_PULL_NONE);
		s3c_gpio_cfgpin(GPIO_CHARGER_ENABLE, S3C_GPIO_SFN(1));
		gpio_direction_output(GPIO_CHARGER_ENABLE, 0);
		gpio_free(GPIO_CHARGER_ENABLE);
	}

	if (gpio_request(GPIO_PM2301_LP, "GPIO_PM2301_LP"))	{
		printk(KERN_ERR "%s :GPIO_PM2301_LP request port error!\n", __func__);
		goto err_gpio_failed;
	} else {
		s3c_gpio_setpull(GPIO_PM2301_LP, S3C_GPIO_PULL_NONE);
		s3c_gpio_cfgpin(GPIO_PM2301_LP, S3C_GPIO_SFN(1));
		gpio_direction_output(GPIO_PM2301_LP, 1);
		gpio_free(GPIO_PM2301_LP);
	}

	return 1;

err_gpio_failed:
	return 0;
}

2)、結構體初始化

Include/linux/pm2301_charger.h

#define GPIO_CHARGER_ONLINE		EXYNOS4_GPX0(7)//對應控制腳的主控接口
#define GPIO_CHARGER_STATUS		EXYNOS4_GPX1(3)
#define GPIO_CHARGER_ENABLE 	EXYNOS4_GPL2(0)
#define GPIO_PM2301_LP		 	EXYNOS4_GPX1(7)
struct pm2301_platform_data {
	int (*hw_init)(void);
	int gpio_enn;
	int gpio_wakeup;
	int gpio_irq;
	int gpio_lpn;
};
extern int pm2301_get_online(void);
extern int pm2301_get_status(void);

3、probe函數分析

如果你是初學者,建議多看程序,你會發現,其實驅動程序的格式大多都是相同的,如這個IIC 器件的, 隊列、定時器之類的東西。

static int __devinit pm2301_probe(struct i2c_client *client,
								  const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	struct pm2301_chip *chip;
	int ret;
	printk(KERN_INFO "PM2301 probe !!\n");
//(1)、前面這部分是對IIC的初始化;
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
		return -EIO;

	chip = kzalloc(sizeof(*chip), GFP_KERNEL);

	if (!chip)
		return -ENOMEM;

	g_chip = chip;
	chip->client = client;
	chip->pdata = client->dev.platform_data;
	i2c_set_clientdata(client, chip);

	/* Hardware Init for PM2301 */
	if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {
		dev_err(&client->dev, "hardware initial failed.\n");
		goto err_hw_failed;
	}

	mutex_init(&i2c_lock);
//(2)、初始化兩個隊列
	INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);
	INIT_DELAYED_WORK_DEFERRABLE(&chip->work_status, pm2301_ststus_work);
//(3)、中斷線程化
	chip->irq_online = gpio_to_irq(chip->pdata->gpio_wakeup);
	chip->irq_status = gpio_to_irq(chip->pdata->gpio_irq);
	/* Request IRQ for PM2301 */
	ret = request_threaded_irq(chip->irq_online,
							   NULL, pm2301_dcin,
							   IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
							   "PM2301 DC IN", chip);

	if (ret) {
		printk(KERN_ERR "Cannot request irq %d for DC (%d)\n",
			   chip->irq_online, ret);
		goto err_hw_failed;
	}

#ifdef PM2301_REPORT_STATUS_BY_IRQ

	ret = request_threaded_irq(chip->irq_status,
							   NULL, pm2301_status,
							   IRQF_TRIGGER_FALLING,
							   "PM2301 STATUS", chip);

	if (ret) {
		printk(KERN_ERR "Cannot request irq %d for CHARGE STATUS (%d)\n",
			   chip->irq_status, ret);
		goto err_hw_failed;
	}
#endif


	charger_initial = 1;
	g_has_charged = 0;
	g_has_charging_full_or_stop = 0;

#ifdef PM2301_REPORT_STATUS_BY_IRQ
	/* Set wakeup source for online pin*/
	irq_set_irq_wake(chip->irq_status, 1);
#endif
	/* Set wakeup source for online pin*/
	irq_set_irq_wake(chip->irq_online, 1);

	/* Init default interrupt route for PM2301 */
	pm2301_reg_init(chip->client);
	/* Init online & status value */
	chip->online = pm2301_charger_online(chip);
	g_pm2301_online = chip->online;	/* Sync to global */
	pm2301_charger_enable(chip->client, chip->online);
	pm2301_charger_status(chip);

	printk(KERN_INFO "PM2301 probe success!!\n");
	return 0;
err_hw_failed:
	dev_err(&client->dev, "failed: power supply register\n");
	i2c_set_clientdata(client, NULL);
	kfree(chip);
	return ret;
}

(1)、前面這部分是對IIC的初始化

這部分就不再多說了,搞來搞去都是這個老樣子;

(2)、任務初始化宏

	INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);
	INIT_DELAYED_WORK_DEFERRABLE(&chip->work_status, pm2301_ststus_work);

把pm2301_online_work加入隊列chip->work_online, pm2301_ststus_work加入chip->work_status隊列。

(3)、中斷線程化  request_threaded_irq

爲什麼要提出中斷線程化?
在 Linux 中,中斷具有最高的優先級。不論在任何時刻,只要產生中斷事件,內核將立即執行相應的中斷處理程序,等到所有掛起的中斷和軟中斷處理完畢後才能執行正常的任務,因此有可能造成實時任務得不到及時的處理。中斷線程化之後,中斷將作爲內核線程運行而且被賦予不同的實時優先級,實時任務可以有比中斷線程更高的優先級。這樣,具有最高優先級的實時任務就能得到優先處理,即使在嚴重負載下仍有實時性保證。但是,並不是所有的中斷都可以被線程化,比如時鐘中斷,主要用來維護系統時間以及定時器等,其中定時器是操作系統的脈搏,一旦被線程化,就有可能被掛起,這樣後果將不堪設想,所以不應當被線程化。 

看下我們程序中如何把中斷線程化的:

	chip->irq_online = gpio_to_irq(chip->pdata->gpio_wakeup);
	chip->irq_status = gpio_to_irq(chip->pdata->gpio_irq);

看到這裏是否想起:

static struct pm2301_platform_data pm2301_platform_data = {
    ………………
	.gpio_lpn = GPIO_PM2301_LP,
	.gpio_irq = GPIO_CHARGER_STATUS,
	.gpio_enn = GPIO_CHARGER_ENABLE,
	.gpio_wakeup = GPIO_CHARGER_ONLINE,
};

#define GPIO_CHARGER_ONLINE		EXYNOS4_GPX0(7)
#define GPIO_CHARGER_STATUS		EXYNOS4_GPX1(3)
#define GPIO_CHARGER_ENABLE 		EXYNOS4_GPL2(0)
#define GPIO_PM2301_LP		 	EXYNOS4_GPX1(7)

感覺申請個中斷腳,這樣有點費勁呀;

中斷線程化:

	/* Request IRQ for PM2301 */
	ret = request_threaded_irq(chip->irq_online,
							   NULL, pm2301_dcin,
							   IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
							   "PM2301 DC IN", chip);

當有插入DC中斷出發時調用:

static irqreturn_t pm2301_dcin(int irq, void *_data)
{
	struct pm2301_chip *chip = _data;
	schedule_delayed_work(&chip->work_online, PM2301_DELAY);
	return IRQ_HANDLED;
}

Pm2301_dcin調度隊列:chip->work_online執行:pm2301_online_work函數

static void pm2301_online_work(struct work_struct *work)
{
	struct pm2301_chip *chip;
	chip = container_of(work, struct pm2301_chip, work_online.work);
	int new_online = pm2301_charger_online(chip);

	if (chip->online != new_online) {
		chip->online = new_online;
		g_pm2301_online = chip->online;	/* Sync to global */
		pm2301_charger_enable(chip->client, chip->online);//①、初始化充電IC;
#ifdef PM2301_REPORT_STATUS_BY_IRQ

		/*To avoid status pin keep low*/
		schedule_delayed_work(&chip->work_status, 1000);
#endif
#if defined(CONFIG_BATTERY_MAX17040)
		TriggerGasgaugeUpdate();//②、把DC狀態更新到max17040;
#endif
	}
}

①、初始化電IC

這裏面主要是寫一些寄存器

static void pm2301_charger_enable(struct i2c_client *client, int online)
{
	if (online) {	/* Enabled Charging*/
		int batt_capacity = 0;
    	batt_capacity = GetGasgaugeCapacity();
    	/* Don't start charging if battery capacity above 95% when DC plug in*/
    	if(0) {
    	//if( batt_capacity >= 95 ) {
			pm2301_write_reg(client, 0x01, 0x02);
			pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */
    	} else {
    		pm2301_write_reg(client, 0x00, 0x01);   /* force resume of charging */
			pm2301_write_reg(client, 0x01, 0x06);	/* ChEn=1, AutoResume=1 */
			pm2301_write_reg(client, 0x05, 0x7A); 	/* ChEoccurrentLevel:150mA, ChPrechcurrentLevel:100mA, ChCCcurrentLevel:1000mA/2000mA */
			pm2301_write_reg(client, 0x06, 0x0A);	/* ChVersumeVot:3.6V ChPrechVoltLevel:2.9V */
			pm2301_write_reg(client, 0x07, 0x1E);	/* ChVoltLevel:4.25V */
			pm2301_write_reg(client, 0x26, 0x00); 	/* always keep the register to 0 */
    	}
		g_has_charged = 1;
	} else {	/* Disable Charging*/
		pm2301_write_reg(client, 0x01, 0x02);
		pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */
		g_has_charged = 0;
	}
}

②、把DC狀態更新到max17040

TriggerGasgaugeUpdate()
插入DC這部流程如下:



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