1265 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1265 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | |
| 
 | |
| Copyright (c) 2010 High Tech Computer Corporation
 | |
| 
 | |
| Module Name:
 | |
| 
 | |
| 		ds2746_battery.c
 | |
| 
 | |
| Abstract:
 | |
| 
 | |
| 		This module implements the power algorithm, including below concepts:
 | |
| 		1. Charging function control.
 | |
| 		2. Charging full condition.
 | |
| 		3. Recharge control.
 | |
| 		4. Battery capacity maintainance.
 | |
| 		5. Battery full capacity calibration.
 | |
| 
 | |
| Original Auther:
 | |
| 
 | |
| 		Andy.YS Wang  June-01-2010
 | |
| ---------------------------------------------------------------------------------*/
 | |
| #include <linux/module.h>
 | |
| #include <linux/param.h>
 | |
| #include <linux/jiffies.h>
 | |
| #include <linux/workqueue.h>
 | |
| #include <linux/pm.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/android_alarm.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/err.h>
 | |
| #include <linux/wakelock.h>
 | |
| #include <asm/gpio.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/ds2746_battery.h>
 | |
| #include <linux/ds2746_battery_config.h>
 | |
| #include <linux/ds2746_param.h>
 | |
| /*#include <linux/ds2746_param_config.h>*/
 | |
| #include <linux/wrapper_types.h>
 | |
| #include <linux/smb329.h>
 | |
| #include <mach/htc_battery.h>
 | |
| #include <asm/mach-types.h>
 | |
| #include "../../arch/arm/mach-msm/proc_comm.h"
 | |
| #include <linux/i2c.h>  					/* for i2c_adapter, i2c_client define*/
 | |
| /*#include "../w1/w1.h"*/
 | |
| /*#include "../w1/slaves/w1_ds2784.h"*/
 | |
| #include <linux/time.h>
 | |
| #include <linux/rtc.h>
 | |
| 
 | |
| struct ds2746_device_info {
 | |
| 
 | |
| 		struct device *dev;
 | |
| 		struct device *w1_dev;
 | |
| 		struct workqueue_struct *monitor_wqueue;
 | |
| 		struct work_struct monitor_work;
 | |
| 		/* lock to protect the battery info */
 | |
| 		struct mutex lock;
 | |
| 		/* DS2784 data, valid after calling ds2784_battery_read_status() */
 | |
| 		unsigned long update_time;	/* jiffies when data read */
 | |
| 		struct alarm alarm;
 | |
| 		struct wake_lock work_wake_lock;
 | |
| 		u8 slow_poll;
 | |
| 		ktime_t last_poll;
 | |
| };
 | |
| static struct wake_lock vbus_wake_lock;
 | |
| 
 | |
| /*========================================================================================
 | |
| 
 | |
| HTC power algorithm helper member and functions
 | |
| 
 | |
| ========================================================================================*/
 | |
| 
 | |
| static struct poweralg_type poweralg = {0};
 | |
| static struct poweralg_config_type config = {0};
 | |
| static struct poweralg_config_type debug_config = {0};
 | |
| 
 | |
| #define FAST_POLL	(1 * 60)
 | |
| #define SLOW_POLL	(10 * 60)
 | |
| #define PREDIC_POLL	20
 | |
| 
 | |
| #define SOURCE_NONE	0
 | |
| #define SOURCE_USB	1
 | |
| #define SOURCE_AC	2
 | |
| 
 | |
| #define CHARGE_OFF	0
 | |
| #define CHARGE_SLOW	1
 | |
| #define CHARGE_FAST	2
 | |
| 
 | |
| #define BATTERY_ID_UNKNOWN 0
 | |
| #define HTC_BATTERY_DS2746_DEBUG_ENABLE 0
 | |
| 
 | |
| /* DS2746 I2C BUS*/
 | |
| #define DS2746_I2C_BUS_ID   	0
 | |
| #define DS2746_I2C_SLAVE_ADDR   0x26
 | |
| 
 | |
| /*========================================================================================
 | |
| 
 | |
| IC dependent defines
 | |
| 
 | |
| ========================================================================================*/
 | |
| 
 | |
| /* DS2746 I2C register address*/
 | |
| #define DS2746_STATUS_REG	0x01
 | |
| #define DS2746_AUX0_MSB		0x08
 | |
| #define DS2746_AUX0_LSB 	0x09
 | |
| #define DS2746_AUX1_MSB 	0x0A
 | |
| #define DS2746_AUX1_LSB 	0x0B
 | |
| #define DS2746_VOLT_MSB 	0x0C
 | |
| #define DS2746_VOLT_LSB 	0x0D
 | |
| #define DS2746_CURRENT_MSB	0x0E
 | |
| #define DS2746_CURRENT_LSB	0x0F
 | |
| #define DS2746_ACR_MSB  	0x10
 | |
| #define DS2746_ACR_LSB  	0x11
 | |
| 
 | |
| /* DS2746 I2C I/O*/
 | |
| static struct i2c_adapter *i2c2 = NULL;
 | |
| static struct i2c_client *ds2746_i2c = NULL;
 | |
| static int htc_battery_initial = 0;
 | |
| 
 | |
| int ds2746_i2c_write_u8(u8 value, u8 reg)
 | |
| {
 | |
| 	int ret;
 | |
| 	u8 buf[2];
 | |
| 	struct i2c_msg *msg;
 | |
| 	struct i2c_msg xfer_msg[1];
 | |
| 
 | |
| 	/* [MSG1] fill the register address data and fill the data Tx buffer */
 | |
| 	msg = &xfer_msg[0];
 | |
| 	msg->addr = ds2746_i2c->addr;
 | |
| 	msg->len = 2;
 | |
| 	msg->flags = 0; 		/* Read the register value */
 | |
| 	msg->buf = buf;
 | |
| 
 | |
| 	buf[0] = reg;
 | |
| 	buf[1] = value;
 | |
| 
 | |
| 	ret = i2c_transfer(ds2746_i2c->adapter, xfer_msg, 1);
 | |
| 	if (ret <= 0){
 | |
| 		printk(DRIVER_ZONE "[%s] fail.\n", __func__);
 | |
| 	}
 | |
| 
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	printk(DRIVER_ZONE "[%s] ds2746[0x%x]<-0x%x.\n", __func__, reg, value);
 | |
| #endif
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int ds2746_i2c_read_u8(u8 *value, u8 reg)
 | |
| {
 | |
| 	int ret;
 | |
| 	struct i2c_msg *msg;
 | |
| 	struct i2c_msg xfer_msg[2];
 | |
| 
 | |
| 	/* [MSG1] fill the register address data */
 | |
| 	msg = &xfer_msg[0];
 | |
| 	msg->addr = ds2746_i2c->addr;
 | |
| 	msg->len = 1;
 | |
| 	msg->flags = 0; 		/* Read the register value */
 | |
| 	msg->buf = ®
 | |
| 	/* [MSG2] fill the data rx buffer */
 | |
| 	msg = &xfer_msg[1];
 | |
| 	msg->addr = ds2746_i2c->addr;
 | |
| 	msg->len = 1;
 | |
| 	msg->flags = I2C_M_RD;  /* Read the register value */
 | |
| 	msg->buf = value;
 | |
| 
 | |
| 	ret = i2c_transfer(ds2746_i2c->adapter, xfer_msg, 2);
 | |
| 	if (ret <= 0){
 | |
| 		printk(DRIVER_ZONE "[%s] fail.\n", __func__);
 | |
| 	}
 | |
| 
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	printk(DRIVER_ZONE "[%s] ds2746[0x%x]=0x%x.\n", __func__, reg, *value);
 | |
| #endif
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void ds2746_i2c_exit(void)
 | |
| {
 | |
| 	if (ds2746_i2c != NULL){
 | |
| 		kfree(ds2746_i2c);
 | |
| 		ds2746_i2c = NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (i2c2 != NULL){
 | |
| 		i2c_put_adapter(i2c2);
 | |
| 		i2c2 = NULL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int ds2746_i2c_init(void)
 | |
| {
 | |
| 	i2c2 = i2c_get_adapter(DS2746_I2C_BUS_ID);
 | |
| 	ds2746_i2c = kzalloc(sizeof(*ds2746_i2c), GFP_KERNEL);
 | |
| 
 | |
| 	if (i2c2 == NULL || ds2746_i2c == NULL){
 | |
| 		printk(DRIVER_ZONE "[%s] fail (0x%x, 0x%x).\n",
 | |
| 			__func__,
 | |
| 			(int) i2c2,
 | |
| 			(int) ds2746_i2c);
 | |
| 		ds2746_i2c_exit();
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	ds2746_i2c->adapter = i2c2;
 | |
| 	ds2746_i2c->addr = DS2746_I2C_SLAVE_ADDR;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*========================================================================================
 | |
| 
 | |
| 	HTC supporting MFG testing member and functions
 | |
| 
 | |
| =========================================================================================*/
 | |
| 
 | |
| static BOOL b_is_charge_off_by_bounding = FALSE;
 | |
| static void bounding_fullly_charged_level(int upperbd)
 | |
| {
 | |
| 	static int pingpong = 1;
 | |
| 	int lowerbd;
 | |
| 	int current_level;
 | |
| 	b_is_charge_off_by_bounding = FALSE;
 | |
| 	if (upperbd <= 0)
 | |
| 		return; /* doesn't activated this function */
 | |
| 	lowerbd = upperbd - 5; /* 5% range */
 | |
| 
 | |
| 	if (lowerbd < 0)
 | |
| 		lowerbd = 0;
 | |
| 	current_level = CEILING(poweralg.capacity_01p, 10);
 | |
| 
 | |
| 	if (pingpong == 1 && upperbd <= current_level) {
 | |
| 		printk(DRIVER_ZONE "MFG: lowerbd=%d, upperbd=%d, current=%d, pingpong:1->0 turn off\n", lowerbd, upperbd, current_level);
 | |
| 		b_is_charge_off_by_bounding = TRUE;
 | |
| 		pingpong = 0;
 | |
| 	} else if (pingpong == 0 && lowerbd < current_level) {
 | |
| 		printk(DRIVER_ZONE "MFG: lowerbd=%d, upperbd=%d, current=%d, toward 0, turn off\n", lowerbd, upperbd, current_level);
 | |
| 		b_is_charge_off_by_bounding = TRUE;
 | |
| 	} else if (pingpong == 0 && current_level <= lowerbd) {
 | |
| 		printk(DRIVER_ZONE "MFG: lowerbd=%d, upperbd=%d, current=%d, pingpong:0->1 turn on\n", lowerbd, upperbd, current_level);
 | |
| 		pingpong = 1;
 | |
| 	} else {
 | |
| 		printk(DRIVER_ZONE "MFG: lowerbd=%d, upperbd=%d, current=%d, toward %d, turn on\n", lowerbd, upperbd, current_level, pingpong);
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| static BOOL is_charge_off_by_bounding_condition(void)
 | |
| {
 | |
| 	return b_is_charge_off_by_bounding;
 | |
| }
 | |
| 
 | |
| void calibrate_id_ohm(struct battery_type *battery)
 | |
| {
 | |
| 	if (!poweralg.charging_source || !poweralg.charging_enable){
 | |
| 		battery->id_ohm += 500; 		/* If device is in discharge mode, Rid=Rid_1 + 0.5Kohm*/
 | |
| 	}
 | |
| 	else if (poweralg.charging_source == 2 && battery->current_mA >= 400 && battery->id_ohm >= 1500){
 | |
| 		battery->id_ohm -= 1500;		/* If device is in charge mode and ISET=1 (charge current is <800mA), Rid=Rid_1 - 1.5Kohm*/
 | |
| 	}
 | |
| 	else if (battery->id_ohm >= 700){
 | |
| 		battery->id_ohm -= 700; 		/* If device is in charge mode and ISET=0 (charge current is <400mA), Rid=Rid_1 - 0.7Kohm*/
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static BOOL is_charging_avaiable(void)
 | |
| {
 | |
| 	if (poweralg.is_software_charger_timeout) return FALSE;
 | |
| 	if (!poweralg.protect_flags.is_charging_enable_available)return FALSE;
 | |
| 	if (!poweralg.is_cable_in) return FALSE;
 | |
| 	if (poweralg.charge_state == CHARGE_STATE_PENDING) return FALSE;
 | |
| 	if (poweralg.charge_state == CHARGE_STATE_FULL_PENDING)	return FALSE;
 | |
| 	if (poweralg.charge_state == CHARGE_STATE_PREDICTION) return FALSE;
 | |
| 	if (is_charge_off_by_bounding_condition()) return FALSE;
 | |
| 	return TRUE; /* CHARGE_STATE_UNKNOWN, SET_LED_BATTERY_CHARGING is available to be charged by default*/
 | |
| }
 | |
| 
 | |
| static BOOL is_high_current_charging_avaialable(void)
 | |
| {
 | |
| 	if (!poweralg.protect_flags.is_charging_high_current_avaialble)	return FALSE;
 | |
| 	//if (!poweralg.is_china_ac_in) return FALSE; /* allow high current charging on china chargers */
 | |
| 	if (poweralg.charge_state == CHARGE_STATE_UNKNOWN) return FALSE;
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static void update_next_charge_state(void)
 | |
| {
 | |
| 	static UINT32 count_charging_full_condition;
 | |
| 	static UINT32 count_charge_over_load;
 | |
| 	int next_charge_state;
 | |
| 	int i;
 | |
| 
 | |
| 	/*  unknown -> prediction -> unknown -> discharge/charging/pending
 | |
| 	charging -> full-wait-stable -> full-charging -> full-pending
 | |
| 	full-pending -> full-charging -> charging
 | |
| 	*(cable in group) -> discharge, charge-pending, dead
 | |
| 	*(cable out group), full-wait-stable, charge-pending, dead -> charging*/
 | |
| 
 | |
| 	for (i = 0; i < 25; i++) /* maximun 25 times state transition to prevent from busy loop; ideally the transition time shall be less than 5 times.*/
 | |
| 	{
 | |
| 		next_charge_state = poweralg.charge_state;
 | |
| 
 | |
| 		/* 0. enter prediction state or not*/
 | |
| 		if (poweralg.charge_state == CHARGE_STATE_UNKNOWN){
 | |
| 			if (poweralg.battery.is_power_on_reset || config.debug_always_predict){
 | |
| 				if (poweralg.protect_flags.is_battery_dead){
 | |
| 					/* keep poweralg.charge_state unchanged, set capacity to 0% directly*/
 | |
| 					printk(DRIVER_ZONE " dead battery, \
 | |
| 						p=0%%\n");
 | |
| 					poweralg.capacity_01p = 0;
 | |
| 					battery_capacity_update(&poweralg.battery, poweralg.capacity_01p);
 | |
| 
 | |
| 					poweralg.fst_discharge_capacity_01p = poweralg.capacity_01p;
 | |
| 					poweralg.fst_discharge_acr_mAh = poweralg.battery.charge_counter_mAh;
 | |
| 				}
 | |
| 				else{
 | |
| 					/* battery replaced, recalculate capacity based on battery voltage*/
 | |
| 					printk(DRIVER_ZONE " start predict discharge...\n");
 | |
| 					next_charge_state = CHARGE_STATE_PREDICTION;
 | |
| 				}
 | |
| 
 | |
| 				config.debug_always_predict = FALSE;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (next_charge_state == poweralg.charge_state){
 | |
| 			/*---------------------------------------------------------------------------------------------------*/
 | |
| 			/* 1. cable in group*/
 | |
| 			if (poweralg.charge_state == CHARGE_STATE_UNKNOWN ||
 | |
| 				poweralg.charge_state == CHARGE_STATE_CHARGING ||
 | |
| 				poweralg.charge_state == CHARGE_STATE_PENDING ||
 | |
| 				poweralg.charge_state == CHARGE_STATE_FULL_WAIT_STABLE ||
 | |
| 				poweralg.charge_state == CHARGE_STATE_FULL_CHARGING ||
 | |
| 				poweralg.charge_state == CHARGE_STATE_FULL_PENDING){
 | |
| 				if (!poweralg.is_cable_in){
 | |
| 					next_charge_state = CHARGE_STATE_DISCHARGE;
 | |
| 				}
 | |
| 				else if (!poweralg.protect_flags.is_charging_enable_available){
 | |
| 					next_charge_state = CHARGE_STATE_PENDING;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			/*---------------------------------------------------------------------------------------------------*/
 | |
| 			/* 2. cable out group*/
 | |
| 			if (poweralg.charge_state == CHARGE_STATE_UNKNOWN ||
 | |
| 				poweralg.charge_state == CHARGE_STATE_DISCHARGE){
 | |
| 				if (poweralg.is_cable_in){
 | |
| 					next_charge_state = CHARGE_STATE_CHARGING;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/*---------------------------------------------------------------------------------------------------*/
 | |
| 		/* 3. state handler/transition, if the charge state is not changed due to cable/protect flags*/
 | |
| 		if (next_charge_state == poweralg.charge_state){
 | |
| 			switch (poweralg.charge_state){
 | |
| 				case CHARGE_STATE_PREDICTION:
 | |
| 					{
 | |
| 						UINT32 end_time_ms = BAHW_MyGetMSecs();
 | |
| 
 | |
| 						if (end_time_ms - poweralg.state_start_time_ms >=
 | |
| 							config.predict_timeout_sec * 1000){
 | |
| 
 | |
| 							printk(DRIVER_ZONE "predict done [%d->%d]\n", poweralg.state_start_time_ms,
 | |
| 								end_time_ms);
 | |
| 							next_charge_state = CHARGE_STATE_UNKNOWN;
 | |
| 						}
 | |
| 					}
 | |
| 					break;
 | |
| 				case CHARGE_STATE_CHARGING:
 | |
| 					if (!poweralg.battery.is_power_on_reset){
 | |
| 						/* -> full-charging, pending, dead*/
 | |
| 						if (poweralg.capacity_01p > 990){
 | |
| 							/* only ever charge-full, the capacity can be larger than 99.0%*/
 | |
| 							next_charge_state = CHARGE_STATE_FULL_CHARGING;
 | |
| 						}
 | |
| 						else if (poweralg.battery.voltage_mV >= config.full_charging_mv &&
 | |
| 							poweralg.battery.current_mA >= 0 &&
 | |
| 							poweralg.battery.current_mA <= config.full_charging_ma){
 | |
| 							/* meet charge full terminate condition, check again*/
 | |
| 							next_charge_state = CHARGE_STATE_FULL_WAIT_STABLE;
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					if (poweralg.battery.current_mA <= 0){
 | |
| 						/* count_charge_over_load is 5 as max*/
 | |
| 						if (count_charge_over_load < 5)
 | |
| 							count_charge_over_load++;
 | |
| 						else
 | |
| 							poweralg.is_charge_over_load = TRUE;
 | |
| 					}
 | |
| 					else{
 | |
| 						count_charge_over_load = 0;
 | |
| 						poweralg.is_charge_over_load = FALSE;
 | |
| 					}
 | |
| 
 | |
| 					/* is_software_charger_timeout: only triggered when AC adapter in*/
 | |
| 					if (config.software_charger_timeout_sec && poweralg.is_china_ac_in){
 | |
| 						/* software charger timer is enabled; for AC charge only*/
 | |
| 						UINT32 end_time_ms = BAHW_MyGetMSecs();
 | |
| 
 | |
| 						if (end_time_ms - poweralg.state_start_time_ms >=
 | |
| 							config.software_charger_timeout_sec * 1000){
 | |
| 
 | |
| 							printk(DRIVER_ZONE "software charger timer timeout [%d->%d]\n",
 | |
| 								poweralg.state_start_time_ms,
 | |
| 								end_time_ms);
 | |
| 							poweralg.is_software_charger_timeout = TRUE;
 | |
| 						}
 | |
| 					}
 | |
| 					break;
 | |
| 				case CHARGE_STATE_FULL_WAIT_STABLE:
 | |
| 					{
 | |
| 						/* -> full-charging, pending, dead*/
 | |
| 						if (poweralg.battery.voltage_mV >= config.full_charging_mv &&
 | |
| 							poweralg.battery.current_mA >= 0 &&
 | |
| 							poweralg.battery.current_mA <= config.full_charging_ma){
 | |
| 
 | |
| 							count_charging_full_condition++;
 | |
| 						}
 | |
| 						else{
 | |
| 							count_charging_full_condition = 0;
 | |
| 							next_charge_state = CHARGE_STATE_CHARGING;
 | |
| 						}
 | |
| 
 | |
| 						if (count_charging_full_condition >= 3){
 | |
| 
 | |
| 							poweralg.capacity_01p = 1000;
 | |
| 							battery_capacity_update(&poweralg.battery, poweralg.capacity_01p);
 | |
| 
 | |
| 							next_charge_state = CHARGE_STATE_FULL_CHARGING;
 | |
| 						}
 | |
| 					}
 | |
| 					break;
 | |
| 				case CHARGE_STATE_FULL_CHARGING:
 | |
| 					{
 | |
| 						/* -> full-pending, charging*/
 | |
| 						UINT32 end_time_ms = BAHW_MyGetMSecs();
 | |
| 
 | |
| 						if (poweralg.battery.voltage_mV < config.voltage_exit_full_mv){
 | |
| 							if (poweralg.capacity_01p > 990)
 | |
| 								poweralg.capacity_01p = 990;
 | |
| 							next_charge_state = CHARGE_STATE_CHARGING;
 | |
| 						}
 | |
| 						else if (config.full_pending_ma != 0 &&
 | |
| 							poweralg.battery.current_mA >= 0 &&
 | |
| 							poweralg.battery.current_mA <= config.full_pending_ma){
 | |
| 
 | |
| 							printk(DRIVER_ZONE " charge-full pending(%dmA)(%d:%d)\n",
 | |
| 								poweralg.battery.current_mA,
 | |
| 								poweralg.state_start_time_ms,
 | |
| 								end_time_ms);
 | |
| 
 | |
| 							next_charge_state = CHARGE_STATE_FULL_PENDING;
 | |
| 						}
 | |
| 						else if (end_time_ms - poweralg.state_start_time_ms >=
 | |
| 							config.full_charging_timeout_sec * 1000){
 | |
| 
 | |
| 							printk(DRIVER_ZONE " charge-full (expect:%dsec)(%d:%d)\n",
 | |
| 								config.full_charging_timeout_sec,
 | |
| 								poweralg.state_start_time_ms,
 | |
| 								end_time_ms);
 | |
| 							next_charge_state = CHARGE_STATE_FULL_PENDING;
 | |
| 						}
 | |
| 					}
 | |
| 					break;
 | |
| 				case CHARGE_STATE_FULL_PENDING:
 | |
| 					if ((poweralg.battery.voltage_mV >= 0 &&
 | |
| 						poweralg.battery.voltage_mV < config.voltage_recharge_mv) ||
 | |
| 						(poweralg.battery.RARC_01p >= 0 &&
 | |
| 						poweralg.battery.RARC_01p <= config.capacity_recharge_p * 10)){
 | |
| 						/* -> full-charging*/
 | |
| 						next_charge_state = CHARGE_STATE_FULL_CHARGING;
 | |
| 					}
 | |
| 					break;
 | |
| 				case CHARGE_STATE_PENDING:
 | |
| 				case CHARGE_STATE_DISCHARGE:
 | |
| 					{
 | |
| 						UINT32 end_time_ms = BAHW_MyGetMSecs();
 | |
| 
 | |
| 						if (!poweralg.is_voltage_stable){
 | |
| 							if (end_time_ms - poweralg.state_start_time_ms >=
 | |
| 								config.wait_votlage_statble_sec * 1000){
 | |
| 
 | |
| 								printk(DRIVER_ZONE " voltage stable\n");
 | |
| 								poweralg.is_voltage_stable = TRUE;
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					if (poweralg.is_cable_in &&
 | |
| 						poweralg.protect_flags.is_charging_enable_available){
 | |
| 						/* -> charging*/
 | |
| 						next_charge_state = CHARGE_STATE_CHARGING;
 | |
| 					}
 | |
| 					break;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/*---------------------------------------------------------------------------------------------------*/
 | |
| 		/* 4. state transition*/
 | |
| 		if (next_charge_state != poweralg.charge_state){
 | |
| 			/* state exit*/
 | |
| 			switch (poweralg.charge_state){
 | |
| 				case CHARGE_STATE_UNKNOWN:
 | |
| 					poweralg.capacity_01p = poweralg.battery.RARC_01p;
 | |
| 					if (poweralg.capacity_01p > 990)
 | |
| 						poweralg.capacity_01p = 990;
 | |
| 					if (poweralg.capacity_01p < 0)
 | |
| 						poweralg.capacity_01p = 0;
 | |
| 
 | |
| 					poweralg.fst_discharge_capacity_01p = poweralg.capacity_01p;
 | |
| 					poweralg.fst_discharge_acr_mAh = poweralg.battery.charge_counter_mAh;
 | |
| 					break;
 | |
| 				case CHARGE_STATE_PREDICTION:
 | |
| 					battery_param_update(&poweralg.battery,
 | |
| 						&poweralg.protect_flags);
 | |
| 
 | |
| 					poweralg.capacity_01p = poweralg.battery.KADC_01p;
 | |
| 					if (poweralg.capacity_01p > 990)
 | |
| 						poweralg.capacity_01p = 990;
 | |
| 					if (poweralg.capacity_01p < 0)
 | |
| 						poweralg.capacity_01p = 0;
 | |
| 					battery_capacity_update(&poweralg.battery,
 | |
| 						poweralg.capacity_01p);
 | |
| 
 | |
| 					poweralg.fst_discharge_capacity_01p = poweralg.capacity_01p;
 | |
| 					poweralg.fst_discharge_acr_mAh = poweralg.battery.charge_counter_mAh;
 | |
| 					break;
 | |
| 			}
 | |
| 
 | |
| 			/* state init*/
 | |
| 			poweralg.state_start_time_ms = BAHW_MyGetMSecs();
 | |
| 
 | |
| 			switch (next_charge_state){
 | |
| 				case CHARGE_STATE_DISCHARGE:
 | |
| 				case CHARGE_STATE_PENDING:
 | |
| 					/*! star_lee 20100426 - always set ACR=FULL when discharge starts and ACR>FULL*/
 | |
| 					if (poweralg.battery.RARC_01p > 1000)
 | |
| 						battery_capacity_update(&poweralg.battery, 1000);
 | |
| 
 | |
| 					poweralg.is_need_calibrate_at_49p = TRUE;
 | |
| 					poweralg.is_need_calibrate_at_14p = TRUE;
 | |
| 					poweralg.fst_discharge_capacity_01p = poweralg.capacity_01p;
 | |
| 					poweralg.fst_discharge_acr_mAh = poweralg.battery.charge_counter_mAh;
 | |
| 					poweralg.is_voltage_stable = FALSE;
 | |
| 
 | |
| 					break;
 | |
| 				case CHARGE_STATE_CHARGING:
 | |
| 					poweralg.is_software_charger_timeout = FALSE;   /* reset software charger timer every time when charging re-starts*/
 | |
| 					poweralg.is_charge_over_load = FALSE;
 | |
| 					count_charge_over_load = 0;
 | |
| 					poweralg.battery.charge_full_real_mAh = poweralg.battery.charge_full_design_mAh;
 | |
| 					battery_capacity_update(&poweralg.battery, poweralg.capacity_01p);
 | |
| 					break;
 | |
| 				case CHARGE_STATE_FULL_WAIT_STABLE:
 | |
| 					/* set to 0 first; the cournter will be add to 1 soon in CHARGE_STATE_FULL_WAIT_STABLE state handler*/
 | |
| 					count_charging_full_condition = 0;
 | |
| 					break;
 | |
| 			}
 | |
| 
 | |
| 			printk(DRIVER_ZONE " state change(%d->%d), full count=%d, over load count=%d [%d]\n",
 | |
| 				poweralg.charge_state,
 | |
| 				next_charge_state,
 | |
| 				count_charging_full_condition,
 | |
| 				count_charge_over_load,
 | |
| 				poweralg.state_start_time_ms);
 | |
| 
 | |
| 			poweralg.charge_state = next_charge_state;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void __update_capacity(void)
 | |
| {
 | |
| 	INT32 next_capacity_01p;
 | |
| 
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	pr_info("ds2746_batt:__update_capacity start\n");
 | |
| #endif
 | |
| 	if (poweralg.charge_state == CHARGE_STATE_PREDICTION ||
 | |
| 		poweralg.charge_state == CHARGE_STATE_UNKNOWN){
 | |
| 
 | |
| 		/*! star_lee 20100429 - return 99%~25% when in prediction mode*/
 | |
| 		poweralg.capacity_01p = max(min(990, poweralg.battery.KADC_01p), 250);
 | |
| 		printk(DRIVER_ZONE "fake percentage (%d) during prediction.\n",
 | |
| 			poweralg.capacity_01p);
 | |
| 	}
 | |
| 	else if (poweralg.charge_state == CHARGE_STATE_FULL_CHARGING ||
 | |
| 		poweralg.charge_state == CHARGE_STATE_FULL_PENDING){
 | |
| 
 | |
| 		poweralg.capacity_01p = 1000;
 | |
| 	}
 | |
| 	else if (!is_charging_avaiable() && poweralg.is_voltage_stable){
 | |
| 		/* DISCHARGE ALG: capacity is based on KADC/RARC; only do this after cable in 3 minutes later*/
 | |
| 		if (poweralg.battery.KADC_01p <= 0){
 | |
| 			if (poweralg.capacity_01p > 0)
 | |
| 				poweralg.capacity_01p -= 10;
 | |
| 			if (poweralg.capacity_01p > 0){
 | |
| 				/* capacity is still not 0 when KADC is 0; record capacity for next boot time*/
 | |
| 				battery_capacity_update(&poweralg.battery, poweralg.capacity_01p);
 | |
| 			}
 | |
| 		}
 | |
| 		else{
 | |
| 			if ((config.enable_weight_percentage) && (poweralg.capacity_01p <150 ||
 | |
| 				poweralg.battery.RARC_01p> poweralg.battery.KADC_01p)){
 | |
| 
 | |
| #define Padc 200
 | |
| #define Pw   5
 | |
| /* 500=<W_KADC<=1000*/
 | |
| #define W_KADC(RARC, Percentage) Padc+(INT32)abs(RARC-Percentage)*Pw
 | |
| 				/*! star_lee 20100426 - W_KADC must be larger or equal to 0*/
 | |
| 				INT32 w_kadc = min(max(W_KADC(poweralg.battery.RARC_01p, poweralg.battery.KADC_01p), 0), 1000);
 | |
| 				INT32 w_rarc = 1000 - w_kadc;
 | |
| 				next_capacity_01p = (w_kadc * poweralg.battery.KADC_01p + w_rarc * poweralg.battery.RARC_01p)/1000;
 | |
| 			}
 | |
| 			else{
 | |
| 				next_capacity_01p = poweralg.battery.RARC_01p;
 | |
| 			}
 | |
| 
 | |
| 			if (next_capacity_01p > 1000)
 | |
| 				next_capacity_01p = 1000;
 | |
| 			if (next_capacity_01p < 0)
 | |
| 				next_capacity_01p = 0;
 | |
| 
 | |
| 			if (next_capacity_01p < poweralg.capacity_01p){
 | |
| 				poweralg.capacity_01p -= min(10, poweralg.capacity_01p-next_capacity_01p);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (config.enable_full_calibration){
 | |
| 			if (poweralg.is_need_calibrate_at_49p &&
 | |
| 				poweralg.capacity_01p <= 500 &&
 | |
| 				poweralg.fst_discharge_capacity_01p >= 600){
 | |
| 
 | |
| 				poweralg.battery.charge_full_real_mAh = (poweralg.fst_discharge_acr_mAh-poweralg.battery.charge_counter_mAh)*1000/
 | |
| 					(poweralg.fst_discharge_capacity_01p-poweralg.capacity_01p);
 | |
| 
 | |
| 				battery_capacity_update(&poweralg.battery, poweralg.capacity_01p);
 | |
| 
 | |
| 				poweralg.is_need_calibrate_at_49p = FALSE;
 | |
| 				poweralg.fst_discharge_capacity_01p = poweralg.capacity_01p;
 | |
| 				poweralg.fst_discharge_acr_mAh = poweralg.battery.charge_counter_mAh;
 | |
| 
 | |
| 				printk(DRIVER_ZONE " 1.full calibrate: full=%d\n",
 | |
| 					poweralg.battery.charge_full_real_mAh);
 | |
| 			}
 | |
| 			else if (poweralg.is_need_calibrate_at_14p &&
 | |
| 				poweralg.capacity_01p <= 150 &&
 | |
| 				poweralg.fst_discharge_capacity_01p >= 250){
 | |
| 				poweralg.battery.charge_full_real_mAh = (poweralg.fst_discharge_acr_mAh-poweralg.battery.charge_counter_mAh)*1000/
 | |
| 					(poweralg.fst_discharge_capacity_01p - poweralg.capacity_01p);
 | |
| 
 | |
| 				battery_capacity_update(&poweralg.battery, poweralg.capacity_01p);
 | |
| 
 | |
| 				poweralg.is_need_calibrate_at_14p = FALSE;
 | |
| 				poweralg.fst_discharge_capacity_01p = poweralg.capacity_01p;
 | |
| 				poweralg.fst_discharge_acr_mAh = poweralg.battery.charge_counter_mAh;
 | |
| 
 | |
| 				printk(DRIVER_ZONE " 2.full calibrate: full=%d\n",
 | |
| 					poweralg.battery.charge_full_real_mAh);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	else{
 | |
| 		/* CHARGE ALG: capacity is always based on ACR
 | |
| 		1. plus 1% as max when charge, if the orignal capacity is <= 99%, the result is no more than 99%
 | |
| 		2. minus 1% as max when discharge, not less than 0%*/
 | |
| 		next_capacity_01p = poweralg.battery.RARC_01p;
 | |
| 
 | |
| 		if (next_capacity_01p > 1000)
 | |
| 			next_capacity_01p = 1000;
 | |
| 		if (next_capacity_01p < 0)
 | |
| 			next_capacity_01p = 0;
 | |
| 
 | |
| 		if (next_capacity_01p > poweralg.capacity_01p){
 | |
| 			/* charge case*/
 | |
| 			next_capacity_01p = poweralg.capacity_01p + min(next_capacity_01p - poweralg.capacity_01p, 10);
 | |
| 			if (poweralg.capacity_01p > 990)
 | |
| 				poweralg.capacity_01p = next_capacity_01p;
 | |
| 			else
 | |
| 				poweralg.capacity_01p = min(next_capacity_01p, 990);
 | |
| 		}
 | |
| 		else if (next_capacity_01p < poweralg.capacity_01p){
 | |
| 			/* discharge case*/
 | |
| 			poweralg.capacity_01p -= min(poweralg.capacity_01p - next_capacity_01p, 10);
 | |
| 			if (poweralg.capacity_01p < 0)
 | |
| 				poweralg.capacity_01p = 0;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*========================================================================================
 | |
| 
 | |
| HTC power algorithm implemetation
 | |
| 
 | |
| ========================================================================================*/
 | |
| 
 | |
| int get_state_check_interval_min_sec(void)
 | |
| {
 | |
| 	/*the minimal check interval of each states in seconds
 | |
| 	reserve for change polling rate
 | |
| 	UINT32 elapse_time_ms = BAHW_MyGetMSecs() - poweralg.state_start_time_ms;
 | |
| 	   switch (poweralg.charge_state)
 | |
| 	   {
 | |
| 	   case CHARGE_STATE_FULL_WAIT_STABLE:
 | |
| 		   //! star_lee 20100429 - takes 30 seconds(10 seconds*3 times) to confirm charge full condition
 | |
| 		   return 10;
 | |
| 	   case CHARGE_STATE_PREDICTION:
 | |
| 		   return min(config.predict_timeout_sec, max((INT32)(config.predict_timeout_sec - elapse_time_ms/1000), (INT32)1));
 | |
| 	   default:
 | |
| 		   if ( BAHW_IsChargeSourceIn() )  return config.polling_time_in_charging_sec;
 | |
| 		   else 						   return config.polling_time_in_discharging_sec;
 | |
| 	   }
 | |
| 	*/
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| BOOL do_power_alg(BOOL is_event_triggered)
 | |
| {
 | |
| 	/* is_event_triggered - TRUE: handle event only, do not update capacity; FALSE; always update capacity*/
 | |
| 	static BOOL s_bFirstEntry = TRUE;
 | |
| 	static UINT32 s_pre_time_ms;
 | |
| 	static INT32 s_level;
 | |
| 
 | |
| 	UINT32 now_time_ms = BAHW_MyGetMSecs();
 | |
| 
 | |
| 	/*------------------------------------------------------
 | |
| 	1 get battery data and update charge state*/
 | |
| 	if (!battery_param_update(&poweralg.battery, &poweralg.protect_flags)){
 | |
| 		printk(DRIVER_ZONE "battery_param_update fail, please retry next time.\n");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	update_next_charge_state();
 | |
| 
 | |
| 	/*-----------------------------------------------------
 | |
| 	2 calculate battery capacity (predict if necessary)*/
 | |
| 	if (s_bFirstEntry || now_time_ms - s_pre_time_ms > 10000 || !is_event_triggered){
 | |
| 		/* DO not update capacity when plug/unplug cable less than 10 seconds*/
 | |
| 		__update_capacity();
 | |
| 
 | |
| 		s_bFirstEntry = FALSE;
 | |
| 		s_pre_time_ms = now_time_ms;
 | |
| 	}
 | |
| 
 | |
| 	if (config.debug_disable_shutdown){
 | |
| 		if (poweralg.capacity_01p <= 0){
 | |
| 			poweralg.capacity_01p = 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	s_level = CEILING(poweralg.capacity_01p, 10);
 | |
| 	if (CEILING(poweralg.last_capacity_01p, 10) != s_level ||
 | |
| 		poweralg.battery.last_temp_01c != poweralg.battery.temp_01c) {
 | |
| 
 | |
| 		poweralg.battery.last_temp_01c = poweralg.battery.temp_01c;
 | |
| 		poweralg.last_capacity_01p = poweralg.capacity_01p;
 | |
| 		ds2746_blocking_notify(DS2784_LEVEL_UPDATE, &s_level);
 | |
| 	}
 | |
| 
 | |
| 	bounding_fullly_charged_level(config.full_level);
 | |
| 
 | |
| 	/*------------------------------------------------------
 | |
| 	3 charging function change*/
 | |
| 	if (is_charging_avaiable()){
 | |
| 		if (is_high_current_charging_avaialable()){
 | |
| 			ds2746_charger_control(CHARGE_FAST);
 | |
| 		}
 | |
| 		else{
 | |
| 			ds2746_charger_control(CHARGE_SLOW);
 | |
| 		}
 | |
| 	}
 | |
| 	else{
 | |
| 		ds2746_charger_control(CHARGE_OFF);
 | |
| 	}
 | |
| 
 | |
| 	if (config.debug_disable_hw_timer && poweralg.is_charge_over_load){
 | |
| 		ds2746_charger_control(CHARGE_OFF);
 | |
| 		printk(DRIVER_ZONE "Toggle charger due to HW disable charger.\n");
 | |
| 	}
 | |
| 
 | |
| 	/*------------------------------------------------------
 | |
| 	 4 debug messages and update os battery status*/
 | |
| 
 | |
| 	/*powerlog_to_file(&poweralg);
 | |
| 	update_os_batt_status(&poweralg);*/
 | |
| 
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	printk(DRIVER_ZONE "[%d] P=%d cable=%d%d flags=%d%d%d debug=%d%d%d%d fst_discharge=%d/%d [%u]\n",
 | |
| 		poweralg.charge_state,
 | |
| 		poweralg.capacity_01p,
 | |
| 		poweralg.is_cable_in,
 | |
| 		poweralg.is_china_ac_in,
 | |
| 		poweralg.protect_flags.is_charging_enable_available,
 | |
| 		poweralg.protect_flags.is_charging_high_current_avaialble,
 | |
| 		poweralg.protect_flags.is_battery_dead,
 | |
| 		config.debug_disable_shutdown,
 | |
| 		config.debug_fake_room_temp,
 | |
| 		config.debug_disable_hw_timer,
 | |
| 		config.debug_always_predict,
 | |
| 		poweralg.fst_discharge_capacity_01p,
 | |
| 		poweralg.fst_discharge_acr_mAh,
 | |
| 		BAHW_MyGetMSecs());
 | |
| #endif
 | |
| 
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| void power_alg_init(struct poweralg_config_type *debug_config)
 | |
| {
 | |
| 	/*-------------------------------------------------------------
 | |
| 	1. setup default poweralg data*/
 | |
| 	poweralg.charge_state = CHARGE_STATE_UNKNOWN;
 | |
| 	poweralg.capacity_01p = 990;
 | |
| 	poweralg.last_capacity_01p = poweralg.capacity_01p;
 | |
| 	poweralg.fst_discharge_capacity_01p = 0;
 | |
| 	poweralg.fst_discharge_acr_mAh = 0;
 | |
| 	poweralg.is_need_calibrate_at_49p = TRUE;
 | |
| 	poweralg.is_need_calibrate_at_14p = TRUE;
 | |
| 	poweralg.is_charge_over_load = FALSE;
 | |
| 	poweralg.is_china_ac_in = FALSE;
 | |
| 	poweralg.is_cable_in = FALSE;
 | |
| 	poweralg.is_voltage_stable = FALSE;
 | |
| 	poweralg.is_software_charger_timeout = FALSE;
 | |
| 	poweralg.state_start_time_ms = 0;
 | |
| 
 | |
| 	if(get_cable_status() == SOURCE_USB)
 | |
| 	{
 | |
| 		poweralg.is_cable_in = TRUE;
 | |
| 		poweralg.charging_source = SOURCE_USB;
 | |
| 		ds2746_charger_control(CHARGE_SLOW);
 | |
| 	}
 | |
| 	else if (get_cable_status() == SOURCE_AC)
 | |
| 	{
 | |
| 		poweralg.is_cable_in = TRUE;
 | |
| 		poweralg.is_china_ac_in = TRUE;
 | |
| 		poweralg.charging_source = SOURCE_AC;
 | |
| 		ds2746_charger_control(CHARGE_FAST);
 | |
| 	} else{
 | |
| 		poweralg.charging_source = SOURCE_NONE;
 | |
| 	}
 | |
| 	/*-------------------------------------------------------------
 | |
| 	2. setup default config flags (board dependent)*/
 | |
| 	poweralg_config_init(&config);
 | |
| 
 | |
| 	if (debug_config){
 | |
| 		config.debug_disable_shutdown = debug_config->debug_disable_shutdown;
 | |
| 		config.debug_fake_room_temp = debug_config->debug_fake_room_temp;
 | |
| 		config.debug_disable_hw_timer = debug_config->debug_disable_hw_timer;
 | |
| 		config.debug_always_predict = debug_config->debug_always_predict;
 | |
| 	}
 | |
| 
 | |
| 	/* if ( BAHW_IsTestMode() )
 | |
| 	 {
 | |
| 		 config.debug_disable_shutdown = TRUE;
 | |
| 		 config.debug_fake_room_temp   = TRUE;
 | |
| 		 config.debug_disable_hw_timer = TRUE;
 | |
| 	 }*/
 | |
| 
 | |
| 	/*-------------------------------------------------------------
 | |
| 	3. setup default protect flags*/
 | |
| 	poweralg.protect_flags.is_charging_enable_available = TRUE;
 | |
| 	poweralg.protect_flags.is_battery_dead = FALSE;
 | |
| 	poweralg.protect_flags.is_charging_high_current_avaialble = FALSE;
 | |
| 	poweralg.protect_flags.is_fake_room_temp = config.debug_fake_room_temp;
 | |
| 
 | |
| 	/*-------------------------------------------------------------
 | |
| 	4. setup default battery structure*/
 | |
| 	battery_param_init(&poweralg.battery);
 | |
| 
 | |
| 	/*pr_info("power alg inited with board name <%s>\n", HTC_BATT_BOARD_NAME);*/
 | |
| }
 | |
| 
 | |
| void power_alg_preinit(void)
 | |
| {
 | |
| 	/* make sure cable and battery is in when off mode charging*/
 | |
| }
 | |
| 
 | |
| static BLOCKING_NOTIFIER_HEAD(ds2746_notifier_list);
 | |
| int ds2746_register_notifier(struct notifier_block *nb)
 | |
| {
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	pr_info("%s\n", __func__);
 | |
| #endif
 | |
| 	return blocking_notifier_chain_register(&ds2746_notifier_list, nb);
 | |
| }
 | |
| 
 | |
| int ds2746_unregister_notifier(struct notifier_block *nb)
 | |
| {
 | |
| 	return blocking_notifier_chain_unregister(&ds2746_notifier_list, nb);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ds2746_blocking_notify(unsigned long val, void *v)
 | |
| {
 | |
| 	int chg_ctl;
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	pr_info("%s\n", __func__);
 | |
| #endif
 | |
| 
 | |
| 	if (val == DS2784_CHARGING_CONTROL){
 | |
| 		chg_ctl = *(int *) v;
 | |
| 		if (machine_is_passionc()){
 | |
| 			pr_info("[ds2746_batt] Switch charging %d\n", chg_ctl);
 | |
| 			if (chg_ctl <= 2){
 | |
| 				gpio_direction_output(22, !(!!chg_ctl));/*PNC*/
 | |
| 				set_charger_ctrl(chg_ctl);
 | |
| 			}
 | |
| 			return 0;
 | |
| 		}
 | |
| 		else if (poweralg.battery.id_index != BATTERY_ID_UNKNOWN){
 | |
| 			/* only notify at changes */
 | |
| 			if (poweralg.charging_enable == chg_ctl)
 | |
| 				return 0;
 | |
| 			else
 | |
| 				poweralg.charging_enable = chg_ctl;
 | |
| 		}
 | |
| 		else{
 | |
| 			/* poweralg.charging_enable = DISABLE;
 | |
| 			v = DISABLE;
 | |
| 			pr_info("[HTC_BATT] Unknow battery\n");*/
 | |
| 			if (poweralg.charging_enable == chg_ctl)
 | |
| 				return 0;
 | |
| 			else
 | |
| 				poweralg.charging_enable = chg_ctl;
 | |
| 		}
 | |
| 	}
 | |
| 	return blocking_notifier_call_chain(&ds2746_notifier_list, val, v);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ds2746_get_battery_info(struct battery_info_reply *batt_info)
 | |
| {
 | |
| 	batt_info->batt_id = poweralg.battery.id_index; /*Mbat ID*/
 | |
| 	batt_info->batt_vol = poweralg.battery.voltage_mV; /*VMbat*/
 | |
| 	batt_info->batt_temp = poweralg.battery.temp_01c; /*Temperature*/
 | |
| 	batt_info->batt_current = poweralg.battery.current_mA; /*Current*/
 | |
| 	batt_info->level = CEILING(poweralg.capacity_01p, 10); /*last_show%*/
 | |
| 	batt_info->charging_source = poweralg.charging_source;
 | |
| 	batt_info->charging_enabled = poweralg.charging_enable;
 | |
| 	batt_info->full_bat = poweralg.battery.charge_full_real_mAh;
 | |
| 	return 0;
 | |
| }
 | |
| ssize_t htc_battery_show_attr(struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	int len = 0;
 | |
| 	pr_info("%s\n", __func__);
 | |
| 	if (!strcmp(attr->attr.name, "batt_attr_text")){
 | |
| 		len += scnprintf(buf +
 | |
| 				len,
 | |
| 				PAGE_SIZE -
 | |
| 				len,
 | |
| 				"Percentage(%%): %d;\n"
 | |
| 				"KADC(%%): %d;\n"
 | |
| 				"RARC(%%): %d;\n"
 | |
| 				"V_MBAT(mV): %d;\n"
 | |
| 				"Main_battery_ID(Kohm): %d;\n"
 | |
| 				"pd_M: %d;\n"
 | |
| 				"Current(mA): %d;\n"
 | |
| 				"Temp: %d;\n"
 | |
| 				"Charging_source: %d;\n"
 | |
| 				"ACR(mAh): %d;\n"
 | |
| 				"FULL(mAh): %d;\n"
 | |
| 				"1st_dis_percentage(%%): %d;\n"
 | |
| 				"1st_dis_ACR: %d;\n",
 | |
| 				CEILING(poweralg.capacity_01p, 10),
 | |
| 				CEILING(poweralg.battery.KADC_01p, 10),
 | |
| 				CEILING(poweralg.battery.RARC_01p, 10),
 | |
| 				poweralg.battery.voltage_mV,
 | |
| 				poweralg.battery.id_ohm,
 | |
| 				poweralg.battery.pd_m,
 | |
| 				poweralg.battery.current_mA,
 | |
| 				CEILING(poweralg.battery.temp_01c, 10),
 | |
| 				poweralg.charging_source,
 | |
| 				poweralg.battery.charge_counter_mAh,
 | |
| 				poweralg.battery.charge_full_real_mAh,
 | |
| 				CEILING(poweralg.fst_discharge_capacity_01p, 10),
 | |
| 				poweralg.fst_discharge_acr_mAh
 | |
| 		);
 | |
| 	}
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int cable_status_handler_func(struct notifier_block *nfb,
 | |
| 	unsigned long action, void *param)
 | |
| {
 | |
| 	u32 cable_type = (u32) action;
 | |
| 	pr_info("[ds2746_batt] cable change to %d\n", cable_type);
 | |
| 	/* When the cable plug out, reset all the related flag,
 | |
| 	Let algorithm machine to judge latest state */
 | |
| 	if (cable_type == 0){
 | |
| 		poweralg.is_cable_in = 0;
 | |
| 		poweralg.is_china_ac_in = 0;
 | |
| 		/*htc_batt_info.rep.OTP_Flag = 0;
 | |
| 		htc_batt_info.rep.charging_sts_flag = 0;
 | |
| 		htc_batt_info.full_charge_count = 0;*/
 | |
| 	}
 | |
| 	else if (cable_type == 1){
 | |
| 		poweralg.is_cable_in = 1;
 | |
| 		poweralg.is_china_ac_in = 0;
 | |
| 	}
 | |
| 	else if (cable_type == 2){
 | |
| 		poweralg.is_cable_in = 1;
 | |
| 		poweralg.is_china_ac_in = 1;
 | |
| 	}
 | |
| 	else if (cable_type == 0xff){
 | |
| 		if (param)
 | |
| 			config.full_level = *(INT32 *)param;
 | |
| 		pr_info("[ds2746_batt] Set the full level to %d\n", config.full_level);
 | |
| 		return NOTIFY_OK;
 | |
| 	}
 | |
| 	else if (cable_type == 0x10){
 | |
| 		poweralg.protect_flags.is_fake_room_temp = TRUE;
 | |
| 		pr_info("[ds2746_batt] enable fake temp mode\n");
 | |
| 		return NOTIFY_OK;
 | |
| 	}
 | |
| 
 | |
| 	if (cable_type <= 2){
 | |
| 		poweralg.charging_source = cable_type;
 | |
| 		ds2746_blocking_notify(DS2784_CHARGING_CONTROL,
 | |
| 			&poweralg.charging_source);
 | |
| 	}
 | |
| 	return NOTIFY_OK;
 | |
| }
 | |
| 
 | |
| static struct notifier_block cable_status_handler =
 | |
| {
 | |
|   .notifier_call = cable_status_handler_func,
 | |
| };
 | |
| 
 | |
| void ds2746_charger_control(int type)
 | |
| {
 | |
| 	int chg_ctl = DISABLE;
 | |
| 	int charge_type = type;
 | |
| 
 | |
| 	switch (charge_type){
 | |
| 		case CHARGE_OFF:
 | |
| 			/* CHARGER_EN is active low.  Set to 1 to disable. */
 | |
| 			chg_ctl = DISABLE;
 | |
| 			ds2746_blocking_notify(DS2784_CHARGING_CONTROL, &chg_ctl);
 | |
| 			/*if (temp >= TEMP_CRITICAL)
 | |
| 				pr_info("batt: charging OFF [OVERTEMP]\n");
 | |
| 			else if (htc_batt_info.rep.cooldown)
 | |
| 				pr_info("batt: charging OFF [COOLDOWN]\n");
 | |
| 			else if (htc_batt_info.rep.battery_full)
 | |
| 				pr_info("batt: charging OFF [FULL]\n");
 | |
| 			else*/
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 			pr_info("batt: charging OFF\n");
 | |
| #endif
 | |
| 			break;
 | |
| 		case CHARGE_SLOW:
 | |
| 			chg_ctl = ENABLE_SLOW_CHG;
 | |
| 			ds2746_blocking_notify(DS2784_CHARGING_CONTROL, &chg_ctl);
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 			pr_info("batt: charging SLOW\n");
 | |
| #endif 
 | |
| 			break;
 | |
| 		case CHARGE_FAST:
 | |
| 			chg_ctl = ENABLE_FAST_CHG;
 | |
| 			ds2746_blocking_notify(DS2784_CHARGING_CONTROL, &chg_ctl);
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 			pr_info("batt: charging FAST\n");
 | |
| #endif
 | |
| 			break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void ds2746_program_alarm(struct ds2746_device_info *di, int seconds)
 | |
| {
 | |
| 	ktime_t low_interval = ktime_set(seconds - 10, 0);
 | |
| 	ktime_t slack = ktime_set(20, 0);
 | |
| 	ktime_t next;
 | |
| 
 | |
| 	next = ktime_add(di->last_poll, low_interval);
 | |
| 
 | |
| 	alarm_start_range(&di->alarm, next, ktime_add(next, slack));
 | |
| }
 | |
| 
 | |
| static void ds2746_battery_work(struct work_struct *work)
 | |
| {
 | |
| 	if (!htc_battery_initial)    return;
 | |
| 	struct ds2746_device_info *di = container_of(work,
 | |
| 				struct ds2746_device_info, monitor_work);
 | |
| 	unsigned long flags;
 | |
| 
 | |
| #if HTC_BATTERY_DS2746_DEBUG_ENABLE
 | |
| 	pr_info("[ds2746_batt] ds2746_battery_work*\n");
 | |
| #endif
 | |
| 	do_power_alg(0);
 | |
| 	get_state_check_interval_min_sec();
 | |
| 	di->last_poll = alarm_get_elapsed_realtime();
 | |
| 
 | |
| 	/* prevent suspend before starting the alarm */
 | |
| 	local_irq_save(flags);
 | |
| 
 | |
| 	wake_unlock(&di->work_wake_lock);
 | |
| 	if (poweralg.battery.is_power_on_reset)
 | |
| 		ds2746_program_alarm(di, PREDIC_POLL);
 | |
| 	else
 | |
| 		ds2746_program_alarm(di, FAST_POLL);
 | |
| 
 | |
| 	local_irq_restore(flags);
 | |
| }
 | |
| 
 | |
| static void ds2746_battery_alarm(struct alarm *alarm)
 | |
| {
 | |
| 	if (!htc_battery_initial)    return;
 | |
| 	struct ds2746_device_info *di = container_of(alarm, struct ds2746_device_info, alarm);
 | |
| 	wake_lock(&di->work_wake_lock);
 | |
| 	queue_work(di->monitor_wqueue, &di->monitor_work);
 | |
| }
 | |
| 
 | |
| static int ds2746_battery_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	int rc;
 | |
| 	struct ds2746_device_info *di;
 | |
| 	struct ds2746_platform_data *pdata = pdev->dev.platform_data;
 | |
| 
 | |
| 	pr_info("[ds2746_batt] ds2746_battery_prob\n");
 | |
| 
 | |
| 	poweralg.battery.thermal_id = pdata->func_get_thermal_id();
 | |
| 
 | |
| 	power_alg_preinit();
 | |
| 	power_alg_init(&debug_config);
 | |
| 
 | |
| 	di = kzalloc(sizeof(*di), GFP_KERNEL);
 | |
| 	if (!di){
 | |
| 		rc = -ENOMEM;
 | |
| 		goto fail_register;
 | |
| 	}
 | |
| 
 | |
| 	di->update_time = jiffies;
 | |
| 	platform_set_drvdata(pdev, di);
 | |
| 
 | |
| 	di->dev = &pdev->dev;
 | |
| 
 | |
| 	INIT_WORK(&di->monitor_work, ds2746_battery_work);
 | |
| 	di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev));
 | |
| 
 | |
| 	/* init to something sane */
 | |
| 	di->last_poll = alarm_get_elapsed_realtime();
 | |
| 
 | |
| 	if (!di->monitor_wqueue){
 | |
| 		rc = -ESRCH;
 | |
| 		goto fail_workqueue;
 | |
| 	}
 | |
| 	wake_lock_init(&di->work_wake_lock, WAKE_LOCK_SUSPEND, "ds2746-battery");
 | |
| 	alarm_init(&di->alarm,
 | |
| 		ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
 | |
| 		ds2746_battery_alarm);
 | |
| 	wake_lock(&di->work_wake_lock);
 | |
| 	queue_work(di->monitor_wqueue, &di->monitor_work);
 | |
| 	htc_battery_initial = 1;
 | |
| 	return 0;
 | |
| 
 | |
| 	fail_workqueue : fail_register : kfree(di);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int ds2746_battery_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	struct ds2746_device_info *di = platform_get_drvdata(pdev);
 | |
| 
 | |
| 	cancel_work_sync(&di->monitor_work);
 | |
| 	destroy_workqueue(di->monitor_wqueue);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* FIXME: power down DQ master when not in use. */
 | |
| static int ds2746_suspend(struct device *dev)
 | |
| {
 | |
| 	struct platform_device *pdev = to_platform_device(dev);
 | |
| 	struct ds2746_device_info *di = platform_get_drvdata(pdev);
 | |
| 	unsigned long flags;
 | |
| 	pr_info("ds2746_batt:ds2746_suspend\n");
 | |
| 	/* If we are on battery, reduce our update rate until
 | |
| 	 * we next resume.*/
 | |
| 	if (poweralg.charging_source == SOURCE_NONE){
 | |
| 		local_irq_save(flags);
 | |
| 		ds2746_program_alarm(di, SLOW_POLL);
 | |
| 		di->slow_poll = 1;
 | |
| 		local_irq_restore(flags);
 | |
| 	}
 | |
| 	/*gpio_direction_output(87, 0);*/
 | |
| 	return 0;
 | |
| }
 | |
| static void ds2746_resume(struct device *dev)
 | |
| {
 | |
| 	struct platform_device *pdev = to_platform_device(dev);
 | |
| 	struct ds2746_device_info *di = platform_get_drvdata(pdev);
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	/* We might be on a slow sample cycle.  If we're
 | |
| 	 * resuming we should resample the battery state
 | |
| 	 * if it's been over a minute since we last did
 | |
| 	 * so, and move back to sampling every minute until
 | |
| 	 * we suspend again.*/
 | |
| 	/*gpio_direction_output(87, 1);*/
 | |
| 	ndelay(100 * 1000);
 | |
| 	pr_info("ds2746_batt:ds2746_resume\n");
 | |
| 	if (di->slow_poll){
 | |
| 		local_irq_save(flags);
 | |
| 		ds2746_program_alarm(di, FAST_POLL);
 | |
| 		di->slow_poll = 0;
 | |
| 		local_irq_restore(flags);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct dev_pm_ops ds2746_pm_ops = {
 | |
|        .prepare = ds2746_suspend,
 | |
|        .complete  = ds2746_resume,
 | |
| };
 | |
| 
 | |
| MODULE_ALIAS("platform:ds2746-battery");
 | |
| static struct platform_driver ds2746_battery_driver =
 | |
| {
 | |
| 	.driver = {
 | |
| 	.name = "ds2746-battery",
 | |
| 	.pm = &ds2746_pm_ops,
 | |
| 	},
 | |
| 	.probe = ds2746_battery_probe,
 | |
| 	.remove = ds2746_battery_remove,
 | |
| };
 | |
| 
 | |
| static int __init ds2746_battery_init(void)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	pr_info("[ds2746_batt]ds2746_battery_init");
 | |
| 	wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
 | |
| 	register_notifier_cable_status(&cable_status_handler);
 | |
| 
 | |
| 	ret = ds2746_i2c_init();
 | |
| 	if (ret < 0){
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/*mutex_init(&htc_batt_info.lock);*/
 | |
| 	return platform_driver_register(&ds2746_battery_driver);
 | |
| }
 | |
| 
 | |
| static void __exit ds2746_battery_exit(void)
 | |
| {
 | |
| 	ds2746_i2c_exit();
 | |
| 	platform_driver_unregister(&ds2746_battery_driver);
 | |
| }
 | |
| 
 | |
| module_init(ds2746_battery_init);
 | |
| module_exit(ds2746_battery_exit);
 | |
| 
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_AUTHOR("Andy.YS Wang  <Andy.ys_wang@htc.com>");
 | |
| MODULE_DESCRIPTION("ds2746 battery driver");
 | |
| 
 |