Keywords: Android battery meter pl2301 task initialization macro Power_Supply interrupt thread
Platform information:
Kernel: linux2.6/linux3.0
System: Android/android4.0
Platform: Samsung exynos 4210, exynos 4412, and exynos 5250
Author: xubin341719 (You are welcome to reprint it. Please note the author)
Correct the mistakes and learn and make progress together !!
Download the complete driver code and specifications: max17040_pl2301
Android battery (I): Basic Principles of Lithium Battery
Android battery (2): Charging Process for Android shutdown and display of charging Screen
Android battery (III): Android battery system
Android battery (4): Battery meter (max17040) Driver Analysis
Android battery (5): Analysis of the battery charging IC (pm2301) Driver
Android charging, some power management chips include Charging management, such as the at8937 commonly used on s5pv210. The max77686 we used this time has no charging control, so we added a charging IC to control it. We chose pm2301.
I. pm2301 and main control and battery Logic
As shown in:
1. Blue part: IIC control interface,This is too much. Many peripheral devices are controlled by IIC. Therefore, you must be familiar with, be familiar with, and complete a lot of work.
2. Yellow part: interrupt, enable control foot,Chg_status (IRQ), dc_in_int (wake_up), pm2301_lp (LPN), charger_en (ENN) control pins;
IRQ: status of the charging IC. If there is any action to notify the master;
Wake_up: If DC insertion occurs, the master of the interrupt notification is generated;
LPN:
ENN: Enable the charging IC;
3. General logic of pm2301, battery, and system voltage
1: The system voltage is provided by pm2301;
Label 2: pm2301 is used to charge the battery;
Label 3: The system voltage is provided by a battery;
Labels: 1 and 3: the voltage is not provided to the system at the same time. There is a MOS tube switch in the middle. There are two situations:
(1) When no charger is inserted, a battery is provided to the system, and the channel number is: 3. power supply to the system;
(2) After DC is inserted, the system detects DC insertion, closes Channel 3, enables Channel 1 to power the system, and 2 to charge the battery;
Ii. pm2301 hardware circuit
As follows:
The MOs in Q5 are controlled by the system power supply. When there is no charging, Vbatt is provided by vbat +, and Vbatt is provided by sense_comm when charging.
The control pin corresponds to the master pin:
IIC |
The iic id is 2. |
Chg_status (IRQ) |
Exynos4_gpx1 (3) |
Dc_in_int (wake_up) |
Exynos4_gpx0 (7) |
Pm2301_lp (LPN) |
Exynos4_gpx1 (7) |
Charger_en (ENN) |
Exynos4_gpl2 (0) |
For the reference circuit solution of pm2301, we also see that P1 controls the switching control of the vsystem power supply.
Process control for the entire battery charging:
Trickle mode, constant current mode (CC mode or fast charge mode), constant voltage mode (CV mode), end of charge feature
3. Driver section of pl2301
The hardware and working principles of pl2301 are briefly explained. Next we will analyze the driver:
Knowledge points used for driver:
IIC registration;
Task initialization macro (we mentioned it in the previous article );
Thread interruption;
1. IIC Registration
This is similar to the electric meter mentioned in the previous article;
(1) pm2301 driver
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) platform-driven
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…………};
Is the file generated by the IIC driver registration;
/Sys/bus/I2C/Drivers/pm2301
2. About the structure pm2301_platform_data
Static struct pm2301_platform_data pm2301_platform_data = {. hw_init = pm2301_hw_init, // (1), hardware interface initialization ;. gpio_lpn = gpio_pm2301_lp, // (2), struct initialization ;. gpio_irq = gpio_charger_status ,. gpio_enn = gpio_charger_enable ,. gpio_wakeup = gpio_charger_online ,};
ARCH/ARM/Mach-exynos/mach-smdk4x12.c
(1) hardware interface Initialization
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) structure Initialization
Include/Linux/pm2301_charger.h
# Define controls (7) // the control interface corresponding to the control script # define controls (3) # define gpio_charger_enable exynos4_gpl2 (0) # define gpio_pm2301_lp exynos4_gpx1 (7) struct detail {int (* hw_init) (void); int gpio_enn; int kernel; int gpio_irq; int gpio_lpn ;}; extern int pm2301_get_online (void); extern int pm2301_get_status );
3. Probe Function Analysis
If you are a beginner, you are recommended to read more programs. You will find that the driver formats are mostly the same, such as the IIC devices, queues, timers, and other things.
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). The previous part is the initialization of IIC; If (! I2c_check_functionality (adapter, i2c_func_smbus_byte) Return-EIO; chip = kzarloc (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) initialize two queues: init_delayed_work_deferrable (& Chip-> work_online, pm230w.online_work ); abort (& Chip-> work_status, pm2301_ststus_work); // (3) interrupt thread-based chip-> irq_online = gpio_to_irq (chip-> pdata-> gpio_wakeup ); chip-> irq_status = gpio_to_irq (chip-> pdata-> gpio_irq);/* requ Est IRQ for pm2301 */ret = request_threaded_irq (chip-> irq_online, null, pm2301_dcin, temperature | ir1__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_irqret = request_threaded_irq (chip-> irq_status, null, pm2301_status, ir1__trigger_falling, "pm2301 status", CH IP); If (RET) {printk (kern_err "cannot request IRQ % d for charge status (% d) \ n", chip-> irq_status, RET); goto err_hw_failed ;} # endifcharger_initial = 1; g_has_charged = 0; encoding = 0; # ifdef restart/* 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 def Ault 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) the previous part is the initialization of the IIC.
I will not talk about this part anymore. It's just like this;
(2) task initialization macro
INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);INIT_DELAYED_WORK_DEFERRABLE(&chip->work_status, pm2301_ststus_work);
Add pm2301_online_work to the chip-> work_online and pm2301_ststus_work queues.
(3) thread interruption: request_threaded_irq
Why thread interruption?
In Linux, interruption has the highest priority. No matter at any time, as long as an interrupt event occurs, the kernel will immediately execute the corresponding interrupt processing program and wait until all pending interruptions and soft interruptions are processed, therefore, real-time tasks may not be processed in a timely manner. After the thread is interrupted, the interrupt will run as the kernel thread and be given different real-time priorities. The real-time task can have a higher priority than the interrupt thread. In this way, real-time tasks with the highest priority can be processed first, even under severe loads, real-time performance is guaranteed. However, not all interruptions can be made thread-based, such as clock interruption, which is mainly used to maintain the system time and timer. The timer is the pulse of the operating system, it may be suspended, and the consequences will be unimaginable, so it should not be threaded.
Let's take a look at how we thread the interrupt in our program:
chip->irq_online = gpio_to_irq(chip->pdata->gpio_wakeup);chip->irq_status = gpio_to_irq(chip->pdata->gpio_irq);
Do you think of it here:
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_ONLINEEXYNOS4_GPX0(7)#define GPIO_CHARGER_STATUSEXYNOS4_GPX1(3)#define GPIO_CHARGER_ENABLE EXYNOS4_GPL2(0)#define GPIO_PM2301_LP EXYNOS4_GPX1(7)
I feel it is a little difficult to apply for a broken foot;
Thread interruption:
/* Request IRQ for PM2301 */ret = request_threaded_irq(chip->irq_online, NULL, pm2301_dcin, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "PM2301 DC IN", chip);
Called when a DC interrupt is inserted:
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 scheduling queue: Chip-> work_online execution: pm2301_online_work Function
Static void Merge (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 ); // ① initialize the charging IC; # ifdef success/* to avoid status pin keep low */schedule_delayed_work (& Chip-> work_status, 1000); # endif # If defined (config_battery_max17040) triggergasgaugeupdate (); // ② update the DC status to max17040; # endif }}
① Initialize an electronic IC
Some registers are written here.
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;}}
② Update the DC status to max17040
TriggerGasgaugeUpdate()
The process of inserting DC is as follows: