diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/power/sec_battery_u1.c | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip |
samsung update 1
Diffstat (limited to 'drivers/power/sec_battery_u1.c')
-rw-r--r-- | drivers/power/sec_battery_u1.c | 3442 |
1 files changed, 3442 insertions, 0 deletions
diff --git a/drivers/power/sec_battery_u1.c b/drivers/power/sec_battery_u1.c new file mode 100644 index 00000000000..a4422518b22 --- /dev/null +++ b/drivers/power/sec_battery_u1.c @@ -0,0 +1,3442 @@ +/* + * sec_battery.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2010 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/android_alarm.h> +#include <plat/adc.h> +#include <linux/power/sec_battery_u1.h> + +#if defined(CONFIG_TARGET_LOCALE_NA) || defined(CONFIG_TARGET_LOCALE_NAATT) +#define POLLING_INTERVAL (10 * 1000) +#else +#define POLLING_INTERVAL (40 * 1000) +#endif /* CONFIG_TARGET_LOCALE_NA */ + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define VF_CHECK_INTERVAL (5 * 1000) + +#define MAX_VF 1800 +#define MIN_VF 1100 +#define VF_COUNT 1 +#elif defined(CONFIG_MACH_Q1_BD) +#define VF_CHECK_INTERVAL (5 * 1000) +#define MAX_VF_ADC 2500 +#define MIN_VF_ADC 1800 +#define ADC_CH_VF 1 +#else +#define VF_CHECK_INTERVAL (10 * 1000) +#endif +#endif +#define FULL_CHARGING_TIME (6 * 60 * 60 * HZ) /* 6hr */ +#ifdef CONFIG_TARGET_LOCALE_NA +#define RECHARGING_TIME (2 * 60 * 60 * HZ) /* 2hr */ +#else +#define RECHARGING_TIME (90 * 60 * HZ) /* 1.5hr */ +#endif +#define RESETTING_CHG_TIME (10 * 60 * HZ) /* 10Min */ +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define EVENT_OVER_TIME (10 * 60 * HZ) /* 10Min */ +#endif +#ifdef CONFIG_TARGET_LOCALE_NA +#define BAT_USE_TIMER_EXPIRE (10 * 60 * HZ) /* 10 min */ +#endif + +#if defined(CONFIG_TARGET_LOCALE_NA) || defined(CONFIG_MACH_Q1_BD) +#define RECHARGING_VOLTAGE (4130 * 1000) /* 4.13 V */ +#else +#define RECHARGING_VOLTAGE (4150 * 1000) /* 4.15 V */ +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#define RECHARGING_CND_COUNT 2 +#endif + +#define FG_T_SOC 0 +#define FG_T_VCELL 1 +#define FG_T_TEMPER 2 +#define FG_T_PSOC 3 +#define FG_T_VFOCV 4 +#define FG_T_AVGVCELL 5 + +#define ADC_SAMPLING_CNT 7 +#if defined(CONFIG_MACH_Q1_BD) +#define ADC_CH_CHGCURRENT 0 +#else +#define ADC_CH_CHGCURRENT 1 +#endif +#define ADC_TOTAL_COUNT 5 + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define OFFSET_VIDEO_PLAY (0x1 << 0) +#define OFFSET_MP3_PLAY (0x1 << 1) +#define OFFSET_VOICE_CALL_2G (0x1 << 2) +#define OFFSET_VOICE_CALL_3G (0x1 << 3) +#define OFFSET_DATA_CALL (0x1 << 4) +#define OFFSET_WIFI (0x1 << 5) +#define OFFSET_GPS (0x1 << 6) +#define OFFSET_CAMERA_ON (0x1 << 7) +#define OFFSET_RECORDING_ON (0x1 << 8) +#endif + +/* put off current during charging*/ +#ifdef CONFIG_TARGET_LOCALE_NA +#define USE_CALL (0x1 << 0) +#define USE_VIDEO (0x1 << 1) +#define USE_MUSIC (0x1 << 2) +#define USE_BROWSER (0x1 << 3) +#define USE_HOTSPOT (0x1 << 4) +#define USE_CAMERA (0x1 << 5) +#define USE_DATA_CALL (0x1 << 6) +#define USE_WIMAX (0x1 << 7) +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#ifdef CONFIG_MACH_U1_NA_SPR_EPIC2_REV00 +#define EVENT_BLOCK_TEMP_ADC 755 +#define HIGH_BLOCK_TEMP_ADC 715 +#define LOW_BLOCK_TEMP_ADC 521 +#define HIGH_RECOVER_TEMP_ADC 714 +#define LOW_RECOVER_TEMP_ADC 522 + +#define HIGH_BLOCK_TEMP_ADC_LPM 715 +#define LOW_BLOCK_TEMP_ADC_LPM 521 +#define HIGH_RECOVER_TEMP_ADC_LPM 714 +#define LOW_RECOVER_TEMP_ADC_LPM 522 +#else +#define EVENT_BLOCK_TEMP_ADC 777 +#define HIGH_BLOCK_TEMP_ADC 729 +#define LOW_BLOCK_TEMP_ADC 502 +#define HIGH_RECOVER_TEMP_ADC 699 +#define LOW_RECOVER_TEMP_ADC 516 + +#define HIGH_BLOCK_TEMP_ADC_LPM 710 +#define LOW_BLOCK_TEMP_ADC_LPM 506 +#define HIGH_RECOVER_TEMP_ADC_LPM 702 +#define LOW_RECOVER_TEMP_ADC_LPM 512 +#endif +#elif defined(CONFIG_TARGET_LOCALE_NAATT) +#define EVENT_BLOCK_TEMP 620 +#define HIGH_BLOCK_TEMP 490 +#define LOW_BLOCK_TEMP (-30) + +#define EVENT_RECOVER_TEMP 430 +#define HIGH_RECOVER_TEMP 430 +#define LOW_RECOVER_TEMP 0 +#elif defined(CONFIG_MACH_Q1_BD) +#define HIGH_BLOCK_TEMP 650 +#define LOW_BLOCK_TEMP (-50) +#define HIGH_RECOVER_TEMP 430 +#define LOW_RECOVER_TEMP 0 +#else +#define HIGH_BLOCK_TEMP 650 +#define LOW_BLOCK_TEMP (-30) +#define HIGH_RECOVER_TEMP 430 +#define LOW_RECOVER_TEMP 0 +#endif + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define HIGH_DEC_CURR_TEMP 410 +#define HIGH_INC_CURR_TEMP 380 +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) +#define CURRENT_OF_FULL_CHG 1 +#else +#define CURRENT_OF_FULL_CHG 350 +#endif /* CONFIG_MACH_U1_NA_SPR_EPIC2_REV00 */ +#elif defined(CONFIG_MACH_Q1_BD) +#ifdef SEC_BATTERY_1ST_2ND_TOPOFF +#if 0 +#define CURRENT_1ST_FULL_CHG 1000 /* 360mA */ +#define CURRENT_2ND_FULL_CHG 650 /* 220mA */ +#endif +#define CURRENT_1ST_FULL_CHG 580 /* 190mA */ +#define CURRENT_2ND_FULL_CHG 540 /* 170mA */ +#else +#define CURRENT_OF_FULL_CHG 850 +#endif +#else +#define CURRENT_OF_FULL_CHG 520 +#endif + +#define TEMP_BLOCK_COUNT 3 +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK +#define BAT_DET_COUNT 0 +#else +#define BAT_DET_COUNT 1 +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#define FULL_CHG_COND_COUNT 2 +#else +#define FULL_CHG_COND_COUNT 3 +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#define FULL_CHARGE_COND_VOLTAGE (4000 * 1000) /* 4.00 V */ +#else +#define FULL_CHARGE_COND_VOLTAGE (4150 * 1000) /* 4.15 V */ +#endif +#define INIT_CHECK_COUNT 4 + +#ifdef CONFIG_TARGET_LOCALE_NA +#define SPRINT_SLATE_TEST +#endif + +enum tmu_status_t { + TMU_STATUS_NORMAL = 0, + TMU_STATUS_TRIPPED, + TMU_STATUS_THROTTLED, + TMU_STATUS_WARNING, +}; + +enum cable_type_t { + CABLE_TYPE_NONE = 0, + CABLE_TYPE_USB, + CABLE_TYPE_AC, + CABLE_TYPE_MISC, +}; + +enum batt_full_t { + BATT_NOT_FULL = 0, + BATT_FULL, +}; + +enum { + BAT_NOT_DETECTED, + BAT_DETECTED +}; + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +enum batt_temp_extra { + BATT_TEMP_EXT_NONE = 0, + BATT_TEMP_EXT_CAMCORDING_NORMAL, + BATT_TEMP_EXT_CAMCORDING_HIGH, +}; +#endif + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +struct adc_sample { + int average_adc; + int adc_arr[ADC_TOTAL_COUNT]; + int index; +}; + +struct sec_bat_info { + struct device *dev; + + char *fuel_gauge_name; + char *charger_name; + char *sub_charger_name; + + unsigned int adc_arr_size; + struct sec_bat_adc_table_data *adc_table; + unsigned int adc_channel; + struct adc_sample temper_adc_sample; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + int vf_adc_channel; +#endif + + struct power_supply psy_bat; + struct power_supply psy_usb; + struct power_supply psy_ac; + + struct wake_lock vbus_wake_lock; + struct wake_lock monitor_wake_lock; + struct wake_lock cable_wake_lock; + + enum cable_type_t cable_type; + enum batt_full_t batt_full_status; + + unsigned int batt_temp; /* Battery Temperature (C) */ + int batt_temp_high_cnt; + int batt_temp_low_cnt; + int batt_temp_recover_cnt; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + enum batt_temp_extra batt_temp_ext; + enum batt_temp_extra batt_temp_ext_pre; +#endif + unsigned int batt_health; + unsigned int batt_vcell; + unsigned int batt_vfocv; + unsigned int batt_soc; + unsigned int batt_raw_soc; + unsigned int polling_interval; + int charging_status; + int charging_int_full_count; + int charging_adc_full_count; +#ifdef CONFIG_TARGET_LOCALE_NA + int recharging_int_threshold_count; +#endif + + unsigned int batt_temp_adc; +#ifdef CONFIG_TARGET_LOCALE_NA + unsigned int batt_temp_radc; +#endif + unsigned int batt_current_adc; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + int batt_vf_adc; + int batt_event_status; + unsigned long event_end_time; +#elif defined(CONFIG_TARGET_LOCALE_NA) + int use_call; + int use_video; + int use_music; + int use_browser; + int use_hotspot; + int use_camera; + int use_data_call; + int use_wimax; + int batt_event_status; + unsigned long event_expired_time; +#endif + struct s3c_adc_client *padc; + + struct workqueue_struct *monitor_wqueue; + struct work_struct monitor_work; + struct work_struct cable_work; + struct delayed_work polling_work; + + unsigned long charging_start_time; + unsigned long charging_passed_time; + unsigned long charging_next_time; + unsigned int recharging_status; + unsigned int batt_lpm_state; +#ifdef SPRINT_SLATE_TEST + bool slate_test_mode; +#endif + + struct mutex adclock; + + unsigned int (*get_lpcharging_state) (void); + int present; + int present_count; + bool use_sub_charger; + + int test_value; + int initial_check_count; + struct proc_dir_entry *entry; + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + unsigned int vf_check_interval; + struct delayed_work vf_check_work; +#endif + int batt_tmu_status; +}; + +static char *supply_list[] = { + "battery", +}; + +static enum power_supply_property sec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +static enum power_supply_property sec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +#ifdef CONFIG_TARGET_LOCALE_NA +static struct sec_bat_info *pchg; +#endif + +struct power_supply *get_power_supply_by_name(char *name) +{ + if (!name) + return (struct power_supply *)NULL; + else + return power_supply_get_by_name(name); +} + +static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable); + +static int calculate_average_adc(struct sec_bat_info *info, + struct adc_sample *sample, int adc) +{ + int i, total_adc = 0; + int average_adc = sample->average_adc; + int index = sample->index; + + if (adc < 0 || adc == 0) { + dev_err(info->dev, "%s: invalid adc : %d\n", __func__, adc); + return 0; + } + + if (!average_adc) { + average_adc = adc; + for (i = 0; i < ADC_TOTAL_COUNT; i++) + sample->adc_arr[i] = adc; + } else { + sample->index = ++index >= ADC_TOTAL_COUNT ? 0 : index; + sample->adc_arr[sample->index] = adc; + for (i = 0; i < ADC_TOTAL_COUNT; i++) + total_adc += sample->adc_arr[i]; + + average_adc = total_adc / ADC_TOTAL_COUNT; + } + + sample->average_adc = average_adc; + dev_dbg(info->dev, "%s: i(%d) adc=%d, avg_adc=%d\n", __func__, + sample->index, adc, average_adc); + + return average_adc; +} + +static int sec_bat_get_fuelgauge_data(struct sec_bat_info *info, int type) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get fuel gauge ps\n", __func__); + return -ENODEV; + } + + switch (type) { + case FG_T_VCELL: + value.intval = 0; /*vcell */ + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); + break; + case FG_T_VFOCV: + value.intval = 1; /*vfocv */ + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); + break; + case FG_T_AVGVCELL: + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_AVG, &value); + break; + case FG_T_SOC: + value.intval = 0; /*normal soc */ + psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value); + break; + case FG_T_PSOC: + value.intval = 1; /*raw soc */ + psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value); + break; + case FG_T_TEMPER: + psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + break; + default: + return -ENODEV; + } + + return value.intval; +} + +static int sec_bat_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = info->charging_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = info->batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = info->batt_temp; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + /* val->intval = 1; */ + val->intval = info->cable_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = info->batt_vcell; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CAPACITY: +#ifdef CONFIG_TARGET_LOCALE_NA + if (info->charging_status != POWER_SUPPLY_STATUS_FULL + && info->batt_soc == 100) { + val->intval = 99; + break; + } +#endif /*CONFIG_TARGET_LOCALE_NA */ + + if (info->charging_status == POWER_SUPPLY_STATUS_FULL) { + val->intval = 100; + break; + } + val->intval = info->batt_soc; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = -1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void sec_bat_handle_charger_topoff(struct sec_bat_info *info) +{ +#ifdef SEC_BATTERY_1ST_2ND_TOPOFF + if (info->charging_status != POWER_SUPPLY_STATUS_FULL) { + /* 1st top-off */ + info->charging_status = POWER_SUPPLY_STATUS_FULL; + + dev_info(info->dev, "%s: Charging 1st Top-off\n", __func__); + } else { + /* 2nd top-off, recharging top-off */ + if (!sec_bat_enable_charging(info, false)) { + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + } + + dev_info(info->dev, "%s: Charging 2nd Top-off\n", __func__); + } +#else + if (!sec_bat_enable_charging(info, false)) { + info->charging_status = POWER_SUPPLY_STATUS_FULL; + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + + dev_info(info->dev, "%s: Charging Top-off\n", __func__); + } +#endif +} + +static int sec_bat_set_property(struct power_supply *ps, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_bat); + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return -EINVAL; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + dev_info(info->dev, "%s: topoff intr\n", __func__); + if (val->intval != POWER_SUPPLY_STATUS_FULL) + return -EINVAL; + + if (info->use_sub_charger) { +#if !defined(CONFIG_MACH_Q1_BD) + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) +#endif + sec_bat_handle_charger_topoff(info); + break; + } + + if (info->batt_full_status == BATT_NOT_FULL) { + info->recharging_status = false; + info->batt_full_status = BATT_FULL; + info->charging_status = POWER_SUPPLY_STATUS_FULL; + + info->charging_start_time = 0; + info->charging_passed_time = 0; + info->charging_next_time = 0; + + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, + &value); + } +#if 0 /* for reference */ + if (info->batt_full_status == BATT_NOT_FULL) { + info->batt_full_status = BATT_1ST_FULL; + info->charging_status = POWER_SUPPLY_STATUS_FULL; + /* TODO: set topoff current 60mA */ + value.intval = 120; + psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &value); + } else { + info->batt_full_status = BATT_2ND_FULL; + info->recharging_status = false; + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, + &value); + } +#endif + break; + case POWER_SUPPLY_PROP_CAPACITY: + dev_info(info->dev, "%s: refresh battery data\n", __func__); + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + /* TODO: lowbatt interrupt: called by fuel gauge */ + dev_info(info->dev, "%s: lowbatt intr\n", __func__); + if (val->intval != POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL) + return -EINVAL; + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + break; + case POWER_SUPPLY_PROP_ONLINE: + /* cable is attached or detached. called by USB switch(MUIC) */ + dev_info(info->dev, "%s: cable was changed(%d)\n", __func__, + val->intval); + switch (val->intval) { + case POWER_SUPPLY_TYPE_BATTERY: + info->cable_type = CABLE_TYPE_NONE; + break; + case POWER_SUPPLY_TYPE_MAINS: + info->cable_type = CABLE_TYPE_AC; + break; + case POWER_SUPPLY_TYPE_USB: + info->cable_type = CABLE_TYPE_USB; + break; + case POWER_SUPPLY_TYPE_MISC: + info->cable_type = CABLE_TYPE_MISC; + break; + default: + return -EINVAL; + } + wake_lock(&info->cable_wake_lock); + queue_work(info->monitor_wqueue, &info->cable_work); + break; + case POWER_SUPPLY_PROP_HEALTH: + /* call by TMU driver + * alarm for abnormal temperature increasement + */ + info->batt_tmu_status = val->intval; + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + dev_info(info->dev, "%s: TMU status has been changed(%d)\n", + __func__, info->batt_tmu_status); + + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_usb); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the USB charger is connected */ + val->intval = (info->cable_type == CABLE_TYPE_USB); + + return 0; +} + +static int sec_ac_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_ac); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the AC charger is connected */ + val->intval = (info->cable_type == CABLE_TYPE_AC) || + (info->cable_type == CABLE_TYPE_MISC); + + return 0; +} + +static int sec_bat_get_adc_data(struct sec_bat_info *info, int adc_ch) +{ + int adc_data; + int adc_max = 0; + int adc_min = 0; + int adc_total = 0; + int i; + int err_value; + + for (i = 0; i < ADC_SAMPLING_CNT; i++) { + adc_data = s3c_adc_read(info->padc, adc_ch); + if (adc_data < 0) { + dev_err(info->dev, "%s : err(%d) returned, skip read\n", + __func__, adc_data); + err_value = adc_data; + goto err; + } + + if (i != 0) { + if (adc_data > adc_max) + adc_max = adc_data; + else if (adc_data < adc_min) + adc_min = adc_data; + } else { + adc_max = adc_data; + adc_min = adc_data; + } + adc_total += adc_data; + } + + return (adc_total - adc_max - adc_min) / (ADC_SAMPLING_CNT - 2); + err: + return err_value; +} + +#ifdef CONFIG_TARGET_LOCALE_NA +static inline int s3c_read_temper_adc(struct sec_bat_info *info) +{ + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, info->adc_channel); + mutex_unlock(&info->adclock); + if (adc <= 0) + adc = info->batt_temp_adc; + + info->batt_temp_adc = + calculate_average_adc(info, &info->temper_adc_sample, adc); + + return info->batt_temp_adc; +} +#else +static inline int s3c_read_temper_adc(struct sec_bat_info *info) +{ + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, info->adc_channel); + mutex_unlock(&info->adclock); + if (adc <= 0) + adc = info->batt_temp_adc; + info->batt_temp_adc = adc; + + return adc; +} +#endif + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +static int is_event_end_timer_running(struct sec_bat_info *info) +{ + unsigned long passed_time = 0; + + if (info->event_end_time == 0xFFFFFFFF) /*initial value */ + return false; + + if (jiffies >= info->event_end_time) + passed_time = jiffies - info->event_end_time; + else + passed_time = 0xFFFFFFFF - info->event_end_time + jiffies; + + if (time_after(passed_time, (unsigned long)EVENT_OVER_TIME)) { + info->event_end_time = 0xFFFFFFFF; + dev_info(info->dev, "%s: Event timer is over 10 min\n", + __func__); + return false; + } else { + dev_info(info->dev, "%s: Event timer is running(%u s)\n", + __func__, jiffies_to_msecs(passed_time) / 1000); + return true; + } + + return false; +} + +static int get_recording_state(struct sec_bat_info *info) +{ + return info->batt_event_status & OFFSET_RECORDING_ON; +} +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +static int is_event_end_timer_running(struct sec_bat_info *info) +{ + unsigned long passed_time = 0; + + if (info->event_expired_time == 0xFFFFFFFF) /*initial value */ + return false; + + if (jiffies >= info->event_expired_time) + passed_time = jiffies - info->event_expired_time; + else + passed_time = 0xFFFFFFFF - info->event_expired_time + jiffies; + + if (time_after(passed_time, (unsigned long)BAT_USE_TIMER_EXPIRE)) { + info->event_expired_time = 0xFFFFFFFF; + dev_info(info->dev, "[SPR_NA] %s: Event timer is over 10 min\n", + __func__); + return false; + } else { + dev_info(info->dev, + "[SPR_NA] %s: Event timer is running(%u s)\n", + __func__, jiffies_to_msecs(passed_time) / 1000); + return true; + } + + return false; +} + +static unsigned long sec_rescale_temp_adc(struct sec_bat_info *info) +{ + int adc_tmp = info->batt_temp_adc; + int adc_tmp1 = 0; + int adc_tmp2 = 0; + + adc_tmp1 = adc_tmp * 10; + adc_tmp2 = (40950 - adc_tmp1); + adc_tmp = adc_tmp2 / 50; + if ((adc_tmp2 % 10) >= 5) + adc_tmp += 1; + + info->batt_temp_radc = adc_tmp; + + dev_dbg(info->dev, "deb***: [battery] bat temper : %d, rescale : %d\n", + info->batt_temp_adc, adc_tmp); + + return adc_tmp; +} +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + + int temp; + int temp_adc = s3c_read_temper_adc(info); + int temp_radc = sec_rescale_temp_adc(info); + int health = info->batt_health; + int low = 0; + int high = 0; + int mid = 0; + + /*calculate_average_adc(info, &info->temper_adc_sample, temp_adc);*/ + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + return temp; + } + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + + info->batt_temp = temp; + + if (info->get_lpcharging_state) { + if (info->get_lpcharging_state()) { + if (temp_radc >= HIGH_BLOCK_TEMP_ADC_LPM) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, "%s: high count = %d\n", + __func__, info->batt_temp_high_cnt); + } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC_LPM && + temp_radc >= LOW_RECOVER_TEMP_ADC_LPM) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info->batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: recovery count = %d\n", + __func__, + info->batt_temp_recover_cnt); + } + } else if (temp_radc <= LOW_BLOCK_TEMP_ADC_LPM) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, "%s: low count = %d\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } else { + if ((info->batt_event_status) + || (is_event_end_timer_running(info))) { + dev_info(info->dev, + "[NA_SPR] Changed Put off Current", + __func__); + if (temp_radc >= EVENT_BLOCK_TEMP_ADC) { + if (health != + POWER_SUPPLY_HEALTH_OVERHEAT + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_high_cnt++; + dev_info(info->dev, + "%s: high count = %d\n", + __func__, + info->batt_temp_high_cnt); + } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC + && temp_radc >= + LOW_RECOVER_TEMP_ADC) { + if (health == + POWER_SUPPLY_HEALTH_OVERHEAT + || health == + POWER_SUPPLY_HEALTH_COLD) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info-> + batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: recovery count = %d\n", + __func__, + info-> + batt_temp_recover_cnt); + } + } else if (temp_radc <= LOW_BLOCK_TEMP_ADC) { + if (health != POWER_SUPPLY_HEALTH_COLD + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_low_cnt++; + dev_info(info->dev, + "%s: low count = %d\n", + __func__, + info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } else { + if (temp_radc >= HIGH_BLOCK_TEMP_ADC) { + if (health != + POWER_SUPPLY_HEALTH_OVERHEAT + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_high_cnt++; + dev_info(info->dev, + "%s: high count = %d\n", + __func__, + info->batt_temp_high_cnt); + } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC + && temp_radc >= + LOW_RECOVER_TEMP_ADC) { + if (health == + POWER_SUPPLY_HEALTH_OVERHEAT + || health == + POWER_SUPPLY_HEALTH_COLD) { + + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info-> + batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: recovery count = %d\n", + __func__, + info-> + batt_temp_recover_cnt); + } + } else if (temp_radc <= LOW_BLOCK_TEMP_ADC) { + if (health != POWER_SUPPLY_HEALTH_COLD + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_low_cnt++; + dev_info(info->dev, + "%s: low count = %d\n", + __func__, + info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } + } + } + + if (info->cable_type == CABLE_TYPE_NONE) { + if (info->batt_health == POWER_SUPPLY_HEALTH_OVERHEAT || + info->batt_health == POWER_SUPPLY_HEALTH_COLD) { + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + goto skip; + } + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /*else + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; */ + + /*else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; */ + skip: + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + } + } + + dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc); + + return temp; +} + +static void sec_bat_get_dcinovp(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return; + } + + if (info->slate_test_mode) { + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + info->cable_type = CABLE_TYPE_NONE; + } else { + psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + info->charging_status = value.intval; + } +} + +#elif defined(CONFIG_TARGET_LOCALE_NAATT) +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + + int temp; + int temp_adc = s3c_read_temper_adc(info); + int health = info->batt_health; + int low = 0; + int high = 0; + int mid = 0; + + calculate_average_adc(info, &info->temper_adc_sample, temp_adc); + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + return temp; + } + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + + info->batt_temp = temp; + + if ((info->batt_event_status) || (is_event_end_timer_running(info))) { + /*printk("[%s] event temp is applied\n", __func__); */ + if (temp >= EVENT_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, + "%s: event high count = %d(in event)\n", + __func__, info->batt_temp_high_cnt); + } else if (temp <= EVENT_RECOVER_TEMP + && temp >= LOW_RECOVER_TEMP) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT + || health == POWER_SUPPLY_HEALTH_COLD) { + + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info->batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: event recovery count = %d\n", + __func__, info->batt_temp_recover_cnt); + } + } else if (temp <= LOW_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, + "%s: event low count = %d(in event)\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } else { +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->batt_temp_ext_pre = info->batt_temp_ext; + switch (info->batt_temp_ext) { + case BATT_TEMP_EXT_NONE: + if (get_recording_state(info)) + info->batt_temp_ext = + BATT_TEMP_EXT_CAMCORDING_NORMAL; + break; + case BATT_TEMP_EXT_CAMCORDING_NORMAL: + if (!get_recording_state(info)) + info->batt_temp_ext = BATT_TEMP_EXT_NONE; + else if (temp >= HIGH_DEC_CURR_TEMP) + info->batt_temp_ext = + BATT_TEMP_EXT_CAMCORDING_HIGH; + break; + case BATT_TEMP_EXT_CAMCORDING_HIGH: + if (!get_recording_state(info)) + info->batt_temp_ext = BATT_TEMP_EXT_NONE; + else if (temp <= HIGH_INC_CURR_TEMP) + info->batt_temp_ext = + BATT_TEMP_EXT_CAMCORDING_NORMAL; + break; + default: + break; + } + dev_info(info->dev, + "%s: batt_temp_ext_pre=%d, batt_temp_ext=%d\n", + __func__, info->batt_temp_ext_pre, + info->batt_temp_ext); +#endif + /*printk("[%s] nomal temp is applied\n", __func__); */ + if (temp >= HIGH_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, "%s: high count = %d\n", + __func__, info->batt_temp_high_cnt); + } else if (temp <= HIGH_RECOVER_TEMP + && temp >= LOW_RECOVER_TEMP) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT + || health == POWER_SUPPLY_HEALTH_COLD) { + + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info->batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, "%s: recovery count = %d\n", + __func__, info->batt_temp_recover_cnt); + } + } else if (temp <= LOW_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, "%s: low count = %d\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } + + if (info->cable_type == CABLE_TYPE_NONE) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + } + } + + dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc); + + return temp; +} + +#else +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + + int temp; + int temp_adc = s3c_read_temper_adc(info); + int health = info->batt_health; + int low = 0; + int high = 0; + int mid = 0; + + calculate_average_adc(info, &info->temper_adc_sample, temp_adc); + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + return temp; + } + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + + info->batt_temp = temp; + + if (temp >= HIGH_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, "%s: high count = %d\n", + __func__, info->batt_temp_high_cnt); + } else if (temp <= HIGH_RECOVER_TEMP && temp >= LOW_RECOVER_TEMP) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) + if (info->batt_temp_recover_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, "%s: recovery count = %d\n", + __func__, info->batt_temp_recover_cnt); + } else if (temp <= LOW_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, "%s: low count = %d\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + } + } + + dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc); + + return temp; +} +#endif +static void check_chgcurrent(struct sec_bat_info *info) +{ + unsigned long chg_current_adc = 0; + + mutex_lock(&info->adclock); + chg_current_adc = sec_bat_get_adc_data(info, ADC_CH_CHGCURRENT); + mutex_unlock(&info->adclock); + if (chg_current_adc < 0) + chg_current_adc = info->batt_current_adc; + info->batt_current_adc = chg_current_adc; + + dev_dbg(info->dev, + "[battery] charging current = %d\n", info->batt_current_adc); +} + +static bool sec_check_chgcurrent(struct sec_bat_info *info) +{ + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + info->batt_current_adc = 0; + if ((info->batt_full_status == BATT_FULL) && + (info->recharging_status == false)) + break; + case POWER_SUPPLY_STATUS_CHARGING: + check_chgcurrent(info); + + if (info->batt_vfocv >= FULL_CHARGE_COND_VOLTAGE / 1000) { + +#ifdef SEC_BATTERY_1ST_2ND_TOPOFF + if (((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) + && (info->batt_current_adc <= + CURRENT_1ST_FULL_CHG)) + || + ((info->charging_status == POWER_SUPPLY_STATUS_FULL) + && (info->batt_current_adc <= + CURRENT_2ND_FULL_CHG))) { +#else + if (info->batt_current_adc <= CURRENT_OF_FULL_CHG) { +#endif + /*TA full charged */ + info->charging_adc_full_count++; + if (info->charging_adc_full_count >= + FULL_CHG_COND_COUNT) { + info->charging_adc_full_count = 0; + sec_bat_handle_charger_topoff(info); + + return true; + } + dev_info(info->dev, "%s : ADC full cnt = %d\n", + __func__, + info->charging_adc_full_count); + } else + info->charging_adc_full_count = 0; + } + break; + default: + info->charging_adc_full_count = 0; + info->batt_current_adc = 0; + break; + } + + return false; +} + +static void sec_bat_update_info(struct sec_bat_info *info) +{ + info->batt_raw_soc = sec_bat_get_fuelgauge_data(info, FG_T_PSOC); + info->batt_soc = sec_bat_get_fuelgauge_data(info, FG_T_SOC); + info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL); + info->batt_vfocv = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV); +} + +static int sec_bat_enable_charging_main(struct sec_bat_info *info, bool enable) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval val_type, val_chg_current, val_topoff; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + info->batt_full_status = BATT_NOT_FULL; + + if (enable) { /* Enable charging */ + switch (info->cable_type) { + case CABLE_TYPE_USB: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; /* mA */ + break; + case CABLE_TYPE_MISC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + default: + dev_err(info->dev, "%s: Invalid func use\n", __func__); + return -EINVAL; + } + + /* Set charging current */ + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } + + /* Set topoff current */ + /* mA: TODO: should change 200->160 */ + val_topoff.intval = 200; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &val_topoff); + if (ret) { + dev_err(info->dev, "%s: fail to set topoff cur(%d)\n", + __func__, ret); + return ret; + } + + /*Reset charging start time only in initial charging start */ + if (info->charging_start_time == 0) { + info->charging_start_time = jiffies; + info->charging_next_time = RESETTING_CHG_TIME; + } + } else { /* Disable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + + info->charging_start_time = 0; + info->charging_passed_time = 0; + info->charging_next_time = 0; + } + + ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + return 0; +} + +static int sec_bat_enable_charging_sub(struct sec_bat_info *info, bool enable) +{ + struct power_supply *psy_main = + get_power_supply_by_name(info->charger_name); + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval val_type, val_chg_current; + int ret; + + if (!psy_main || !psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + info->batt_full_status = BATT_NOT_FULL; + + if (enable) { /* Enable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + return ret; + } +#if defined(CONFIG_MACH_Q1_BD) + /* Set charging current only in charging start time */ + if (info->charging_start_time == 0) { +#endif + switch (info->cable_type) { + case CABLE_TYPE_USB: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + if (info->batt_temp_ext == + BATT_TEMP_EXT_CAMCORDING_HIGH) + val_chg_current.intval = 450; /* mA */ + else +#endif + val_chg_current.intval = 650; /* mA */ + break; + case CABLE_TYPE_MISC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + default: + dev_err(info->dev, "%s: Invalid func use\n", + __func__); + return -EINVAL; + } + + /* Set charging current */ + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, + "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } +#if defined(CONFIG_MACH_Q1_BD) + /* Reset charging */ + } else + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; +#endif + + /*Reset charging start time only in initial charging start */ + if (info->charging_start_time == 0) { + info->charging_start_time = jiffies; + info->charging_next_time = RESETTING_CHG_TIME; + } + } else { /* Disable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + return ret; + } + info->charging_start_time = 0; + info->charging_passed_time = 0; + info->charging_next_time = 0; + } + + ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable) +{ + if (enable && (info->batt_health != POWER_SUPPLY_HEALTH_GOOD)) { + info->charging_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + dev_info(info->dev, "%s: Battery is NOT good!!!\n", __func__); + return -EPERM; + } + + if (info->use_sub_charger) + return sec_bat_enable_charging_sub(info, enable); + else + return sec_bat_enable_charging_main(info, enable); +} + +static void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_bat_info *info = container_of(work, struct sec_bat_info, + cable_work); + + switch (info->cable_type) { + case CABLE_TYPE_NONE: + if (!sec_bat_enable_charging(info, false)) { + info->batt_full_status = BATT_NOT_FULL; + info->recharging_status = false; + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->batt_temp_ext = BATT_TEMP_EXT_NONE; + info->batt_temp_ext_pre = BATT_TEMP_EXT_NONE; +#endif + wake_lock_timeout(&info->vbus_wake_lock, HZ * 5); + } + break; + case CABLE_TYPE_USB: + case CABLE_TYPE_AC: + case CABLE_TYPE_MISC: + if (!sec_bat_enable_charging(info, true)) { + info->charging_status = POWER_SUPPLY_STATUS_CHARGING; + wake_lock(&info->vbus_wake_lock); + } + break; + default: + dev_err(info->dev, "%s: Invalid cable type\n", __func__); + break; + } + + power_supply_changed(&info->psy_ac); + power_supply_changed(&info->psy_usb); + + wake_unlock(&info->cable_wake_lock); +} + +static bool sec_bat_charging_time_management(struct sec_bat_info *info) +{ + if (info->charging_start_time == 0) { + dev_info(info->dev, + "%s: charging_start_time has never been used since initializing\n", + __func__); + return false; + } + + if (jiffies >= info->charging_start_time) + info->charging_passed_time = + jiffies - info->charging_start_time; + else + info->charging_passed_time = + 0xFFFFFFFF - info->charging_start_time + jiffies; + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (info->recharging_status == true && + time_after(info->charging_passed_time, + (unsigned long)RECHARGING_TIME)) { + if (!sec_bat_enable_charging(info, false)) { + info->recharging_status = false; + dev_info(info->dev, + "%s: Recharging timer expired\n", + __func__); + return true; + } + } +#ifndef SEC_BATTERY_1ST_2ND_TOPOFF + break; +#endif + case POWER_SUPPLY_STATUS_CHARGING: +#if defined(CONFIG_MACH_Q1_BD) + if (info->cable_type != CABLE_TYPE_USB) { +#endif + if (time_after(info->charging_passed_time, + (unsigned long)FULL_CHARGING_TIME)) { + if (!sec_bat_enable_charging(info, false)) { + info->charging_status = + POWER_SUPPLY_STATUS_FULL; + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + + dev_info(info->dev, + "%s: Charging timer expired\n", + __func__); + return true; + } + } +#if defined(CONFIG_MACH_Q1_BD) + } +#endif + if (time_after(info->charging_passed_time, + info->charging_next_time)) { + /*reset current in charging status */ + if (!sec_bat_enable_charging(info, true)) { + info->charging_next_time = + info->charging_passed_time + + RESETTING_CHG_TIME; + + dev_info(info->dev, + "%s: Reset charging current\n", + __func__); + } + } + break; + default: + dev_info(info->dev, "%s: Undefine Battery Status\n", __func__); + return false; + } + + dev_info(info->dev, "Time past : %u secs\n", + jiffies_to_msecs(info->charging_passed_time) / 1000); + + return false; +} + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +static void sec_bat_check_vf_adc(struct sec_bat_info *info) +{ + int adc; + static int invalid_vf_count; + + if (system_rev == 11) + return; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, info->vf_adc_channel); + mutex_unlock(&info->adclock); + + if (adc <= 0) + adc = info->batt_vf_adc; + + info->batt_vf_adc = adc; + + if ((info->batt_vf_adc > MAX_VF || info->batt_vf_adc < MIN_VF) + && ((info->cable_type == CABLE_TYPE_AC) + || info->cable_type == CABLE_TYPE_USB)) { + invalid_vf_count++; + + printk(KERN_DEBUG + "[%s] invalid VF is detected... (vf = %d, count = %d)\n", + __func__, info->batt_vf_adc, invalid_vf_count); + + if (invalid_vf_count >= VF_COUNT) { + printk(KERN_DEBUG + "[%s] invalid VF is detected over %d times and now power off.\n", + __func__, invalid_vf_count); + if (pm_power_off) + pm_power_off(); + } + } else { + invalid_vf_count = 0; + } + + return; +} +#endif + +static void sec_bat_check_vf(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return; + } + +#if defined(CONFIG_MACH_Q1_BD) + if (system_rev <= HWREV_FOR_BATTERY) { + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, ADC_CH_VF); + mutex_unlock(&info->adclock); + + dev_info(info->dev, "%s: adc (%d/%d)\n", __func__, adc, + HWREV_FOR_BATTERY); + + if (adc > MAX_VF_ADC || adc < MIN_VF_ADC) + value.intval = BAT_NOT_DETECTED; + else + value.intval = BAT_DETECTED; + } else { +#endif + ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return; + } +#if defined(CONFIG_MACH_Q1_BD) + } +#endif + + if (value.intval == BAT_NOT_DETECTED) { + if (info->present_count < BAT_DET_COUNT) + info->present_count++; + else { + info->present = BAT_NOT_DETECTED; + if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) + info->batt_health = + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } + } else { + info->present = BAT_DETECTED; + if ((info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || + (info->batt_health == POWER_SUPPLY_HEALTH_UNKNOWN)) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + info->present_count = 0; + } + + dev_info(info->dev, "%s: Battery Health (%d)\n", + __func__, info->batt_health); + return; +} + +#if (defined(CONFIG_MACH_Q1_BD) && defined(CONFIG_SMB328_CHARGER)) +static void sec_bat_check_ovp(struct sec_bat_info *info) +{ + struct power_supply *psy = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->sub_charger_name); + return; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_HEALTH, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get health(%d)\n", + __func__, ret); + return; + } + + if (value.intval == POWER_SUPPLY_HEALTH_OVERVOLTAGE) + info->batt_health = value.intval; +} +#endif + +static bool sec_bat_check_ing_level_trigger(struct sec_bat_info *info) +{ + struct power_supply *psy; + union power_supply_propval value; + int ret; + + if (info->use_sub_charger && info->sub_charger_name) { + psy = get_power_supply_by_name(info->sub_charger_name); + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->sub_charger_name); + return false; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return false; + } + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (info->recharging_status == false) + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (value.intval) { +#ifdef SEC_BATTERY_TOPOFF_BY_CHARGER + case POWER_SUPPLY_STATUS_FULL: + /* top-off by full charging */ + if (info->batt_vcell >= + FULL_CHARGE_COND_VOLTAGE) { + sec_bat_handle_charger_topoff(info); + dev_info(info->dev, + "%s : top-off by full charging\n", + __func__); + return true; + } else { + /*reactivate charging in */ + /*next monitor work */ + /*for abnormal */ + /*full-charged status */ + info->charging_next_time = + info->charging_passed_time + HZ; + } + break; +#endif +#if defined(CONFIG_MACH_Q1_BD) + case POWER_SUPPLY_STATUS_NOT_CHARGING: + ret = + psy->get_property(psy, + POWER_SUPPLY_PROP_HEALTH, + &value); + + if (ret < 0) { + dev_err(info->dev, + "%s: fail to get health(%d)\n", + __func__, ret); + return false; + } + info->batt_health = value.intval; +#else + case POWER_SUPPLY_STATUS_DISCHARGING: + if (info->cable_type != CABLE_TYPE_NONE) + info->batt_health = + POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: +#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC || + info->cable_type == CABLE_TYPE_AC) { +#else + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) { +#endif + if (info->batt_vcell >= + FULL_CHARGE_COND_VOLTAGE) { + /* USB full charged */ + info->charging_int_full_count++; + if (info-> + charging_int_full_count >= + FULL_CHG_COND_COUNT) { + info-> + charging_int_full_count + = 0; + sec_bat_handle_charger_topoff + (info); + return true; + } + dev_info(info->dev, + "%s : full interrupt cnt = %d\n", + __func__, + info-> + charging_int_full_count); + } else { + info->charging_int_full_count = + 0; + /*reactivate charging in */ + /*next monitor work */ + /*for abnormal */ + /*full-charged status */ + info->charging_next_time = + info->charging_passed_time + + HZ; + } + } + break; +#endif + } + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: +#if defined(CONFIG_MACH_Q1_BD) + if (info->batt_health == + POWER_SUPPLY_HEALTH_OVERVOLTAGE) { + ret = + psy->get_property(psy, + POWER_SUPPLY_PROP_HEALTH, + &value); + + if (ret < 0) { + dev_err(info->dev, + "%s: fail to get health(%d)\n", + __func__, ret); + return false; + } + info->batt_health = value.intval; + } +#else + if (info->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE + && value.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; +#endif + break; + default: + info->charging_int_full_count = 0; + break; + } + return false; + } else { + psy = get_power_supply_by_name(info->charger_name); + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return false; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return false; + } + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (info->recharging_status == false) + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (value.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) { + if (info->batt_vcell >= + FULL_CHARGE_COND_VOLTAGE) { + /*USB full charged */ + info->charging_int_full_count++; + if (info-> + charging_int_full_count >= + FULL_CHG_COND_COUNT) { + info-> + charging_int_full_count + = 0; + sec_bat_handle_charger_topoff + (info); + return true; + } + dev_info(info->dev, + "%s : full interrupt cnt = %d\n", + __func__, + info-> + charging_int_full_count); + } else { + info->charging_int_full_count = + 0; + /*reactivate charging in */ + /*next monitor work */ + /*for abnormal full-charged status */ + info->charging_next_time = + info->charging_passed_time + + HZ; + } + } + break; + } + break; + default: + info->charging_int_full_count = 0; + break; + } + return false; + } +} + +static void sec_bat_monitor_work(struct work_struct *work) +{ + struct sec_bat_info *info = container_of(work, struct sec_bat_info, + monitor_work); + + sec_bat_check_temper(info); +#ifndef SEC_BATTERY_INDEPEDENT_VF_CHECK + sec_bat_check_vf(info); +#endif + sec_bat_update_info(info); +#ifdef SPRINT_SLATE_TEST + if (info->charging_status != POWER_SUPPLY_STATUS_FULL && + info->batt_health == POWER_SUPPLY_HEALTH_GOOD) + if (info->batt_temp_recover_cnt == 0 + || info->batt_temp_recover_cnt > 3) + sec_bat_get_dcinovp(info); +#endif + if (sec_bat_charging_time_management(info)) + goto full_charged; + + if (sec_bat_check_ing_level_trigger(info)) + goto full_charged; + +#if !defined(CONFIG_MACH_Q1_BD) + if (info->cable_type == CABLE_TYPE_AC) +#endif + if (sec_check_chgcurrent(info)) + goto full_charged; + + if (info->cable_type == CABLE_TYPE_NONE && + info->charging_status == POWER_SUPPLY_STATUS_FULL) { + dev_err(info->dev, "invalid full state\n"); + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (info->test_value) { + switch (info->test_value) { + case 1: /*full status */ + info->charging_status = POWER_SUPPLY_STATUS_FULL; + break; + case 2: /*low temperature */ + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + break; + case 3: /*high temperature */ + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 4: /*over voltage */ + info->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case 5: /*abnormal battery */ + info->batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case 6: /*tmu tripped */ + info->batt_tmu_status = TMU_STATUS_TRIPPED; + break; + } + } + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + +#ifdef CONFIG_TARGET_LOCALE_NA + if (info->batt_vcell < RECHARGING_VOLTAGE && + info->recharging_status == false) { + info->recharging_int_threshold_count++; + if (info->recharging_int_threshold_count + >= RECHARGING_CND_COUNT) { + if (!sec_bat_enable_charging(info, true)) { + info->recharging_int_threshold_count = + 0; + info->recharging_status = true; + + dev_info(info->dev, + "%s: Start Recharging, Vcell = %d\n", + __func__, info->batt_vcell); + } + } + } else { + info->recharging_int_threshold_count = 0; + } +#else + if (info->batt_vcell < RECHARGING_VOLTAGE && + info->recharging_status == false) { + if (!sec_bat_enable_charging(info, true)) { + info->recharging_status = true; + + dev_info(info->dev, + "%s: Start Recharging, Vcell = %d\n", + __func__, info->batt_vcell); + } + } +#endif + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (info->batt_health) { +#if defined(CONFIG_TARGET_LOCALE_NAATT) + case POWER_SUPPLY_HEALTH_GOOD: + if (info->batt_temp_ext_pre != info->batt_temp_ext) { + sec_bat_enable_charging(info, false); + + if (!sec_bat_enable_charging(info, true)) { + dev_info(info->dev, + "%s: Changed charging current\n", + __func__); + } + } + break; +#endif + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_DEAD: + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + if (!sec_bat_enable_charging(info, false)) { + info->charging_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + info->recharging_status = false; + + dev_info(info->dev, "%s: Not charging\n", + __func__); + } + break; + default: + break; + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_dbg(info->dev, "%s: Discharging\n", __func__); + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) { + dev_info(info->dev, "%s: recover health state\n", + __func__); + if (info->cable_type != CABLE_TYPE_NONE) { + if (!sec_bat_enable_charging(info, true)) + info->charging_status = + POWER_SUPPLY_STATUS_CHARGING; + } else + info->charging_status + = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + default: + dev_info(info->dev, "%s: Undefined Battery Status\n", __func__); + return; + } + + full_charged: +#if defined(CONFIG_TARGET_LOCALE_NAATT) + dev_info(info->dev, + "soc(%d), vfocv(%d), vcell(%d), temp(%d), charging(%d), health(%d), vf(%d)\n", + info->batt_soc, info->batt_vfocv, info->batt_vcell / 1000, + info->batt_temp / 10, info->charging_status, info->batt_health, + info->batt_vf_adc); +#else + dev_info(info->dev, + "soc(%d), vfocv(%d), vcell(%d), temp(%d), charging(%d), health(%d), chg_adc(%d)\n", + info->batt_soc, info->batt_vfocv, info->batt_vcell / 1000, + info->batt_temp / 10, info->charging_status, + info->batt_health, info->batt_current_adc); +#endif + + power_supply_changed(&info->psy_bat); + + wake_unlock(&info->monitor_wake_lock); + + return; +} + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK +static void sec_bat_vf_check_work(struct work_struct *work) +{ + struct sec_bat_info *info; + info = container_of(work, struct sec_bat_info, vf_check_work.work); + + sec_bat_check_vf(info); + +#if defined(CONFIG_TARGET_LOCALE_NAATT) + sec_bat_check_vf_adc(info); +#endif + +#if (defined(CONFIG_MACH_Q1_BD) && defined(CONFIG_SMB328_CHARGER)) + sec_bat_check_ovp(info); + + if ((info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || + (info->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE)) { + dev_info(info->dev, + "%s: Battery Disconnected or OVP activated\n", + __func__); +#else + if (info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + dev_info(info->dev, "%s: Battery Disconnected\n", __func__); +#endif + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + } + + schedule_delayed_work(&info->vf_check_work, + msecs_to_jiffies(VF_CHECK_INTERVAL)); +} +#endif + +static void sec_bat_polling_work(struct work_struct *work) +{ + struct sec_bat_info *info; + info = container_of(work, struct sec_bat_info, polling_work.work); + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + if (info->initial_check_count) { + schedule_delayed_work(&info->polling_work, HZ); + info->initial_check_count--; + } else + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->polling_interval)); +} + +#define SEC_BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = 0664 }, \ + .show = sec_bat_show_property, \ + .store = sec_bat_store, \ +} + +static struct device_attribute sec_battery_attrs[] = { + SEC_BATTERY_ATTR(batt_vol), + SEC_BATTERY_ATTR(batt_soc), + SEC_BATTERY_ATTR(batt_vfocv), + SEC_BATTERY_ATTR(batt_temp), + SEC_BATTERY_ATTR(batt_temp_adc), +#ifdef CONFIG_TARGET_LOCALE_NA + SEC_BATTERY_ATTR(batt_temp_radc), +#endif + SEC_BATTERY_ATTR(batt_temp_adc_avg), + SEC_BATTERY_ATTR(charging_source), + SEC_BATTERY_ATTR(batt_lp_charging), + SEC_BATTERY_ATTR(video), + SEC_BATTERY_ATTR(mp3), + SEC_BATTERY_ATTR(batt_type), + SEC_BATTERY_ATTR(batt_full_check), + SEC_BATTERY_ATTR(batt_temp_check), + SEC_BATTERY_ATTR(batt_temp_adc_spec), + SEC_BATTERY_ATTR(batt_test_value), + SEC_BATTERY_ATTR(batt_current_now), + SEC_BATTERY_ATTR(batt_current_adc), + SEC_BATTERY_ATTR(siop_activated), + SEC_BATTERY_ATTR(system_rev), +#ifdef CONFIG_TARGET_LOCALE_NA + SEC_BATTERY_ATTR(fg_soc), +#else + SEC_BATTERY_ATTR(fg_psoc), +#endif /* CONFIG_TARGET_LOCALE_NA */ + SEC_BATTERY_ATTR(batt_lpm_state), + SEC_BATTERY_ATTR(batt_tmu_status), +#ifdef SPRINT_SLATE_TEST + SEC_BATTERY_ATTR(slate_test_mode), +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + SEC_BATTERY_ATTR(batt_v_f_adc), + SEC_BATTERY_ATTR(batt_voice_call_2g), + SEC_BATTERY_ATTR(batt_voice_call_3g), + SEC_BATTERY_ATTR(batt_data_call), + SEC_BATTERY_ATTR(wifi), + SEC_BATTERY_ATTR(batt_gps), + SEC_BATTERY_ATTR(camera), + SEC_BATTERY_ATTR(recording), +#elif defined(CONFIG_TARGET_LOCALE_NA) + SEC_BATTERY_ATTR(call), + /*SEC_BATTERY_ATTR(video),*/ + /*SEC_BATTERY_ATTR(music),*/ + SEC_BATTERY_ATTR(browser), + SEC_BATTERY_ATTR(hotspot), + SEC_BATTERY_ATTR(camera), + SEC_BATTERY_ATTR(data_call), + SEC_BATTERY_ATTR(wimax), +#endif +}; + +enum { + BATT_VOL = 0, + BATT_SOC, + BATT_VFOCV, + BATT_TEMP, + BATT_TEMP_ADC, +#ifdef CONFIG_TARGET_LOCALE_NA + BATT_TEMP_RADC, +#endif + BATT_TEMP_ADC_AVG, + CHARGING_SOURCE, + BATT_LP_CHARGING, + BATT_VIDEO, + BATT_MP3, + BATT_TYPE, + BATT_FULL_CHECK, + BATT_TEMP_CHECK, + BATT_TEMP_ADC_SPEC, + BATT_TEST_VALUE, + BATT_CURRENT_NOW, + BATT_CURRENT_ADC, + BATT_SIOP_ACTIVATED, + BATT_SYSTEM_REV, + BATT_FG_PSOC, + BATT_LPM_STATE, + BATT_TMU_STATUS, +#ifdef SPRINT_SLATE_TEST + SLATE_TEST_MODE, +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + BATT_V_F_ADC, + BATT_VOICE_CALL_2G, + BATT_VOICE_CALL_3G, + BATT_DATA_CALL, + BATT_WIFI, + BATT_GPS, + BATT_CAMERA, + BATT_RECORDING, +#endif +#if defined(CONFIG_TARGET_LOCALE_NA) + BATT_CALL, + BATT_BROWSER, + BATT_HOTSOPT, + BATT_CAMERA, + BATT_DATA_CALL, + BATT_WIMAX, +#endif +}; + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +static void sec_bat_check_event_status(struct sec_bat_info *info, int mode, + int offset) +{ + int is_event_running = 0; + + if ((info->batt_event_status != 0) + /*&& (info->cable_type != CABLE_TYPE_NONE) */ + ) + is_event_running = 1; + + if (mode) { + if (!(info->batt_event_status & offset)) + info->batt_event_status |= offset; + } else { + if (info->batt_event_status & offset) + info->batt_event_status &= ~offset; + } + + printk(KERN_DEBUG "[%s] current batt_event_status = 0x%x\n", __func__, + info->batt_event_status); + + if ((info->batt_event_status == 0) && (is_event_running == 1)) + info->event_end_time = jiffies; + + return; +} +#elif defined(CONFIG_TARGET_LOCALE_NA) +static void sec_bat_check_event_status(struct sec_bat_info *info, int mode, + int offset) +{ + int is_event_running = 0; + + if ((info->batt_event_status != 0) + /*&& (info->cable_type != CABLE_TYPE_NONE) */ + ) + is_event_running = 1; + + if (mode) { + if (!(info->batt_event_status & offset)) + info->batt_event_status |= offset; + } else { + if (info->batt_event_status & offset) + info->batt_event_status &= ~offset; + } + + printk(KERN_DEBUG "[%s] current batt_event_status = 0x%x\n", __func__, + info->batt_event_status); + + if ((info->batt_event_status == 0) && (is_event_running == 1)) + info->event_expired_time = jiffies; + + return; +} + +int sec_bat_use_wimax(int onoff) +{ + struct sec_bat_info *info = pchg; + sec_bat_check_event_status(info, onoff, USE_WIMAX); +} +EXPORT_SYMBOL(sec_bat_use_wimax); +#endif + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_bat_info *info = dev_get_drvdata(dev->parent); + int i = 0, val; + const ptrdiff_t off = attr - sec_battery_attrs; + + switch (off) { + case BATT_VOL: + val = sec_bat_get_fuelgauge_data(info, FG_T_AVGVCELL); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_SOC: + val = sec_bat_get_fuelgauge_data(info, FG_T_SOC); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_VFOCV: + val = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->batt_temp); + break; + case BATT_TEMP_ADC: + val = s3c_read_temper_adc(info); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; +#ifdef CONFIG_TARGET_LOCALE_NA + case BATT_TEMP_RADC: + val = s3c_read_temper_adc(info); + val = sec_rescale_temp_adc(info); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_temp_radc); + break; +#endif + case BATT_TEMP_ADC_AVG: + val = info->temper_adc_sample.average_adc; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->cable_type); + break; + case BATT_LP_CHARGING: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->get_lpcharging_state()); + break; + case BATT_VIDEO: + /* TODO */ +#ifdef CONFIG_TARGET_LOCALE_NA + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_video); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); +#endif + break; + case BATT_MP3: + /* TODO */ +#ifdef CONFIG_TARGET_LOCALE_NA + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_music); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); +#endif + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "SDI_SDI"); + break; + case BATT_FULL_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (info->charging_status == + POWER_SUPPLY_STATUS_FULL) + ? 1 : 0); + break; + case BATT_TEMP_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->batt_health); + break; + case BATT_TEMP_ADC_SPEC: +#ifdef CONFIG_TARGET_LOCALE_NA + i += scnprintf(buf + i, PAGE_SIZE - i, + "(HIGH: %d - %d, LOW: %d - %d)\n", + HIGH_BLOCK_TEMP_ADC, HIGH_RECOVER_TEMP_ADC, + LOW_BLOCK_TEMP_ADC, LOW_RECOVER_TEMP_ADC); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, + "(HIGH: %d - %d, LOW: %d - %d)\n", + HIGH_BLOCK_TEMP / 10, HIGH_RECOVER_TEMP / 10, + LOW_BLOCK_TEMP / 10, LOW_RECOVER_TEMP / 10); +#endif + break; + case BATT_TEST_VALUE: + i += scnprintf(buf + i, PAGE_SIZE - i, + "0-normal, 1-full, 2-low, 3-high, 4-over, 5-cf, 6-trip (%d)\n", + info->test_value); + break; + case BATT_CURRENT_NOW: + case BATT_SIOP_ACTIVATED: + { + struct power_supply *psy = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + /* initialized as NOT charging */ + value.intval = 0; + + if ((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) + || (info->charging_status == + POWER_SUPPLY_STATUS_FULL)) + psy->get_property(psy, + POWER_SUPPLY_PROP_CURRENT_NOW, + &value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_CURRENT_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_current_adc); + break; + case BATT_SYSTEM_REV: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", system_rev); + break; + case BATT_FG_PSOC: + val = sec_bat_get_fuelgauge_data(info, FG_T_PSOC); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_LPM_STATE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_lpm_state); + break; + case BATT_TMU_STATUS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_tmu_status); + break; +#ifdef SPRINT_SLATE_TEST + case SLATE_TEST_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->slate_test_mode); + break; +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + case BATT_V_F_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_vf_adc); + break; + case BATT_VOICE_CALL_2G: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_VOICE_CALL_3G: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_DATA_CALL: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_WIFI: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_GPS: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_CAMERA: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_RECORDING: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; +#endif +#ifdef CONFIG_TARGET_LOCALE_NA + case BATT_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_call); + break; + case BATT_BROWSER: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_browser); + break; + case BATT_HOTSOPT: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_hotspot); + break; + case BATT_CAMERA: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_camera); + break; + case BATT_DATA_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_data_call); + break; + case BATT_WIMAX: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_wimax); + break; +#endif + default: + i = -EINVAL; + break; + } + + return i; +} + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = 0, x = 0; + const ptrdiff_t off = attr - sec_battery_attrs; + struct sec_bat_info *info = dev_get_drvdata(dev->parent); + + switch (off) { + case BATT_CURRENT_NOW: + if (sscanf(buf, "%d\n", &x) == 1) { + ret = count; + if ((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) + || (info->charging_status == + POWER_SUPPLY_STATUS_FULL)) { + struct power_supply *psy = + get_power_supply_by_name(info-> + sub_charger_name); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + value.intval = x; /* charging current */ + psy->set_property(psy, + POWER_SUPPLY_PROP_CURRENT_NOW, + &value); + + /* reflect changing current */ + value.intval = true; + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, + &value); + } else + pr_err("%s: NOT charging status\n", __func__); + } + break; + case BATT_SIOP_ACTIVATED: + if (sscanf(buf, "%d\n", &x) == 1) { + ret = count; + if ((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) || + (info->charging_status == + POWER_SUPPLY_STATUS_FULL)) { + struct power_supply *psy = + get_power_supply_by_name(info-> + sub_charger_name); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + if (x) { + /* charging current for SIOP */ + value.intval = 450; + } else { + switch (info->cable_type) { + case CABLE_TYPE_USB: + case CABLE_TYPE_MISC: + value.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + value.intval = 650; /* mA */ + break; + default: + dev_err(info->dev, + "%s: Invalid func use\n", + __func__); + return -EINVAL; + } + } + psy->set_property(psy, + POWER_SUPPLY_PROP_CURRENT_NOW, + &value); + + /* reflect changing current */ + value.intval = true; + psy->set_property(psy, + POWER_SUPPLY_PROP_ONLINE, + &value); + } else + pr_err("%s: NOT charging status\n", __func__); + } + break; + case BATT_VIDEO: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: video(%d)\n", __func__, x); + ret = count; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + sec_bat_check_event_status(info, x, OFFSET_VIDEO_PLAY); +#elif defined(CONFIG_TARGET_LOCALE_NA) + info->use_video = x; + sec_bat_check_event_status(info, x, USE_VIDEO); +#endif + } + break; + case BATT_MP3: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: mp3(%d)\n", __func__, x); + ret = count; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + sec_bat_check_event_status(info, x, OFFSET_MP3_PLAY); +#elif defined(CONFIG_TARGET_LOCALE_NA) + info->use_music = x; + sec_bat_check_event_status(info, x, USE_MUSIC); +#endif + } + break; + case BATT_TEST_VALUE: + if (sscanf(buf, "%d\n", &x) == 1) { + info->test_value = x; + printk(KERN_DEBUG "%s : test case : %d\n", __func__, + info->test_value); + ret = count; + } + break; + case BATT_LPM_STATE: + if (sscanf(buf, "%d\n", &x) == 1) { + info->batt_lpm_state = x; + ret = count; + } + break; +#ifdef SPRINT_SLATE_TEST + case SLATE_TEST_MODE: + if (strncmp(buf, "1", 1) == 0) { + info->slate_test_mode = true; + ret = count; + } else { + info->slate_test_mode = false; + ret = count; + } + break; +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + case BATT_VOICE_CALL_2G: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: call 2G(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, + OFFSET_VOICE_CALL_2G); + } + break; + case BATT_VOICE_CALL_3G: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: call 3G(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, + OFFSET_VOICE_CALL_3G); + } + break; + case BATT_DATA_CALL: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: data call(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_DATA_CALL); + } + break; + case BATT_WIFI: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: wifi(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_WIFI); + } + break; + case BATT_GPS: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: gps(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_GPS); + } + break; + case BATT_CAMERA: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: camera(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_CAMERA_ON); + } + break; + case BATT_RECORDING: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: recording(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, + OFFSET_RECORDING_ON); + } + break; +#endif +#ifdef CONFIG_TARGET_LOCALE_NA + case BATT_CALL: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_call = x; + dev_info(info->dev, "[NA_SPR]%s: call (%d)\n", __func__, + x); + ret = count; + sec_bat_check_event_status(info, x, USE_CALL); + } + break; + case BATT_BROWSER: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_browser = x; + dev_info(info->dev, "[NA_SPR]%s: BROWSER(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_BROWSER); + } + break; + case BATT_HOTSOPT: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_hotspot = x; + dev_info(info->dev, "[NA_SPR]%s: HOTSPOT(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_HOTSPOT); + } + break; + case BATT_CAMERA: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "[NA_SPR]%s: CAMERA(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_CAMERA); + } + break; + case BATT_DATA_CALL: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_data_call = x; + dev_info(info->dev, "[NA_SPR]%s: DATA CALL(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_DATA_CALL); + } + break; + case BATT_WIMAX: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_wimax = x; + dev_info(info->dev, "[NA_SPR]%s: WIMAX(%d)\n", __func__, + x); + ret = count; + sec_bat_check_event_status(info, x, USE_WIMAX); + } + break; +#endif + default: + ret = -EINVAL; + } + + return ret; +} + +static int sec_bat_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_battery_attrs); i++) { + rc = device_create_file(dev, &sec_battery_attrs[i]); + if (rc) + goto sec_attrs_failed; + } + goto succeed; + + sec_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); + succeed: + return rc; +} + +static int sec_bat_is_charging(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", __func__, + ret); + return ret; + } + + return value.intval; +} + +static int sec_bat_read_proc(char *buf, char **start, + off_t offset, int count, int *eof, void *data) +{ + struct sec_bat_info *info = data; + struct timespec cur_time; + ktime_t ktime; + int len = 0; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + +#ifdef CONFIG_TARGET_LOCALE_NA + len = sprintf(buf, + "%lu, %u, %u, %u, %u, %u, %d, %d, %d, %u, %u, %u, %u, %u, %u, %u, %d, %lu\n", + cur_time.tv_sec, + info->batt_raw_soc, + info->batt_soc, + info->batt_vfocv, + info->batt_vcell, + info->batt_current_adc, + info->batt_full_status, + info->charging_int_full_count, + info->charging_adc_full_count, + info->recharging_status, + info->batt_temp_adc, + info->batt_temp_radc, + info->batt_health, + info->charging_status, + info->batt_tmu_status, + info->present, + info->cable_type, info->charging_passed_time); + + return len; +} +#else + len = sprintf(buf, + "%lu, %u, %u, %u, %u, %u, %d, %d, %d, %u, %u, %u, %u, %u, %u, %d, %lu\n", + cur_time.tv_sec, + info->batt_raw_soc, + info->batt_soc, + info->batt_vfocv, + info->batt_vcell, + info->batt_current_adc, + info->batt_full_status, + info->charging_int_full_count, + info->charging_adc_full_count, + info->recharging_status, + info->batt_temp_adc, + info->batt_health, + info->charging_status, + info->batt_tmu_status, + info->present, + info->cable_type, info->charging_passed_time); + + return len; +} +#endif + +static __devinit int sec_bat_probe(struct platform_device *pdev) +{ + struct sec_bat_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct sec_bat_info *info; + struct power_supply *psy; + union power_supply_propval value; + int ret = 0; + + dev_info(&pdev->dev, "%s: SEC Battery Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + if (!pdata->fuel_gauge_name || !pdata->charger_name) { + dev_err(info->dev, "%s: no fuel gauge or charger name\n", + __func__); + goto err_kfree; + } + info->fuel_gauge_name = pdata->fuel_gauge_name; + info->charger_name = pdata->charger_name; + info->sub_charger_name = pdata->sub_charger_name; +#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) + if (system_rev <= HWREV_FOR_BATTERY) { +#else + if (system_rev >= HWREV_FOR_BATTERY) { +#endif + dev_info(&pdev->dev, "%s: use sub-charger (%s)\n", + __func__, info->sub_charger_name); + info->adc_arr_size = pdata->adc_sub_arr_size; + info->adc_table = pdata->adc_sub_table; + info->adc_channel = pdata->adc_sub_channel; + info->use_sub_charger = true; + } else { + dev_info(&pdev->dev, "%s: use main charger (%s)\n", + __func__, info->charger_name); + info->adc_arr_size = pdata->adc_arr_size; + info->adc_table = pdata->adc_table; + info->adc_channel = pdata->adc_channel; + info->use_sub_charger = false; + } + info->get_lpcharging_state = pdata->get_lpcharging_state; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->vf_adc_channel = pdata->adc_vf_channel; +#endif + + info->psy_bat.name = "battery", + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + info->psy_bat.properties = sec_battery_props, + info->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props), + info->psy_bat.get_property = sec_bat_get_property, + info->psy_bat.set_property = sec_bat_set_property, + info->psy_usb.name = "usb", + info->psy_usb.type = POWER_SUPPLY_TYPE_USB, + info->psy_usb.supplied_to = supply_list, + info->psy_usb.num_supplicants = ARRAY_SIZE(supply_list), + info->psy_usb.properties = sec_power_props, + info->psy_usb.num_properties = ARRAY_SIZE(sec_power_props), + info->psy_usb.get_property = sec_usb_get_property, + info->psy_ac.name = "ac", + info->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + info->psy_ac.supplied_to = supply_list, + info->psy_ac.num_supplicants = ARRAY_SIZE(supply_list), + info->psy_ac.properties = sec_power_props, + info->psy_ac.num_properties = ARRAY_SIZE(sec_power_props), + info->psy_ac.get_property = sec_ac_get_property; + + wake_lock_init(&info->vbus_wake_lock, WAKE_LOCK_SUSPEND, + "vbus_present"); + wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-monitor"); + wake_lock_init(&info->cable_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-cable"); + + psy = get_power_supply_by_name(info->charger_name); + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger\n", __func__); + return -ENODEV; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return -ENODEV; + } + info->present = value.intval; + + if (info->present == BAT_NOT_DETECTED) + info->batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + +#if defined(CONFIG_MACH_Q1_BD) + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; +#else + info->charging_status = sec_bat_is_charging(info); + if (info->charging_status < 0) + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; +#endif + + info->present_count = 0; + info->charging_int_full_count = 0; + info->charging_adc_full_count = 0; +#ifdef CONFIG_TARGET_LOCALE_NA + info->recharging_int_threshold_count = 0; +#endif + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + + info->initial_check_count = INIT_CHECK_COUNT; + + mutex_init(&info->adclock); + + info->padc = s3c_adc_register(pdev, NULL, NULL, 0); + info->charging_start_time = 0; + +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->batt_vf_adc = 0; + info->batt_event_status = 0; + info->event_end_time = 0xFFFFFFFF; +#elif defined(CONFIG_TARGET_LOCALE_NA) + info->use_call = 0; + info->use_video = 0; + info->use_music = 0; + info->use_browser = 0; + info->use_hotspot = 0; + info->use_camera = 0; + info->use_data_call = 0; + info->use_wimax = 0; + info->batt_event_status = 0; + info->event_expired_time = 0xFFFFFFFF; +#endif + if (info->get_lpcharging_state) { + if (info->get_lpcharging_state()) + info->polling_interval = POLLING_INTERVAL / 4; + else + info->polling_interval = POLLING_INTERVAL; + } + + info->test_value = 0; + + info->batt_tmu_status = TMU_STATUS_NORMAL; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_bat\n", + __func__); + goto err_wake_lock; + } + + ret = power_supply_register(&pdev->dev, &info->psy_usb); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_usb\n", + __func__); + goto err_supply_unreg_bat; + } + + ret = power_supply_register(&pdev->dev, &info->psy_ac); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_ac\n", __func__); + goto err_supply_unreg_usb; + } + + /* create sec detail attributes */ + sec_bat_create_attrs(info->psy_bat.dev); + + info->entry = create_proc_entry("batt_info_proc", S_IRUGO, NULL); + if (!info->entry) + dev_err(info->dev, "%s: failed to create proc_entry\n", + __func__); + else { + info->entry->read_proc = sec_bat_read_proc; + info->entry->data = (struct sec_bat_info *)info; + } + + info->monitor_wqueue = create_freezable_workqueue(dev_name(&pdev->dev)); + if (!info->monitor_wqueue) { + dev_err(info->dev, "%s: fail to create workqueue\n", __func__); + goto err_supply_unreg_ac; + } + + INIT_WORK(&info->monitor_work, sec_bat_monitor_work); + INIT_WORK(&info->cable_work, sec_bat_cable_work); + + INIT_DELAYED_WORK_DEFERRABLE(&info->polling_work, sec_bat_polling_work); + schedule_delayed_work(&info->polling_work, 0); + +#ifdef CONFIG_TARGET_LOCALE_NA + pchg = info; /* using pointer - for wimax */ +#endif + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + INIT_DELAYED_WORK_DEFERRABLE(&info->vf_check_work, + sec_bat_vf_check_work); + schedule_delayed_work(&info->vf_check_work, 0); +#endif + +#if defined(CONFIG_MACH_Q1_BD) + if (pdata->initial_check) + pdata->initial_check(); +#endif + + dev_info(info->dev, "%s: SEC Battery Driver Loaded\n", __func__); + return 0; + + err_supply_unreg_ac: + power_supply_unregister(&info->psy_ac); + err_supply_unreg_usb: + power_supply_unregister(&info->psy_usb); + err_supply_unreg_bat: + power_supply_unregister(&info->psy_bat); + err_wake_lock: + wake_lock_destroy(&info->vbus_wake_lock); + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->cable_wake_lock); + s3c_adc_release(info->padc); + mutex_destroy(&info->adclock); + err_kfree: + kfree(info); + + return ret; +} + +static int __devexit sec_bat_remove(struct platform_device *pdev) +{ + struct sec_bat_info *info = platform_get_drvdata(pdev); + + remove_proc_entry("batt_info_proc", NULL); + + flush_workqueue(info->monitor_wqueue); + destroy_workqueue(info->monitor_wqueue); + + cancel_delayed_work(&info->polling_work); +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + cancel_delayed_work(&info->vf_check_work); +#endif + + power_supply_unregister(&info->psy_bat); + power_supply_unregister(&info->psy_usb); + power_supply_unregister(&info->psy_ac); + + wake_lock_destroy(&info->vbus_wake_lock); + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->cable_wake_lock); + mutex_destroy(&info->adclock); + + s3c_adc_release(info->padc); + + kfree(info); + + return 0; +} + +static int sec_bat_suspend(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + + cancel_work_sync(&info->monitor_work); + cancel_delayed_work(&info->polling_work); +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + cancel_delayed_work(&info->vf_check_work); +#endif + + return 0; +} + +static int sec_bat_resume(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->polling_interval)); + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + schedule_delayed_work(&info->vf_check_work, + msecs_to_jiffies(VF_CHECK_INTERVAL)); +#endif + + return 0; +} + +#if defined(CONFIG_TARGET_LOCALE_NA) +static void sec_bat_shutdown(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + struct power_supply *psy_main, *psy_sub; + union power_supply_propval val_type; + int ret; + + if (!info->use_sub_charger) + return; + + psy_main = get_power_supply_by_name(info->charger_name); + psy_sub = get_power_supply_by_name(info->sub_charger_name); + + if (!psy_main || !psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return; + } + + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + ret = + psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return; + } + + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = + psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return; + } +} +#elif defined(CONFIG_MACH_Q1_BD) +static void sec_bat_shutdown(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); +#if defined(CONFIG_SMB328_CHARGER) + struct power_supply *psy_sub = + get_power_supply_by_name("smb328-charger"); + union power_supply_propval value; + int ret; + + if (!psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return; + } + + /* only for OTG */ + value.intval = false; + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CHARGE_TYPE, &value); + if (ret) { + dev_info(info->dev, "%s: fail to set OTG (%d)\n", + __func__, ret); + return; + } +#endif +} +#else +#define sec_bat_shutdown NULL +#endif + +static const struct dev_pm_ops sec_bat_pm_ops = { + .suspend = sec_bat_suspend, + .resume = sec_bat_resume, +}; + +static struct platform_driver sec_bat_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_bat_pm_ops, + .shutdown = sec_bat_shutdown, + }, + .probe = sec_bat_probe, + .remove = __devexit_p(sec_bat_remove), +}; + +static int __init sec_bat_init(void) +{ + return platform_driver_register(&sec_bat_driver); +} + +static void __exit sec_bat_exit(void) +{ + platform_driver_unregister(&sec_bat_driver); +} + +late_initcall(sec_bat_init); +module_exit(sec_bat_exit); + +MODULE_DESCRIPTION("SEC battery driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_AUTHOR("<joshua.chang@samsung.com>"); +MODULE_LICENSE("GPL"); |