zoukankan      html  css  js  c++  java
  • 充电clientandroid电池(五):电池 充电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和主控、电池的逻辑

        如下图所示:

        充电和client

        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硬件电路

        如下所示:

        充电和client

        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电源部份的切换控制。

        充电和client

        下图为全部电池充电的过程控制:

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

        充电和client

        三、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

        充电和client

        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这部流程如下:

        充电和client

    文章结束给大家分享下程序员的一些笑话语录: IBM和波音777
      波音777是有史以来第一架完全在电脑虚拟现实中设计制造的飞机,所用的设备完全由IBM公司所提供。试飞前,波音公司的总裁非常热情的邀请IBM的技术主管去参加试飞,可那位主管却说道:“啊,非常荣幸,可惜那天是我妻子的生日,So..”..
      波音公司的总载一听就生气了:“胆小鬼,我还没告诉你试飞的日期呢!”

    --------------------------------- 原创文章 By
    充电和client
    ---------------------------------

  • 相关阅读:
    安卓查询当前所在地天气及查询地区(城市)代码cityCode localCode
    文件读写IO
    【桌面虚拟化】之三 Persistent vs NonP
    Android软件开发之常用系统控件界面整理
    Spring 3.x企业应用开发实战(11)----基于@AspectJ配置切面
    Java之Static静态修饰符详解
    OWASP
    2001中美黑客大战
    能"干掉"苹果的中国"黑客"
    Roy Li的学习和成长自传
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3097740.html
Copyright © 2011-2022 走看看