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

mirkerson發表於2015-05-28

分類: S5PXX(三星) linux android 6736人閱讀 評論(4) 收藏 舉報

關鍵詞: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驅動部分

  1. static const struct i2c_device_id pm2301_id[] = {  
  2.     { "pm2301", 0 },  
  3.     { }  
  4. };  
  5. MODULE_DEVICE_TABLE(i2c, pm2301_id);  
  6.   
  7. static struct i2c_driver pm2301_i2c_driver = {  
  8.     .driver = {  
  9.         .name   = "pm2301",  
  10.     },  
  11.     .probe      = pm2301_probe,  
  12.     .remove     = __devexit_p(pm2301_remove),  
  13.     .suspend    = pm2301_suspend,  
  14.     .resume     = pm2301_resume,  
  15.     .id_table   = pm2301_id,  
  16. };  
  17.   
  18. static int __init pm2301_init(void)  
  19. {  
  20.     printk(KERN_INFO "pm2301_init !!\n");  
  21.     return i2c_add_driver(&pm2301_i2c_driver);  
  22. }  
  23. module_init(pm2301_init);  

(2)、平臺驅動部分

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

  1. static struct i2c_board_info i2c_devs1[] __initdata = {  
  2.   
  3. …………  
  4. #ifdef CONFIG_CHARGER_PM2301  
  5.     {  
  6.         I2C_BOARD_INFO("pm2301", 0x2c),  
  7.         .platform_data  = &pm2301_platform_data,  
  8.     },  
  9. #endif  
  10. …………  
  11. };  

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

/sys/bus/i2c/drivers/pm2301

2、關於:pm2301_platform_data這個結構體

  1. static struct pm2301_platform_data pm2301_platform_data = {  
  2.     .hw_init = pm2301_hw_init,//(1)、硬體介面初始化化;  
  3.     .gpio_lpn = GPIO_PM2301_LP,//(2)、結構體初始化;  
  4.     .gpio_irq = GPIO_CHARGER_STATUS,  
  5.     .gpio_enn = GPIO_CHARGER_ENABLE,  
  6.     .gpio_wakeup = GPIO_CHARGER_ONLINE,  
  7. };  

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

1)、硬體介面初始化

  1. static int pm2301_hw_init(void)  
  2. {  
  3.     printk("pm2301_hw_init !!\n");  
  4.   
  5.     if (gpio_request(GPIO_CHARGER_ONLINE, "GPIO_CHARGER_ONLINE"))   {  
  6.         printk(KERN_ERR "%s :GPIO_CHARGER_ONLINE request port error!\n", __func__);  
  7.         goto err_gpio_failed;  
  8.     } else {  
  9.         s3c_gpio_setpull(GPIO_CHARGER_ONLINE, S3C_GPIO_PULL_NONE);  
  10.         s3c_gpio_cfgpin(GPIO_CHARGER_ONLINE, S3C_GPIO_SFN(0));  
  11.         gpio_direction_input(GPIO_CHARGER_ONLINE);  
  12.         gpio_free(GPIO_CHARGER_ONLINE);  
  13.     }  
  14.   
  15.     if (gpio_request(GPIO_CHARGER_STATUS, "GPIO_CHARGER_STATUS"))   {  
  16.         printk(KERN_ERR "%s :GPIO_CHARGER_STATUS request port error!\n", __func__);  
  17.         goto err_gpio_failed;  
  18.     } else {  
  19.         s3c_gpio_setpull(GPIO_CHARGER_STATUS, S3C_GPIO_PULL_NONE);  
  20.         s3c_gpio_cfgpin(GPIO_CHARGER_STATUS, S3C_GPIO_SFN(0));  
  21.         gpio_direction_input(GPIO_CHARGER_STATUS);  
  22.         gpio_free(GPIO_CHARGER_STATUS);  
  23.     }  
  24.   
  25.   
  26.     if (gpio_request(GPIO_CHARGER_ENABLE, "GPIO_CHARGER_ENABLE"))   {  
  27.         printk(KERN_ERR "%s :GPIO_CHARGER_ENABLE request port error!\n", __func__);  
  28.         goto err_gpio_failed;  
  29.     } else {  
  30.         s3c_gpio_setpull(GPIO_CHARGER_ENABLE, S3C_GPIO_PULL_NONE);  
  31.         s3c_gpio_cfgpin(GPIO_CHARGER_ENABLE, S3C_GPIO_SFN(1));  
  32.         gpio_direction_output(GPIO_CHARGER_ENABLE, 0);  
  33.         gpio_free(GPIO_CHARGER_ENABLE);  
  34.     }  
  35.   
  36.     if (gpio_request(GPIO_PM2301_LP, "GPIO_PM2301_LP")) {  
  37.         printk(KERN_ERR "%s :GPIO_PM2301_LP request port error!\n", __func__);  
  38.         goto err_gpio_failed;  
  39.     } else {  
  40.         s3c_gpio_setpull(GPIO_PM2301_LP, S3C_GPIO_PULL_NONE);  
  41.         s3c_gpio_cfgpin(GPIO_PM2301_LP, S3C_GPIO_SFN(1));  
  42.         gpio_direction_output(GPIO_PM2301_LP, 1);  
  43.         gpio_free(GPIO_PM2301_LP);  
  44.     }  
  45.   
  46.     return 1;  
  47.   
  48. err_gpio_failed:  
  49.     return 0;  
  50. }  

2)、結構體初始化

Include/linux/pm2301_charger.h

  1. #define GPIO_CHARGER_ONLINE     EXYNOS4_GPX0(7)//對應控制腳的主控介面  
  2. #define GPIO_CHARGER_STATUS     EXYNOS4_GPX1(3)  
  3. #define GPIO_CHARGER_ENABLE     EXYNOS4_GPL2(0)  
  4. #define GPIO_PM2301_LP          EXYNOS4_GPX1(7)  
  5. struct pm2301_platform_data {  
  6.     int (*hw_init)(void);  
  7.     int gpio_enn;  
  8.     int gpio_wakeup;  
  9.     int gpio_irq;  
  10.     int gpio_lpn;  
  11. };  
  12. extern int pm2301_get_online(void);  
  13. extern int pm2301_get_status(void);  

3、probe函式分析

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

  1. static int __devinit pm2301_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 pm2301_chip *chip;  
  6.     int ret;  
  7.     printk(KERN_INFO "PM2301 probe !!\n");  
  8. //(1)、前面這部分是對IIC的初始化;  
  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.     chip->client = client;  
  19.     chip->pdata = client->dev.platform_data;  
  20.     i2c_set_clientdata(client, chip);  
  21.   
  22.     /* Hardware Init for PM2301 */  
  23.     if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {  
  24.         dev_err(&client->dev, "hardware initial failed.\n");  
  25.         goto err_hw_failed;  
  26.     }  
  27.   
  28.     mutex_init(&i2c_lock);  
  29. //(2)、初始化兩個佇列  
  30.     INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);  
  31.     INIT_DELAYED_WORK_DEFERRABLE(&chip->work_status, pm2301_ststus_work);  
  32. //(3)、中斷執行緒化  
  33.     chip->irq_online = gpio_to_irq(chip->pdata->gpio_wakeup);  
  34.     chip->irq_status = gpio_to_irq(chip->pdata->gpio_irq);  
  35.     /* Request IRQ for PM2301 */  
  36.     ret = request_threaded_irq(chip->irq_online,  
  37.                                NULL, pm2301_dcin,  
  38.                                IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,  
  39.                                "PM2301 DC IN", chip);  
  40.   
  41.     if (ret) {  
  42.         printk(KERN_ERR "Cannot request irq %d for DC (%d)\n",  
  43.                chip->irq_online, ret);  
  44.         goto err_hw_failed;  
  45.     }  
  46.   
  47. #ifdef PM2301_REPORT_STATUS_BY_IRQ  
  48.   
  49.     ret = request_threaded_irq(chip->irq_status,  
  50.                                NULL, pm2301_status,  
  51.                                IRQF_TRIGGER_FALLING,  
  52.                                "PM2301 STATUS", chip);  
  53.   
  54.     if (ret) {  
  55.         printk(KERN_ERR "Cannot request irq %d for CHARGE STATUS (%d)\n",  
  56.                chip->irq_status, ret);  
  57.         goto err_hw_failed;  
  58.     }  
  59. #endif  
  60.   
  61.   
  62.     charger_initial = 1;  
  63.     g_has_charged = 0;  
  64.     g_has_charging_full_or_stop = 0;  
  65.   
  66. #ifdef PM2301_REPORT_STATUS_BY_IRQ  
  67.     /* Set wakeup source for online pin*/  
  68.     irq_set_irq_wake(chip->irq_status, 1);  
  69. #endif  
  70.     /* Set wakeup source for online pin*/  
  71.     irq_set_irq_wake(chip->irq_online, 1);  
  72.   
  73.     /* Init default interrupt route for PM2301 */  
  74.     pm2301_reg_init(chip->client);  
  75.     /* Init online & status value */  
  76.     chip->online = pm2301_charger_online(chip);  
  77.     g_pm2301_online = chip->online;  /* Sync to global */  
  78.     pm2301_charger_enable(chip->client, chip->online);  
  79.     pm2301_charger_status(chip);  
  80.   
  81.     printk(KERN_INFO "PM2301 probe success!!\n");  
  82.     return 0;  
  83. err_hw_failed:  
  84.     dev_err(&client->dev, "failed: power supply register\n");  
  85.     i2c_set_clientdata(client, NULL);  
  86.     kfree(chip);  
  87.     return ret;  
  88. }  

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

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

(2)、任務初始化巨集

  1. INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);  
  2. 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 中,中斷具有最高的優先順序。不論在任何時刻,只要產生中斷事件,核心將立即執行相應的中斷處理程式,等到所有掛起的中斷和軟中斷處理完畢後才能執行正常的任務,因此有可能造成實時任務得不到及時的處理。中斷執行緒化之後,中斷將作為核心執行緒執行而且被賦予不同的實時優先順序,實時任務可以有比中斷執行緒更高的優先順序。這樣,具有最高優先順序的實時任務就能得到優先處理,即使在嚴重負載下仍有實時性保證。但是,並不是所有的中斷都可以被執行緒化,比如時鐘中斷,主要用來維護系統時間以及定時器等,其中定時器是作業系統的脈搏,一旦被執行緒化,就有可能被掛起,這樣後果將不堪設想,所以不應當被執行緒化。 

看下我們程式中如何把中斷執行緒化的:

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

看到這裡是否想起:

  1. static struct pm2301_platform_data pm2301_platform_data = {  
  2.     ………………  
  3.     .gpio_lpn = GPIO_PM2301_LP,  
  4.     .gpio_irq = GPIO_CHARGER_STATUS,  
  5.     .gpio_enn = GPIO_CHARGER_ENABLE,  
  6.     .gpio_wakeup = GPIO_CHARGER_ONLINE,  
  7. };  
  8.   
  9. #define GPIO_CHARGER_ONLINE     EXYNOS4_GPX0(7)  
  10. #define GPIO_CHARGER_STATUS     EXYNOS4_GPX1(3)  
  11. #define GPIO_CHARGER_ENABLE         EXYNOS4_GPL2(0)  
  12. #define GPIO_PM2301_LP          EXYNOS4_GPX1(7)  

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

中斷執行緒化:

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

當有插入DC中斷出發時呼叫:

  1. static irqreturn_t pm2301_dcin(int irq, void *_data)  
  2. {  
  3.     struct pm2301_chip *chip = _data;  
  4.     schedule_delayed_work(&chip->work_online, PM2301_DELAY);  
  5.     return IRQ_HANDLED;  
  6. }  

Pm2301_dcin排程佇列:chip->work_online執行:pm2301_online_work函式

  1. static void pm2301_online_work(struct work_struct *work)  
  2. {  
  3.     struct pm2301_chip *chip;  
  4.     chip = container_of(work, struct pm2301_chip, work_online.work);  
  5.     int new_online = pm2301_charger_online(chip);  
  6.   
  7.     if (chip->online != new_online) {  
  8.         chip->online = new_online;  
  9.         g_pm2301_online = chip->online;  /* Sync to global */  
  10.         pm2301_charger_enable(chip->client, chip->online);//①、初始化充電IC;  
  11. #ifdef PM2301_REPORT_STATUS_BY_IRQ  
  12.   
  13.         /*To avoid status pin keep low*/  
  14.         schedule_delayed_work(&chip->work_status, 1000);  
  15. #endif  
  16. #if defined(CONFIG_BATTERY_MAX17040)  
  17.         TriggerGasgaugeUpdate();//②、把DC狀態更新到max17040;  
  18. #endif  
  19.     }  
  20. }  

①、初始化電IC

這裡面主要是寫一些暫存器

  1. static void pm2301_charger_enable(struct i2c_client *client, int online)  
  2. {  
  3.     if (online) {   /* Enabled Charging*/  
  4.         int batt_capacity = 0;  
  5.         batt_capacity = GetGasgaugeCapacity();  
  6.         /* Don't start charging if battery capacity above 95% when DC plug in*/  
  7.         if(0) {  
  8.         //if( batt_capacity >= 95 ) {  
  9.             pm2301_write_reg(client, 0x01, 0x02);  
  10.             pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */  
  11.         } else {  
  12.             pm2301_write_reg(client, 0x00, 0x01);   /* force resume of charging */  
  13.             pm2301_write_reg(client, 0x01, 0x06);   /* ChEn=1, AutoResume=1 */  
  14.             pm2301_write_reg(client, 0x05, 0x7A);   /* ChEoccurrentLevel:150mA, ChPrechcurrentLevel:100mA, ChCCcurrentLevel:1000mA/2000mA */  
  15.             pm2301_write_reg(client, 0x06, 0x0A);   /* ChVersumeVot:3.6V ChPrechVoltLevel:2.9V */  
  16.             pm2301_write_reg(client, 0x07, 0x1E);   /* ChVoltLevel:4.25V */  
  17.             pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */  
  18.         }  
  19.         g_has_charged = 1;  
  20.     } else {    /* Disable Charging*/  
  21.         pm2301_write_reg(client, 0x01, 0x02);  
  22.         pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */  
  23.         g_has_charged = 0;  
  24.     }  
  25. }  

②、把DC狀態更新到max17040

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


相關文章