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/battery | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/battery')
-rw-r--r-- | drivers/battery/Kconfig | 168 | ||||
-rw-r--r-- | drivers/battery/Makefile | 30 | ||||
-rw-r--r-- | drivers/battery/battery-factory.c | 398 | ||||
-rw-r--r-- | drivers/battery/battery-factory.h | 39 | ||||
-rw-r--r-- | drivers/battery/bq24190_charger.c | 740 | ||||
-rw-r--r-- | drivers/battery/max17042_fuelgauge.c | 2436 | ||||
-rw-r--r-- | drivers/battery/max17047_fuelgauge.c | 1210 | ||||
-rw-r--r-- | drivers/battery/max17050_fuelgauge.c | 2192 | ||||
-rw-r--r-- | drivers/battery/max77693_charger.c | 1644 | ||||
-rw-r--r-- | drivers/battery/samsung_battery.c | 1991 | ||||
-rw-r--r-- | drivers/battery/samsung_battery_s2plus.c | 1273 | ||||
-rw-r--r-- | drivers/battery/sec_battery.c | 2199 | ||||
-rw-r--r-- | drivers/battery/sec_charger.c | 395 | ||||
-rw-r--r-- | drivers/battery/sec_fuelgauge.c | 423 |
14 files changed, 15138 insertions, 0 deletions
diff --git a/drivers/battery/Kconfig b/drivers/battery/Kconfig new file mode 100644 index 0000000..acfbdb3 --- /dev/null +++ b/drivers/battery/Kconfig @@ -0,0 +1,168 @@ +menuconfig POWER_SUPPLY + tristate "Power supply class support" + help + Say Y here to enable power supply class support. This allows + power supply (batteries, AC, USB) monitoring by userspace + via sysfs and uevent (if available) and/or APM kernel interface + (if selected below). + +if POWER_SUPPLY + +config BATTERY_SEC + tristate "SAMSUNG battery driver" + depends on ARCH_S5PV310 + help + Say Y to enable samsung battery driver for devices with S5PV310 chip. + +config MAX8997_CHARGER + tristate "MAX8997 battery charger support" + depends on MFD_MAX8997 + help + Say Y here to enable support for the battery charger in the Maxim + MAX8997 PMIC. + +config BATTERY_MAX17043_FUELGAUGE + tristate "Maxim MAX17043 Fuel Gauge" + depends on I2C + help + MAX17043 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17043 is configured + to operate with a single lithium cell + +config BATTERY_MAX17042_FUELGAUGE + tristate "Maxim MAX17042 Fuel Gauge" + depends on I2C + help + MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17042 is configured + to operate with a single lithium cell + +config BATTERY_MAX17047_FUELGAUGE + tristate "Maxim MAX17047 Fuel Gauge" + depends on I2C + help + MAX17047 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17047 is configured + to operate with a single lithium cell + +config BATTERY_SMB136_CHARGER + tristate "SMB136 battery charger support" + depends on I2C + help + Say Y here to enable support for the SMB136 charger + +config BATTERY_MAX77693_CHARGER + tristate "MAX77693 battery charger support" + depends on MFD_MAX77693 && I2C + help + Say Y here to enable support for the MAX77693 charger + +config BATTERY_WPC_CHARGER + tristate "wireless charger support" + depends on BATTERY_MAX77693_CHARGER + help + Say Y here to enable support for the MAX77693 charger + +config BATTERY_SAMSUNG_P1X + tristate "samsung battery driver for P1x" + help + Say Y to include support for samsung battery driver for P1x. + + +# Fuel Gauge + +config FUELGAUGE_DUMMY + tristate "dummy fuel gauge driver" + depends on BATTERY_SAMSUNG + help + Say Y to include support for dummy fuel gauge driver. + +config FUELGAUGE_MAX17042 + tristate "MAX17042 fuel gauge driver" + depends on BATTERY_SAMSUNG_P1X + help + Say Y to include support for MAXIM MAX17042 fuel gauge driver. + +config FUELGAUGE_MAX17042_VOLTAGE_TRACKING + tristate "use MAX17042 fuel gauge only as voltage tracking" + depends on FUELGAUGE_MAX17042 + help + Say Y to use MAX17042 fuel gauge only as voltage tracking. + +config FUELGAUGE_MAX17042_COULOMB_COUNTING + tristate "use MAX17042 fuel gauge as coulomb counting (including voltage tracking)" + depends on FUELGAUGE_MAX17042 + help + Say Y to use MAX17042 fuel gauge as coulomb counting (including voltage tracking). + + +config FUELGAUGE_MAX17048 + tristate "MAX17048 fuel gauge driver" + depends on BATTERY_SAMSUNG + help + Say Y to include support for MAXIM MAX17048 fuel gauge driver. + +config FUELGAUGE_MAX17050 + tristate "MAX17050 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG_P1X + help + Say Y to include support + for MAXIM MAX17047 or MAX17050 fuel gauge driver. + This fuel-gauge can be used in voltage-tracking mode + or coulomb-counting mode. + +config FUELGAUGE_MAX17050_VOLTAGE_TRACKING + tristate "use MAX17050 fuel gauge only as voltage tracking" + default n + depends on FUELGAUGE_MAX17050 + help + Say Y to use MAX17050 fuel gauge + only as voltage tracking. + This mode is for target that consumes low current + like smart-phone. + +config FUELGAUGE_MAX17050_COULOMB_COUNTING + tristate "use MAX17050 fuel gauge as coulomb counting (including voltage tracking)" + default n + depends on FUELGAUGE_MAX17050 + help + Say Y to use MAX17050 fuel gauge + as coulomb counting (including voltage tracking). + This mode is for target that consumes high current + like tablet. + + +# Charger + +config CHARGER_DUMMY + tristate "dummy charger driver" + depends on BATTERY_SAMSUNG + help + Say Y to include support for dummy charger driver. + +config CHARGER_MAX8903 + tristate "MAX8903 charger driver" + depends on BATTERY_SAMSUNG + help + Say Y to include support for MAXIM MAX8903 charger driver. + +config CHARGER_SMB328 + tristate "SMB328 charger driver" + depends on BATTERY_SAMSUNG + help + Say Y to include support for Summit SMB328 charger driver. + +config CHARGER_BQ24190 + tristate "BQ24190 charger driver" + depends on BATTERY_SAMSUNG_P1X + help + Say Y to include support for TI BQ24190 charger driver. + +config CHARGER_BQ24191 + tristate "BQ24191 charger driver" + depends on BATTERY_SAMSUNG_P1X + help + Say Y to include support for TI BQ24191 charger driver. + +endif # POWER_SUPPLY diff --git a/drivers/battery/Makefile b/drivers/battery/Makefile new file mode 100644 index 0000000..e70ab69 --- /dev/null +++ b/drivers/battery/Makefile @@ -0,0 +1,30 @@ + +obj-$(CONFIG_BATTERY_SAMSUNG) += samsung_battery.o \ + battery-factory.o + +obj-$(CONFIG_BATTERY_SAMSUNG_S2PLUS) += samsung_battery_s2plus.o \ + battery-factory.o + +obj-$(CONFIG_MAX8997_CHARGER) += max8997-charger.o + +obj-$(CONFIG_BATTERY_MAX17043_FUELGAUGE) += max17043_fuelgauge.o +obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE) += max17042_fuelgauge.o +obj-$(CONFIG_BATTERY_MAX17047_FUELGAUGE) += max17047_fuelgauge.o + +obj-$(CONFIG_BATTERY_SMB136_CHARGER) += smb136_charger.o +obj-$(CONFIG_BATTERY_MAX77693_CHARGER) += max77693_charger.o + +obj-$(CONFIG_BATTERY_SAMSUNG_P1X) += sec_battery.o \ + sec_charger.o \ + sec_fuelgauge.o + +obj-$(CONFIG_FUELGAUGE_MAX17042) += max17042_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_MAX17048) += max17048_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_MAX17050) += max17050_fuelgauge.o + +obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o +obj-$(CONFIG_CHARGER_SMB328) += smb328_charger.o +obj-$(CONFIG_CHARGER_SMB347) += smb347_charger.o +obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o +obj-$(CONFIG_CHARGER_BQ24191) += bq24190_charger.o + diff --git a/drivers/battery/battery-factory.c b/drivers/battery/battery-factory.c new file mode 100644 index 0000000..d617a01 --- /dev/null +++ b/drivers/battery/battery-factory.c @@ -0,0 +1,398 @@ +/* + * battery-factory.c - factory mode for battery driver + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "battery-factory.h" + +static ssize_t battery_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t battery_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = S_IRUGO | S_IWUSR | S_IWGRP, \ + }, \ + .show = battery_show_property, \ + .store = battery_store_property, \ +} + +static struct device_attribute battery_attrs[] = { + BATTERY_ATTR(batt_reset_soc), + BATTERY_ATTR(batt_read_raw_soc), + BATTERY_ATTR(batt_read_adj_soc), + BATTERY_ATTR(batt_type), + BATTERY_ATTR(batt_temp_adc), + BATTERY_ATTR(batt_temp_aver), + BATTERY_ATTR(batt_temp_adc_aver), + BATTERY_ATTR(batt_vol_aver), + BATTERY_ATTR(batt_vfocv), + BATTERY_ATTR(batt_lp_charging), + BATTERY_ATTR(batt_charging_source), + BATTERY_ATTR(test_mode), + BATTERY_ATTR(batt_error_test), + BATTERY_ATTR(siop_activated), + BATTERY_ATTR(wc_status), + BATTERY_ATTR(wpc_pin_state), + + /* not use */ + BATTERY_ATTR(batt_vol_adc), + BATTERY_ATTR(batt_vol_adc_cal), + BATTERY_ATTR(batt_vol_adc_aver), + BATTERY_ATTR(batt_temp_adc_cal), + BATTERY_ATTR(batt_vf_adc), + BATTERY_ATTR(auth_battery), + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + BATTERY_ATTR(batt_temp_adc_spec), + BATTERY_ATTR(batt_sysrev), +#endif +}; + +enum { + BATT_RESET_SOC = 0, + BATT_READ_RAW_SOC, + BATT_READ_ADJ_SOC, + BATT_TYPE, + BATT_TEMP_ADC, + BATT_TEMP_AVER, + BATT_TEMP_ADC_AVER, + BATT_VOL_AVER, + BATT_VFOCV, + BATT_LP_CHARGING, + BATT_CHARGING_SOURCE, + TEST_MODE, + BATT_ERROR_TEST, + SIOP_ACTIVATED, + WC_STATUS, + WPC_PIN_STATE, + + /* not use */ + BATT_VOL_ADC, + BATT_VOL_ADC_CAL, + BATT_VOL_ADC_AVER, + BATT_TEMP_ADC_CAL, + BATT_VF_ADC, + AUTH_BATTERY, + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + BATT_TEMP_ADC_SPEC, + BATT_SYSREV, +#endif +}; + +static ssize_t battery_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct battery_info *info = dev_get_drvdata(dev->parent); + int i; + int cnt, dat, d_max, d_min, d_total; + int val; + const ptrdiff_t off = attr - battery_attrs; + pr_debug("%s: %s\n", __func__, battery_attrs[off].attr.name); + + i = 0; + val = 0; + switch (off) { + case BATT_READ_RAW_SOC: + battery_update_info(info); + val = info->battery_raw_soc; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_READ_ADJ_SOC: + battery_get_info(info, POWER_SUPPLY_PROP_CAPACITY); + val = info->battery_soc; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "SDI_SDI\n"); + break; + case BATT_TEMP_ADC: + battery_get_info(info, POWER_SUPPLY_PROP_TEMP); + val = info->battery_temper_adc; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TEMP_AVER: + val = 0; + for (cnt = 0; cnt < CNT_TEMPER_AVG; cnt++) { + msleep(100); + battery_get_info(info, POWER_SUPPLY_PROP_TEMP); + val += info->battery_temper_adc; + info->battery_temper_adc_avg = val / (cnt + 1); + } +#ifdef CONFIG_S3C_ADC + info->battery_temper_avg = info->pdata->covert_adc( + info->battery_temper_adc_avg, + info->pdata->temper_ch); +#else + info->battery_temper_avg = info->battery_temper; +#endif + val = info->battery_temper_avg; + pr_info("%s: temper avg(%d)\n", __func__, val); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TEMP_ADC_AVER: + val = info->battery_temper_adc_avg; + pr_info("%s: temper adc avg(%d)\n", __func__, val); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_VOL_AVER: /* not use POWER_SUPPLY_PROP_VOLTAGE_AVG */ + val = dat = d_max = d_min = d_total = 0; + for (cnt = 0; cnt < CNT_VOLTAGE_AVG; cnt++) { + msleep(200); + dat = battery_get_info(info, + POWER_SUPPLY_PROP_VOLTAGE_NOW); + + if (cnt != 0) { + d_max = max(dat, d_max); + d_min = min(dat, d_min); + } else + d_max = d_min = dat; + + d_total += dat; + } + val = (d_total - d_max - d_min) / (CNT_VOLTAGE_AVG - 2); + pr_info("%s: voltage avg(%d)\n", __func__, val); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_VFOCV: + battery_update_info(info); + val = info->battery_vfocv; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_LP_CHARGING: + val = info->lpm_state; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_CHARGING_SOURCE: + battery_get_info(info, POWER_SUPPLY_PROP_ONLINE); + val = info->cable_type; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case TEST_MODE: + val = info->battery_test_mode; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_ERROR_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, + "(%d): 0: normal, 1: full charged, 2: freezed, 3: overheated, 4: ovp, 5: vf\n", + info->battery_error_test); + break; + case SIOP_ACTIVATED: + val = info->siop_state; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case WC_STATUS: + case WPC_PIN_STATE: +#ifdef CONFIG_BATTERY_WPC_CHARGER + val = !gpio_get_value(GPIO_WPC_INT); +#else + val = false; +#endif + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_VOL_ADC: + case BATT_VOL_ADC_CAL: + case BATT_VOL_ADC_AVER: + case BATT_TEMP_ADC_CAL: + case BATT_VF_ADC: + case AUTH_BATTERY: + i += scnprintf(buf + i, PAGE_SIZE - i, "N/A\n"); + break; +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + case BATT_SYSREV: + val = system_rev; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TEMP_ADC_SPEC: + i += scnprintf(buf + i, PAGE_SIZE - i, + "(HIGH: %d / %d, LOW: %d / %d)\n", + info->pdata->overheat_stop_temp, + info->pdata->overheat_recovery_temp, + info->pdata->freeze_stop_temp, + info->pdata->freeze_recovery_temp); + break; +#endif + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t battery_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct battery_info *info = dev_get_drvdata(dev->parent); + int x; + int ret; + const ptrdiff_t off = attr - battery_attrs; + pr_info("%s: %s\n", __func__, battery_attrs[off].attr.name); + + x = 0; + ret = 0; + switch (off) { + case BATT_RESET_SOC: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 1) { + pr_info("%s: Reset SOC.\n", __func__); + battery_control_info(info, + POWER_SUPPLY_PROP_CAPACITY, + 1); + } else + pr_info("%s: Not supported param.\n", __func__); + ret = count; + } + break; + case TEST_MODE: + if (sscanf(buf, "%d\n", &x) == 1) { + info->battery_test_mode = x; + pr_info("%s: battery test mode: %d\n", __func__, + info->battery_test_mode); + ret = count; + } + break; + case BATT_ERROR_TEST: + if (sscanf(buf, "%d\n", &x) == 1) { + info->battery_error_test = x; + pr_info("%s: battery error test: %d\n", __func__, + info->battery_error_test); + ret = count; + } + break; + case SIOP_ACTIVATED: + if (sscanf(buf, "%d\n", &x) == 1) { + info->siop_state = x; + + if (info->siop_state == SIOP_ACTIVE) + info->siop_charge_current = + info->pdata->chg_curr_usb; + + pr_info("%s: SIOP %s\n", __func__, + (info->siop_state ? + "activated" : "deactivated")); + ret = count; + } + break; + default: + ret = -EINVAL; + } + + schedule_work(&info->monitor_work); + + return ret; +} + +void battery_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(battery_attrs); i++) { + rc = device_create_file(dev, &battery_attrs[i]); + pr_debug("%s: battery attr.: %s\n", __func__, + battery_attrs[i].attr.name); + if (rc) + goto create_attrs_failed; + } + goto succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &battery_attrs[i]); +succeed: + return; +} + +#if defined(CONFIG_TARGET_LOCALE_KOR) +int battery_info_proc(char *buf, char **start, + off_t offset, int count, int *eof, void *data) +{ + struct battery_info *info = data; + struct timespec cur_time; + ktime_t ktime; + int len = 0; + /* Guess we need no more than 100 bytes. */ + int size = 100; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + len = snprintf(buf, size, + "%lu\t%u\t%u\t%u\t%u\t%d\t%u\t%d\t%d\t%u\t" + "%u\t%u\t%u\t%u\t%u\t%u\t%d\t%u\t%u\n", + cur_time.tv_sec, + info->battery_raw_soc, + info->battery_soc, + info->battery_vcell / 1000, + info->battery_vfocv / 1000, + info->battery_full_soc, + info->battery_present, + info->battery_temper, + info->battery_temper_adc, + info->battery_health, + info->charge_real_state, + info->charge_virt_state, + info->cable_type, + info->charge_current, + info->full_charged_state, + info->recharge_phase, + info->abstimer_state, + info->monitor_interval, + info->charge_start_time); + return len; +} +#elif defined(CONFIG_MACH_M0_CTC) +int battery_info_proc(char *buf, char **start, + off_t offset, int count, int *eof, void *data) +{ + struct battery_info *info = data; + struct timespec cur_time; + ktime_t ktime; + int len = 0; + /* Guess we need no more than 100 bytes. */ + int size = 100; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + len = snprintf(buf, size, + "%lu\t%u\t%u\t%u\t%u\t%u\t%d\t%d\t%u\t" + "%u\t%u\t%u\t%u\t%u\t%u\t%d\t%u\t%u\n", + cur_time.tv_sec, + info->battery_raw_soc, + info->battery_soc, + info->battery_vcell / 1000, + info->battery_vfocv / 1000, + info->battery_present, + info->battery_temper, + info->battery_temper_adc, + info->battery_health, + info->charge_real_state, + info->charge_virt_state, + info->cable_type, + info->charge_current, + info->full_charged_state, + info->recharge_phase, + info->abstimer_state, + info->monitor_interval, + info->charge_start_time); + return len; +} +#endif diff --git a/drivers/battery/battery-factory.h b/drivers/battery/battery-factory.h new file mode 100644 index 0000000..c2f9b93 --- /dev/null +++ b/drivers/battery/battery-factory.h @@ -0,0 +1,39 @@ +/* + * battery-factory.h - factory mode for battery driver + * + * Copyright (C) 2011 Samsung Electrnoics + * SangYoung Son <hello.son@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/battery/samsung_battery.h> + +#ifdef CONFIG_SYSFS +extern void battery_create_attrs(struct device *dev); + +/* Add function prototype for supporting factory */ +extern int battery_get_info(struct battery_info *info, + enum power_supply_property property); +extern void battery_update_info(struct battery_info *info); +extern void battery_control_info(struct battery_info *info, + enum power_supply_property property, + int intval); +#endif /* CONFIG_SYSFS */ + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +extern int battery_info_proc(char *buf, char **start, + off_t offset, int count, int *eof, void *data); +#endif diff --git a/drivers/battery/bq24190_charger.c b/drivers/battery/bq24190_charger.c new file mode 100644 index 0000000..a68572c --- /dev/null +++ b/drivers/battery/bq24190_charger.c @@ -0,0 +1,740 @@ +/* + * bq24190_charger.c + * Samsung BQ24190 Charger Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG + +#include <linux/battery/sec_charger.h> + +static int bq24190_i2c_write(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + return ret; +} + +static int bq24190_i2c_read(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + return ret; +} + +#if 0 +static void bq24190_i2c_write_array(struct i2c_client *client, u8 *buf, + int size) +{ + int i; + for (i = 0; i < size; i += 3) + bq24190_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1); +} +#endif + +static int bq24190_read_regs(struct i2c_client *client, char *str) +{ + u8 data = 0; + u32 addr = 0; + + if (!client) + return -EINVAL; + + for (addr = 0; addr <= 0x0A; addr++) { + bq24190_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%02x", data); + if (addr != 0x0A) + sprintf(str+strlen(str), ", "); + } + + return 0; +} + +static int bq24190_test_read(struct i2c_client *client) +{ + struct sec_charger_info *charger; + u8 data = 0; + u32 addr = 0; + + if (!client) + return -EINVAL; + + for (addr = 0; addr <= 0x0A; addr++) { + bq24190_i2c_read(client, addr, &data); + dev_info(&client->dev, + "bq24190 addr : 0x%02x data : 0x%02x\n", + addr, data); + } + + charger = i2c_get_clientdata(client); + + dev_info(&client->dev, + "bq24190 EN(%d), nCHG(%d), nCON(%d)\n", + gpio_get_value(charger->pdata->chg_gpio_en), + gpio_get_value(charger->pdata->chg_gpio_status), + gpio_get_value(charger->pdata->bat_gpio_ta_nconnected)); + + return 0; +} + +static int bq24190_enable_charging(struct i2c_client *client) +{ + struct sec_charger_info *charger; + u8 data = 0; + + if (!client) + return -EINVAL; + + charger = i2c_get_clientdata(client); + + /* Set CE pin to LOW */ + if (charger->pdata->chg_gpio_en) + gpio_set_value(charger->pdata->chg_gpio_en, + charger->pdata->chg_polarity_en ? 1 : 0); + + /* Set register */ + bq24190_i2c_read(client, BQ24190_REG_PWRON_CFG, &data); + data = data & ~(0x3 << 4) | (0x1 << 4); + bq24190_i2c_write(client, BQ24190_REG_PWRON_CFG, &data); + + return 0; +} + +static int bq24190_disable_charging(struct i2c_client *client) +{ + struct sec_charger_info *charger; + u8 data = 0; + + if (!client) + return -EINVAL; + + charger = i2c_get_clientdata(client); + + /* Set CE pin to HIGH */ + if (charger->pdata->chg_gpio_en) + gpio_set_value(charger->pdata->chg_gpio_en, + charger->pdata->chg_polarity_en ? 0 : 1); + + /* Set register */ + bq24190_i2c_read(client, BQ24190_REG_PWRON_CFG, &data); + data = data & ~(0x3 << 4); + bq24190_i2c_write(client, BQ24190_REG_PWRON_CFG, &data); + + return 0; +} + +static int bq24190_get_status(struct i2c_client *client, u8 *status) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + bq24190_i2c_read(client, BQ24190_REG_STATUS, &data); + + *status = data; + + /* [7:6] VBUS_STAT */ + switch (data >> 6) { + case 0: + dev_dbg(&client->dev, + "%s: 0x%02x VBUS_STAT: Unknown\n", + __func__, data); + break; + case 1: + dev_dbg(&client->dev, + "%s: 0x%02x VBUS_STAT: USB host\n", + __func__, data); + break; + case 2: + dev_dbg(&client->dev, + "%s: 0x%02x VBUS_STAT: Adapter port\n", + __func__, data); + break; + case 3: + dev_dbg(&client->dev, + "%s: 0x%02x VBUS_STAT: OTG\n", + __func__, data); + break; + } + + /* [5:4] CHRG_STAT */ + switch ((data & (0x3 << 4)) >> 4) { + case 0: + dev_dbg(&client->dev, + "%s: 0x%02x CHRG_STAT: Not Charging\n", + __func__, data); + break; + case 1: + dev_dbg(&client->dev, + "%s: 0x%02x CHRG_STAT: Pre-charge\n", + __func__, data); + break; + case 2: + dev_dbg(&client->dev, + "%s: 0x%02x CHRG_STAT: Fast Charging\n", + __func__, data); + break; + case 3: + dev_dbg(&client->dev, + "%s: 0x%02x CHRG_STAT: Charge Done\n", + __func__, data); + break; + } + + /* [3] DPM_STAT */ + switch ((data & (0x1 << 3)) >> 3) { + case 0: + dev_dbg(&client->dev, + "%s: 0x%02x DPM_STAT: Not DPM\n", + __func__, data); + break; + case 1: + dev_dbg(&client->dev, + "%s: 0x%02x DPM_STAT: VINDPM or IINDPM\n", + __func__, data); + break; + } + + /* [2] PG_STAT */ + switch ((data & (0x1 << 2)) >> 2) { + case 0: + dev_dbg(&client->dev, + "%s: 0x%02x PG_STAT: Not Power Good\n", + __func__, data); + break; + case 1: + dev_dbg(&client->dev, + "%s: 0x%02x PG_STAT: Power Good\n", + __func__, data); + break; + } + + /* [1] THERM_STAT */ + switch ((data & (0x1 << 1)) >> 1) { + case 0: + dev_dbg(&client->dev, + "%s: 0x%02x THERM_STAT: Normal\n", + __func__, data); + break; + case 1: + dev_dbg(&client->dev, + "%s: 0x%02x THERM_STAT: TREG\n", + __func__, data); + break; + } + + /* [0] VSYS_STAT */ + switch (data & 0x1) { + case 0: + dev_dbg(&client->dev, + "%s: 0x%02x VSYS_STAT: BAT > VSYSMIN\n", + __func__, data); + break; + case 1: + dev_dbg(&client->dev, + "%s: 0x%02x VSYS_STAT: Battery is too low\n", + __func__, data); + break; + } + + return 0; +} + +static int bq24190_get_fault(struct i2c_client *client, u8 *fault) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + bq24190_i2c_read(client, BQ24190_REG_FAULT, &data); + + *fault = data; + + if (data == 0x00) + dev_dbg(&client->dev, "%s: 0x%02x No fault\n", + __func__, data); + else { + if (data & (0x1 << 7)) + dev_info(&client->dev, + "%s: 0x%02x watchdog timer expiration\n", + __func__, data); + if (data & (0x1 << 6)) + dev_info(&client->dev, + "%s: 0x%02x OTG fault\n", __func__, data); + if (data & (0x1 << 4)) + dev_info(&client->dev, + "%s: 0x%02x Input fault (OVP or bad source)\n", + __func__, data); + if (data & (0x2 << 4)) + dev_info(&client->dev, + "%s: 0x%02x Thermal shutdown\n", + __func__, data); + if (data & (0x3 << 4)) + dev_info(&client->dev, + "%s: 0x%02x Charge timer expiration\n", + __func__, data); + if (data & (0x1 << 3)) + dev_info(&client->dev, + "%s: 0x%02x System OVP\n", __func__, data); + if (data & (0x1 << 0)) + dev_info(&client->dev, + "%s: 0x%02x TS1 Cold\n", __func__, data); + if (data & (0x2 << 0)) + dev_info(&client->dev, + "%s: 0x%02x TS1 Hot\n", __func__, data); + if (data & (0x3 << 0)) + dev_info(&client->dev, + "%s: 0x%02x TS2 Cold\n", __func__, data); + if (data & (0x4 << 0)) + dev_info(&client->dev, + "%s: 0x%02x TS2 Hot\n", __func__, data); + if (data & (0x5 << 0)) + dev_info(&client->dev, + "%s: 0x%02x Both Cold\n", __func__, data); + if (data & (0x6 << 0)) + dev_info(&client->dev, + "%s: 0x%02x Both Hot\n", __func__, data); + if (data & (0x7 << 0)) + dev_info(&client->dev, + "%s: 0x%02x one Hot one Cold\n", + __func__, data); + } + + return 0; +} + +static int bq24190_get_charging_status(struct i2c_client *client) +{ + struct sec_charger_info *charger; + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data_status = 0; + u8 data_fault = 0; + + if (!client) + return -EINVAL; + + charger = i2c_get_clientdata(client); + + if (bq24190_get_status(client, &data_status) < 0) + dev_err(&client->dev, "%s: fail to get status\n", __func__); + + if (bq24190_get_fault(client, &data_fault) < 0) + dev_err(&client->dev, "%s: fail to get fault\n", __func__); + +#if 0 /* CHECK ME */ + /* At least one charge cycle terminated, + *Charge current < Termination Current + */ + if ((data_status & BQ24190_CHARGING_DONE) == 0x30) + status = POWER_SUPPLY_STATUS_FULL; + goto charging_status_end; + if (data_status & BQ24190_CHARGING_ENABLE) + status = POWER_SUPPLY_STATUS_CHARGING; + goto charging_status_end; + if (data_fault) + + /* if error bit check, ignore the status of charger-ic */ + status = POWER_SUPPLY_STATUS_DISCHARGING; +charging_status_end: +#endif + + return (int)status; +} + +static int bq24190_get_charging_health(struct i2c_client *client) +{ + int health = POWER_SUPPLY_HEALTH_GOOD; + u8 data_fault = 0; + + if (!client) + return -EINVAL; + + if (bq24190_get_fault(client, &data_fault) < 0) + dev_err(&client->dev, "%s: fail to get fault\n", __func__); + +#if 0 /* CHECK ME */ + if (data_fault) + pr_info("%s : Fault (0x%02x)\n", __func__, data_fault); + if ((data_fault & BQ24190_CHARGING_DONE) == 0x20) + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto charging_health_end; + if (data_fault) + + /* if error bit check, ignore the status of charger-ic */ + health = POWER_SUPPLY_HEALTH_GOOD; +charging_health_end: +#endif + + return health; +} + +static int bq24190_get_charging_current(struct i2c_client *client) +{ + u8 data = 0; + int ichg = 500; + + if (!client) + return -EINVAL; + + bq24190_i2c_read(client, BQ24190_REG_CHRG_C, &data); + + if (data & (0x1 << 7)) + ichg += 2048; + if (data & (0x1 << 6)) + ichg += 1024; + if (data & (0x1 << 5)) + ichg += 512; + if (data & (0x1 << 4)) + ichg += 256; + if (data & (0x1 << 3)) + ichg += 128; + if (data & (0x1 << 2)) + ichg += 64; + + return ichg; +} + +static int bq24190_set_charging_current( + struct i2c_client *client, int set_current) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + if (set_current > 500) { + + /* High-current mode */ + data = 0x48; + bq24190_i2c_write(client, BQ24190_REG_CHRG_C, &data); + udelay(10); + } else { + + /* USB5 */ + data = 0x00; + bq24190_i2c_write(client, BQ24190_REG_CHRG_C, &data); + udelay(10); + } + + dev_dbg(&client->dev, "%s: Set charging current as %dmA.\n", + __func__, set_current); + + return 0; +} + +static int bq24190_set_input_current_limit(struct i2c_client *client, + int charger_type) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + bq24190_i2c_read(client, BQ24190_REG_INSRC, &data); + data = data & ~(0x7); /* clear IINLIM bits */ + + switch (charger_type) { + case POWER_SUPPLY_TYPE_MAINS: + /* 2A (110) */ + data = data | (0x6); + break; + case POWER_SUPPLY_TYPE_USB: + default: + /* 500mA (010) */ + data = data | (0x2); + break; + } + + bq24190_i2c_write(client, BQ24190_REG_INSRC, &data); + + return 0; +} + +static int bq24190_set_fast_charge_current(struct i2c_client *client, + int charger_type) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + bq24190_i2c_read(client, BQ24190_REG_CHRG_C, &data); + data = data & ~(0x3F << 2); /* clear ICHG bits (111111) */ + + switch (charger_type) { + case POWER_SUPPLY_TYPE_MAINS: + /* 2036mA (011000) */ + data = data | (0x18 << 2); + break; + case POWER_SUPPLY_TYPE_USB: + default: + /* 500mA (000000) */ + data = data | (0x0 << 2); + break; + } + + bq24190_i2c_write(client, BQ24190_REG_CHRG_C, &data); + + return 0; +} + +static int bq24190_set_termination_current(struct i2c_client *client, + int charger_type) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + /* Set termination current */ + bq24190_i2c_read(client, BQ24190_REG_PCHRG_TRM_C, &data); + data = data & ~(0xF); /* clear ITERM bits */ + data = data | 0x1; /* 256mA (0001) */ + bq24190_i2c_write(client, BQ24190_REG_PCHRG_TRM_C, &data); + + /* Enable charge termination */ + bq24190_i2c_read(client, BQ24190_REG_CHRG_TRM_TMR, &data); + data = data & ~(0x1 << 7); /* clear EN_TERM bit */ + data = data | (0x1 << 7); /* Enable termination */ + bq24190_i2c_write(client, BQ24190_REG_CHRG_TRM_TMR, &data); + + return 0; +} + +static int bq24190_charger_function_control(struct i2c_client *client, + int charger_type) +{ + struct sec_charger_info *charger; + + if (!client) + return -EINVAL; + + charger = i2c_get_clientdata(client); + + if (charger_type == POWER_SUPPLY_TYPE_BATTERY) { + /* No charger */ + bq24190_disable_charging(client); + charger->is_charging = false; + } else { + /* Set charging current by charger type */ + bq24190_set_input_current_limit(client, charger_type); + bq24190_set_fast_charge_current(client, charger_type); + bq24190_set_termination_current(client, charger_type); + bq24190_enable_charging(client); + } + + return 0; +} + +static int bq24190_disable_timer(struct i2c_client *client) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + dev_dbg(&client->dev, "%s\n", __func__); + + /* Disable watchdog timer and safety timer */ + bq24190_i2c_read(client, BQ24190_REG_CHRG_TRM_TMR, &data); + data = data & ~(0x30); + data = data & ~(0x08); + bq24190_i2c_write(client, BQ24190_REG_CHRG_TRM_TMR, &data); + + return 0; +} + +static int bq24190_set_minimum_system_voltage(struct i2c_client *client) +{ + u8 data = 0; + + if (!client) + return -EINVAL; + + dev_dbg(&client->dev, "%s\n", __func__); + + /* 3.0V */ + bq24190_i2c_read(client, BQ24190_REG_PWRON_CFG, &data); + data = data & ~(0x7 << 1); /* Clear SYS_MIN bits */ + bq24190_i2c_write(client, BQ24190_REG_PWRON_CFG, &data); + + return 0; +} + +bool sec_hal_chg_init(struct i2c_client *client) +{ + bq24190_disable_timer(client); + bq24190_set_minimum_system_voltage(client); + bq24190_test_read(client); + return true; +} + +bool sec_hal_chg_suspend(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_chg_resume(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_chg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger; + + if (!client) + return false; + + charger = i2c_get_clientdata(client); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bq24190_get_charging_status(client); + dev_dbg(&client->dev, "%s: STATUS, %d\n", + __func__, val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = bq24190_get_charging_health(client); + dev_dbg(&client->dev, "%s: HEALTH, %d\n", + __func__, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (charger->charging_current) + val->intval = bq24190_get_charging_current(client); + + else + val->intval = 0; + dev_dbg(&client->dev, "%s: CURRENT_NOW, %d\n", + __func__, val->intval); + break; + default: + return false; + } + + return true; +} + +bool sec_hal_chg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger; + + if (!client) + return false; + + charger = i2c_get_clientdata(client); + + switch (psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + dev_dbg(&client->dev, "%s: ONLINE, %d\n", + __func__, val->intval); + bq24190_charger_function_control(client, val->intval); + bq24190_test_read(client); + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + dev_dbg(&client->dev, "%s: CURRENT_NOW, %d\n", + __func__, val->intval); + bq24190_set_charging_current(client, val->intval); + bq24190_test_read(client); + break; + default: + return false; + } + + return true; +} + +ssize_t sec_hal_chg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int i = 0; + char *str = NULL; + + switch (offset) { +/* case CHG_REG: */ +/* break; */ + case CHG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", + chg->reg_data); + break; + case CHG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + bq24190_read_regs(chg->client, str); + dev_dbg(&chg->client->dev, "%s: %s\n", __func__, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + } + + return i; +} + +ssize_t sec_hal_chg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int ret = 0; + int x = 0; + u8 data = 0; + + switch (offset) { + case CHG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + chg->reg_addr = x; + bq24190_i2c_read(chg->client, chg->reg_addr, &data); + chg->reg_data = data; + dev_dbg(&chg->client->dev, + "%s: (read) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, chg->reg_data); + ret = count; + } + break; + case CHG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data = (u8)x; + dev_dbg(&chg->client->dev, + "%s: (write) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, data); + bq24190_i2c_write(chg->client, + chg->reg_addr, &data); + ret = count; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + diff --git a/drivers/battery/max17042_fuelgauge.c b/drivers/battery/max17042_fuelgauge.c new file mode 100644 index 0000000..61d4174 --- /dev/null +++ b/drivers/battery/max17042_fuelgauge.c @@ -0,0 +1,2436 @@ +/* + * max17042_fuelgauge.c + * Samsung MAX17042 Fuel Gauge Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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/battery/sec_fuelgauge.h> + +#ifdef CONFIG_FUELGAUGE_MAX17042_VOLTAGE_TRACKING +static int max17042_write_reg(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + pr_err("%s: Error(%d)\n", __func__, ret); + + return ret; +} + +static int max17042_read_reg(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + pr_err("%s: Error(%d)\n", __func__, ret); + + return ret; +} + +static void max17042_write_reg_array(struct i2c_client *client, + u8 *buf, int size) +{ + int i; + + for (i = 0; i < size; i += 3) + max17042_write_reg(client, (u8) (*(buf + i)), (buf + i) + 1); +} + +static void max17042_init_regs(struct i2c_client *client) +{ + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_FILTERCFG, data) < 0) + return; + + /* Clear average vcell (12 sec) */ + data[0] &= 0x8f; + + max17042_write_reg(client, MAX17042_REG_FILTERCFG, data); +} + +static void max17042_get_version(struct i2c_client *client) +{ + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_VERSION, data) < 0) + return; + + pr_debug("MAX17042 Fuel-Gauge Ver %d%d\n", data[0], data[1]); +} + +static void max17042_alert_init(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + + /* SALRT Threshold setting */ + data[0] = fuelgauge->pdata->fuel_alert_soc; + data[1] = 0xff; + max17042_write_reg(client, MAX17042_REG_SALRT_TH, data); + + /* VALRT Threshold setting */ + data[0] = 0x00; + data[1] = 0xff; + max17042_write_reg(client, MAX17042_REG_VALRT_TH, data); + + /* TALRT Threshold setting */ + data[0] = 0x80; + data[1] = 0x7f; + max17042_write_reg(client, MAX17042_REG_TALRT_TH, data); +} + +static bool max17042_check_status(struct i2c_client *client) +{ + u8 data[2]; + bool ret = false; + + /* check if Smn was generated */ + if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) + return ret; + + pr_info("%s : status_reg(%02x%02x)\n", __func__, data[1], data[0]); + + /* minimum SOC threshold exceeded. */ + if (data[1] & (0x1 << 2)) + ret = true; + + /* clear status reg */ + if (!ret) { + data[1] = 0; + max17042_write_reg(client, MAX17042_REG_STATUS, data); + msleep(200); + } + + return ret; +} + +static int max17042_set_temperature(struct i2c_client *client, int temperature) +{ + u8 data[2]; + + data[0] = 0; + data[1] = temperature; + max17042_write_reg(client, MAX17042_REG_TEMPERATURE, data); + + pr_debug("%s: temperature to (%d)\n", __func__, temperature); + + return temperature; +} + +static int max17042_get_temperature(struct i2c_client *client) +{ + u8 data[2]; + s32 temperature = 0; + + if (max17042_read_reg(client, MAX17042_REG_TEMPERATURE, data) < 0) + return -ERANGE; + + /* data[] store 2's compliment format number */ + if (data[1] & (0x1 << 7)) { + /* Negative */ + temperature = ((~(data[1])) & 0xFF) + 1; + temperature *= (-1000); + } else { + temperature = data[1] & 0x7F; + temperature *= 1000; + temperature += data[0] * 39 / 10; + } + + pr_debug("%s: temperature (%d)\n", __func__, temperature); + + return temperature; +} + +/* soc should be 0.1% unit */ +static int max17042_get_soc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return -EINVAL; + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + pr_debug("%s : raw capacity (%d)\n", __func__, soc); + + return min(soc, 1000); +} + +static int max17042_get_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + + if (max17042_read_reg(client, MAX17042_REG_VFOCV, data) < 0) + return -EINVAL; + + vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + pr_debug("%s : vfocv (%d)\n", __func__, vfocv); + + return vfocv; +} + +static int max17042_get_vcell(struct i2c_client *client) +{ + u8 data[2]; + u32 vcell = 0; + + if (max17042_read_reg(client, MAX17042_REG_VCELL, data) < 0) + return -EINVAL; + + vcell = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + pr_debug("%s : vcell (%d)\n", __func__, vcell); + + return vcell; +} + +static int max17042_get_avgvcell(struct i2c_client *client) +{ + u8 data[2]; + u32 avgvcell = 0; + + if (max17042_read_reg(client, MAX17042_REG_AVGVCELL, data) < 0) + return -EINVAL; + + avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + pr_debug("%s : avgvcell (%d)\n", __func__, avgvcell); + + return avgvcell; +} + +static int max17042_regs[] = { + MAX17042_REG_STATUS, /* 0x00 */ + MAX17042_REG_VALRT_TH, /* 0x01 */ + MAX17042_REG_TALRT_TH, /* 0x02 */ + MAX17042_REG_SALRT_TH, /* 0x03 */ + MAX17042_REG_TEMPERATURE, /* 0x08 */ + MAX17042_REG_VCELL, /* 0x09 */ + MAX17042_REG_AVGVCELL, /* 0x19 */ + MAX17042_REG_CONFIG, /* 0x1D */ + MAX17042_REG_VERSION, /* 0x21 */ + MAX17042_REG_LEARNCFG, /* 0x28 */ + MAX17042_REG_FILTERCFG, /* 0x29 */ + MAX17042_REG_MISCCFG, /* 0x2B */ + MAX17042_REG_CGAIN, /* 0x2E */ + MAX17042_REG_RCOMP, /* 0x38 */ + MAX17042_REG_VFOCV, /* 0xFB */ + MAX17042_REG_SOC_VF, /* 0xFF */ + -1, /* end */ +}; + +static void max17042_read_regs(struct i2c_client *client, char *str) +{ + int i; + u8 data = 0; + + for (i = 0; ; i++) { + if (max17042_regs[i] == -1) + break; + + max17042_read_reg(client, max17042_regs[i], &data); + sprintf(str+strlen(str), "%04xh, ", data); + } +} + +bool sec_hal_fg_init(struct i2c_client *client) +{ + /* initialize fuel gauge registers */ + max17042_init_regs(client); + + max17042_get_version(client); + + return true; +} + +bool sec_hal_fg_suspend(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_resume(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + + /* 1. Set max17042 alert configuration. */ + max17042_alert_init(client); + + if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) + < 0) + return -1; + + /*Enable Alert (Aen = 1) */ + data[0] |= (0x1 << 2); + + max17042_write_reg(client, MAX17042_REG_CONFIG, data); + + pr_debug("%s : config_reg(%02x%02x) irq(%d)\n", + __func__, data[1], data[0], fuelgauge->pdata->fg_irq); + + return true; +} + +bool sec_hal_fg_is_fuelalerted(struct i2c_client *client) +{ + return max17042_check_status(client); +} + +bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) +{ + struct sec_fuelgauge_info *fuelgauge = irq_data; + u8 data[2]; + + /* update SOC */ + /* max17042_get_soc(fuelgauge->client); */ + + if (is_fuel_alerted) { + if (max17042_read_reg(fuelgauge->client, + MAX17042_REG_CONFIG, data) < 0) + return false; + + data[1] |= (0x1 << 3); + + max17042_write_reg(fuelgauge->client, + MAX17042_REG_CONFIG, data); + + pr_info("%s: Fuel-alert Alerted!! (%02x%02x)\n", + __func__, data[1], data[0]); + } else { + if (max17042_read_reg(fuelgauge->client, + MAX17042_REG_CONFIG, data) + < 0) + return false; + + data[1] &= (~(0x1 << 3)); + + max17042_write_reg(fuelgauge->client, + MAX17042_REG_CONFIG, data); + + pr_info("%s: Fuel-alert Released!! (%02x%02x)\n", + __func__, data[1], data[0]); + } + + max17042_read_reg(fuelgauge->client, MAX17042_REG_VCELL, data); + pr_debug("%s : MAX17042_REG_VCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_TEMPERATURE, data); + pr_debug("%s : MAX17042_REG_TEMPERATURE(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_CONFIG, data); + pr_debug("%s : MAX17042_REG_CONFIG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_VFOCV, data); + pr_debug("%s : MAX17042_REG_VFOCV(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_SOC_VF, data); + pr_debug("%s : MAX17042_REG_SOC_VF(%02x%02x)\n", + __func__, data[1], data[0]); + + pr_debug("%s : FUEL GAUGE IRQ (%d)\n", + __func__, gpio_get_value(fuelgauge->pdata->fg_irq)); + +#if 0 + max17042_read_reg(fuelgauge->client, MAX17042_REG_STATUS, data); + pr_debug("%s : MAX17042_REG_STATUS(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_VALRT_TH, data); + pr_debug("%s : MAX17042_REG_VALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_TALRT_TH, data); + pr_debug("%s : MAX17042_REG_TALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_SALRT_TH, data); + pr_debug("%s : MAX17042_REG_SALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_AVGVCELL, data); + pr_debug("%s : MAX17042_REG_AVGVCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_VERSION, data); + pr_debug("%s : MAX17042_REG_VERSION(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_LEARNCFG, data); + pr_debug("%s : MAX17042_REG_LEARNCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_MISCCFG, data); + pr_debug("%s : MAX17042_REG_MISCCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_CGAIN, data); + pr_debug("%s : MAX17042_REG_CGAIN(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(fuelgauge->client, MAX17042_REG_RCOMP, data); + pr_debug("%s : MAX17042_REG_RCOMP(%02x%02x)\n", + __func__, data[1], data[0]); +#endif + + return true; +} + +bool sec_hal_fg_full_charged(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = max17042_get_vcell(client); + break; + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (val->intval) { + case SEC_BATTEY_VOLTAGE_AVERAGE: + val->intval = max17042_get_avgvcell(client); + break; + case SEC_BATTEY_VOLTAGE_OCV: + val->intval = max17042_get_vfocv(client); + break; + } + break; + /* Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = 0; + break; + /* Average Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = 0; + break; + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = max17042_get_soc(client); + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = max17042_get_temperature(client); + break; + default: + return false; + } + return true; +} + +bool sec_hal_fg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + max17042_set_temperature(client, val->intval); + break; + default: + return false; + } + return true; +} + +ssize_t sec_hal_fg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int i = 0; + char *str = NULL; + + switch (offset) { +/* case FG_REG: */ +/* break; */ + case FG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%02x%02x\n", + fg->reg_data[1], fg->reg_data[0]); + break; + case FG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + max17042_read_regs(fg->client, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_hal_fg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int ret = 0; + int x = 0; + u8 data[2]; + + switch (offset) { + case FG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + fg->reg_addr = x; + max17042_read_reg(fg->client, + fg->reg_addr, fg->reg_data); + pr_debug("%s: (read) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, + fg->reg_data[1], fg->reg_data[0]); + } + ret = count; + break; + case FG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data[0] = (x & 0xFF00) >> 8; + data[1] = (x & 0x00FF); + pr_debug("%s: (write) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, + data[1], data[0]); + max17042_write_reg(fg->client, + fg->reg_addr, data); + } + ret = count; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#endif + +#ifdef CONFIG_FUELGAUGE_MAX17042_COULOMB_COUNTING +static int fg_get_battery_type(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + return fuelgauge->info.battery_type; +} + +static int fg_i2c_read(struct i2c_client *client, + u8 reg, u8 *data, u8 length) +{ + s32 value; + + value = i2c_smbus_read_i2c_block_data(client, reg, length, data); + if (value < 0 || value != length) { + pr_err("%s: Failed to fg_i2c_read. status = %d\n", + __func__, value); + return -1; + } + + return 0; +} + +static int fg_i2c_write(struct i2c_client *client, + u8 reg, u8 *data, u8 length) +{ + s32 value; + + value = i2c_smbus_write_i2c_block_data(client, reg, length, data); + if (value < 0) { + pr_err("%s: Failed to fg_i2c_write, error code=%d\n", + __func__, value); + return -1; + } + + return 0; +} + +static int fg_read_register(struct i2c_client *client, + u8 addr) +{ + u8 data[2]; + + if (fg_i2c_read(client, addr, data, 2) < 0) { + pr_err("%s: Failed to read addr(0x%x)\n", __func__, addr); + return -1; + } + + return (data[1] << 8) | data[0]; +} + +static int fg_write_register(struct i2c_client *client, + u8 addr, u16 w_data) +{ + u8 data[2]; + + data[0] = w_data & 0xFF; + data[1] = w_data >> 8; + + if (fg_i2c_write(client, addr, data, 2) < 0) { + pr_err("%s: Failed to write addr(0x%x)\n", __func__, addr); + return -1; + } + + return 0; +} + +static int fg_read_16register(struct i2c_client *client, + u8 addr, u16 *r_data) +{ + u8 data[32]; + int i = 0; + + if (fg_i2c_read(client, addr, data, 32) < 0) { + pr_err("%s: Failed to read addr(0x%x)\n", __func__, addr); + return -1; + } + + for (i = 0; i < 16; i++) + r_data[i] = (data[2 * i + 1] << 8) | data[2 * i]; + + return 0; +} + +static void fg_write_and_verify_register(struct i2c_client *client, + u8 addr, u16 w_data) +{ + u16 r_data; + u8 retry_cnt = 2; + + while (retry_cnt) { + fg_write_register(client, addr, w_data); + r_data = fg_read_register(client, addr); + + if (r_data != w_data) { + pr_err("%s: verification failed (addr : 0x%x, w_data : 0x%x, r_data : 0x%x)\n", + __func__, addr, w_data, r_data); + retry_cnt--; + } else + break; + } +} + +static void fg_test_print(struct i2c_client *client) +{ + u8 data[2]; + u32 average_vcell; + u16 w_data; + u32 temp; + u32 temp2; + u16 reg_data; + + if (fg_i2c_read(client, AVR_VCELL_REG, data, 2) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + average_vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + average_vcell += (temp2 << 4); + + pr_info("%s : AVG_VCELL(%d), data(0x%04x)\n", __func__, + average_vcell, (data[1]<<8) | data[0]); + + reg_data = fg_read_register(client, FULLCAP_REG); + pr_info("%s : FULLCAP(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + + reg_data = fg_read_register(client, REMCAP_REP_REG); + pr_info("%s : REMCAP_REP(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + + reg_data = fg_read_register(client, REMCAP_MIX_REG); + pr_info("%s : REMCAP_MIX(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + + reg_data = fg_read_register(client, REMCAP_AV_REG); + pr_info("%s : REMCAP_AV(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + +} + +static void fg_periodic_read(struct i2c_client *client) +{ + u8 reg; + int i; + int data[0x10]; + + for (i = 0; i < 16; i++) { + for (reg = 0; reg < 0x10; reg++) + data[reg] = fg_read_register(client, reg + i * 0x10); + + pr_debug("%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + pr_debug("%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + if (i == 4) + i = 13; + } + pr_debug("\n"); +} + +static void fg_read_regs(struct i2c_client *client, char *str) +{ + u8 reg; + int i; + int data[0x10]; + + for (i = 0; i < 16; i++) { + for (reg = 0; reg < 0x10; reg++) + data[reg] = fg_read_register(client, reg + i * 0x10); + + sprintf(str, "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + sprintf(str, "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + if (i == 4) + i = 13; + } +} + +static int fg_read_vcell(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + u32 vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VCELL_REG, data, 2) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vcell += (temp2 << 4); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s : VCELL(%d), data(0x%04x)\n", + __func__, vcell, (data[1]<<8) | data[0]); + + return vcell; +} + +static int fg_read_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VFOCV_REG, data, 2) < 0) { + pr_err("%s: Failed to read VFOCV\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vfocv = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vfocv += (temp2 << 4); + + return vfocv; +} + +static int fg_check_battery_present(struct i2c_client *client) +{ + u8 status_data[2]; + int ret = 1; + + /* 1. Check Bst bit */ + if (fg_i2c_read(client, STATUS_REG, status_data, 2) < 0) { + pr_err("%s: Failed to read STATUS_REG\n", __func__); + return 0; + } + + if (status_data[0] & (0x1 << 3)) { + pr_info("%s - addr(0x01), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + pr_info("%s : battery is absent!!\n", __func__); + ret = 0; + } + + return ret; +} + + +static int fg_read_temp(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + int temper = 0; + + if (fg_check_battery_present(client)) { + if (fg_i2c_read(client, TEMPERATURE_REG, data, 2) < 0) { + pr_err("%s: Failed to read TEMPERATURE_REG\n", + __func__); + return -1; + } + + if (data[1]&(0x1 << 7)) { + temper = ((~(data[1]))&0xFF)+1; + temper *= (-1000); + } else { + temper = data[1] & 0x7f; + temper *= 1000; + temper += data[0] * 39 / 10; + + /* Adjust temperature over 47, + * only for SDI battery type + */ + if (fg_get_battery_type(client) == SDI_BATTERY_TYPE) { + if (temper >= 47000 && temper < 60000) + temper = (temper * SDI_TRIM1_1 / 100) - + SDI_TRIM1_2; + else if (temper >= 60000) + temper = (temper * SDI_TRIM2_1 / 100) - + 51000; + } + } + } else + temper = 20000; + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s : TEMPERATURE(%d), data(0x%04x)\n", __func__, + temper, (data[1]<<8) | data[0]); + + return temper/100; +} + +static int fg_read_vfsoc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (fg_i2c_read(client, VFSOC_REG, data, 2) < 0) { + pr_err("%s: Failed to read VFSOC\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.1% unit */ +static int fg_read_soc(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + int soc; + + if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) { + pr_err("%s: Failed to read SOCREP\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + pr_debug("%s : raw capacity (%d)\n", __func__, soc); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_debug("%s : raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1]<<8) | data[0]); + + return min(soc, 1000); +} + +static int fg_read_current(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data1[2], data2[2]; + u32 temp, sign; + s32 i_current; + s32 avg_current; + + if (fg_i2c_read(client, CURRENT_REG, data1, 2) < 0) { + pr_err("%s: Failed to read CURRENT\n", __func__); + return -1; + } + + if (fg_i2c_read(client, AVG_CURRENT_REG, data2, 2) < 0) { + pr_err("%s: Failed to read AVERAGE CURRENT\n", __func__); + return -1; + } + + temp = ((data1[1]<<8) | data1[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + i_current = temp * 15625 / 100000; + if (sign) + i_current *= -1; + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + avg_current = temp * 15625 / 100000; + if (sign) + avg_current *= -1; + + if (!(fuelgauge->info.pr_cnt++ % PRINT_COUNT)) { + fg_test_print(client); + pr_info("%s : CURRENT(%dmA), AVG_CURRENT(%dmA)\n", __func__, + i_current, avg_current); + fuelgauge->info.pr_cnt = 1; + /* Read max17042's all registers every 5 minute. */ + fg_periodic_read(client); + } + + return i_current; +} + +static int fg_read_avg_current(struct i2c_client *client) +{ + u8 data2[2]; + u32 temp, sign; + s32 avg_current; + + if (fg_i2c_read(client, AVG_CURRENT_REG, data2, 2) < 0) { + pr_err("%s: Failed to read AVERAGE CURRENT\n", __func__); + return -1; + } + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + avg_current = temp * 15625 / 100000; + + if (sign) + avg_current *= -1; + + return avg_current; +} + +int fg_reset_soc(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + + /* delay for current stablization */ + msleep(500); + + pr_info("%s : Before quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vcell(client), fg_read_vfocv(client), + fg_read_vfsoc(client), fg_read_soc(client)); + pr_info("%s : Before quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(client), + fg_read_avg_current(client)); + + if (!fuelgauge->pdata->check_jig_status()) { + pr_info("%s : Return by No JIG_ON signal\n", __func__); + return 0; + } + + fg_write_register(client, CYCLES_REG, 0); + + if (fg_i2c_read(client, MISCCFG_REG, data, 2) < 0) { + pr_err("%s: Failed to read MiscCFG\n", __func__); + return -1; + } + + data[1] |= (0x1 << 2); + if (fg_i2c_write(client, MISCCFG_REG, data, 2) < 0) { + pr_err("%s: Failed to write MiscCFG\n", __func__); + return -1; + } + + msleep(250); + fg_write_register(client, FULLCAP_REG, fuelgauge->info.capacity); + msleep(500); + + pr_info("%s : After quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vcell(client), fg_read_vfocv(client), + fg_read_vfsoc(client), fg_read_soc(client)); + pr_info("%s : After quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(client), + fg_read_avg_current(client)); + fg_write_register(client, CYCLES_REG, 0x00a0); + +/* P8 is not turned off by Quickstart @3.4V + * (It's not a problem, depend on mode data) + * Power off for factory test(File system, etc..) */ +#if defined(CONFIG_MACH_P8_REV00) || defined(CONFIG_MACH_P8_REV01) || \ + defined(CONFIG_MACH_P8LTE_REV00) +#define QUICKSTART_POWER_OFF_VOLTAGE 3400 + vfocv = fg_read_vfocv(client); + if (vfocv < QUICKSTART_POWER_OFF_VOLTAGE) { + pr_info("%s: Power off condition(%d)\n", __func__, vfocv); + + fullcap = fg_read_register(client, FULLCAP_REG); + /* FullCAP * 0.009 */ + fg_write_register(client, REMCAP_REP_REG, + (u16)(fullcap * 9 / 1000)); + msleep(200); + pr_info("%s : new soc=%d, vfocv=%d\n", __func__, + fg_read_soc(client), vfocv); + } + + pr_info("%s : Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vfocv(client), + fg_read_vfsoc(client), fg_read_soc(client)); +#endif + + return 0; +} + + +int fg_reset_capacity(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + return fg_write_register(client, DESIGNCAP_REG, + fuelgauge->info.vfcapacity-1); +} + +int fg_adjust_capacity(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + + data[0] = 0; + data[1] = 0; + + /* 1. Write RemCapREP(05h)=0; */ + if (fg_i2c_write(client, REMCAP_REP_REG, data, 2) < 0) { + pr_err("%s: Failed to write RemCap_REP\n", __func__); + return -1; + } + msleep(200); + + pr_info("%s : After adjust - RepSOC(%d)\n", __func__, + fg_read_soc(client)); + + fuelgauge->info.soc_restart_flag = 1; + + return 0; +} + +void fg_low_batt_compensation(struct i2c_client *client, u32 level) +{ + int read_val; + u32 temp; + + pr_info("%s : Adjust SOCrep to %d!!\n", __func__, level); + + read_val = fg_read_register(client, FULLCAP_REG); + if (read_val < 0) + return; + + if (read_val > 2) /* 3% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0301 */ + temp = read_val * (level*100 + 1) / 10000; + else /* 1% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0090 */ + temp = read_val * (level*90) / 10000; + fg_write_register(client, REMCAP_REP_REG, (u16)temp); + + /* fuelgauge->info.low_batt_comp_flag = 1; */ +} + +static void fg_read_model_data(struct i2c_client *client) +{ + u16 data0[16], data1[16], data2[16]; + int i; + int relock_check; + + pr_info("[FG_Model] "); + + /* Unlock model access */ + fg_write_register(client, 0x62, 0x0059); + fg_write_register(client, 0x63, 0x00C4); + + /* Read model data */ + fg_read_16register(client, 0x80, data0); + fg_read_16register(client, 0x90, data1); + fg_read_16register(client, 0xa0, data2); + + /* Print model data */ + for (i = 0; i < 16; i++) + pr_info("0x%04x, ", data0[i]); + + for (i = 0; i < 16; i++) + pr_info("0x%04x, ", data1[i]); + + for (i = 0; i < 16; i++) { + if (i == 15) + pr_info("0x%04x", data2[i]); + else + pr_info("0x%04x, ", data2[i]); + } + + do { + relock_check = 0; + /* Lock model access */ + fg_write_register(client, 0x62, 0x0000); + fg_write_register(client, 0x63, 0x0000); + + /* Read model data again */ + fg_read_16register(client, 0x80, data0); + fg_read_16register(client, 0x90, data1); + fg_read_16register(client, 0xa0, data2); + + for (i = 0; i < 16; i++) { + if (data0[i] || data1[i] || data2[i]) { + pr_info("%s : data is non-zero, lock again!!\n", + __func__); + relock_check = 1; + } + } + } while (relock_check); + +} + +int fg_alert_init(struct i2c_client *client) +{ + u8 misccgf_data[2]; + u8 salrt_data[2]; + u8 config_data[2]; + u8 valrt_data[2]; + u8 talrt_data[2]; + u16 read_data = 0; + + /* Using RepSOC */ + if (fg_i2c_read(client, MISCCFG_REG, misccgf_data, 2) < 0) { + pr_err("%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + misccgf_data[0] = misccgf_data[0] & ~(0x03); + + if (fg_i2c_write(client, MISCCFG_REG, misccgf_data, 2) < 0) { + pr_info("%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } + + /* SALRT Threshold setting */ + salrt_data[1] = 0xff; + salrt_data[0] = 0x01; + if (fg_i2c_write(client, SALRT_THRESHOLD_REG, salrt_data, 2) < 0) { + pr_info("%s: Failed to write SALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + /* Reset VALRT Threshold setting (disable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = 0x00; + if (fg_i2c_write(client, VALRT_THRESHOLD_REG, valrt_data, 2) < 0) { + pr_info("%s: Failed to write VALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = fg_read_register(client, (u8)VALRT_THRESHOLD_REG); + if (read_data != 0xff00) + pr_err("%s : VALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + /* Reset TALRT Threshold setting (disable) */ + talrt_data[1] = 0x7F; + talrt_data[0] = 0x80; + if (fg_i2c_write(client, TALRT_THRESHOLD_REG, talrt_data, 2) < 0) { + pr_info("%s: Failed to write TALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = fg_read_register(client, (u8)TALRT_THRESHOLD_REG); + if (read_data != 0x7f80) + pr_err("%s : TALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + mdelay(100); + + /* Enable SOC alerts */ + if (fg_i2c_read(client, CONFIG_REG, config_data, 2) < 0) { + pr_err("%s: Failed to read CONFIG_REG\n", __func__); + return -1; + } + config_data[0] = config_data[0] | (0x1 << 2); + + if (fg_i2c_write(client, CONFIG_REG, config_data, 2) < 0) { + pr_info("%s: Failed to write CONFIG_REG\n", __func__); + return -1; + } + + return 1; +} + +static int fg_check_status_reg(struct i2c_client *client) +{ + u8 status_data[2]; + int ret = 0; + + /* 1. Check Smn was generatedread */ + if (fg_i2c_read(client, STATUS_REG, status_data, 2) < 0) { + pr_err("%s: Failed to read STATUS_REG\n", __func__); + return -1; + } + pr_info("%s - addr(0x00), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + + if (status_data[1] & (0x1 << 2)) + ret = 1; + + /* 2. clear Status reg */ + status_data[1] = 0; + if (fg_i2c_write(client, STATUS_REG, status_data, 2) < 0) { + pr_info("%s: Failed to write STATUS_REG\n", __func__); + return -1; + } + + return ret; +} + +void fg_fullcharged_compensation(struct i2c_client *client, + u32 is_recharging, u32 pre_update) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + static int new_fullcap_data; + + pr_info("%s : is_recharging(%d), pre_update(%d)\n", + __func__, is_recharging, pre_update); + + new_fullcap_data = + fg_read_register(client, FULLCAP_REG); + if (new_fullcap_data < 0) + new_fullcap_data = fuelgauge->info.capacity; + + if (new_fullcap_data > + (fuelgauge->info.capacity * 110 / 100)) { + pr_info("%s : [Case 1] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + + new_fullcap_data = + (fuelgauge->info.capacity * 110) / 100; + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else if (new_fullcap_data < + (fuelgauge->info.capacity * 70 / 100)) { + pr_info("%s : [Case 5] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + + new_fullcap_data = + (fuelgauge->info.capacity * 70) / 100; + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else { + if (new_fullcap_data > + (fuelgauge->info.previous_fullcap * 105 / 100)) { + pr_info("%s : [Case 2] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + + new_fullcap_data = + (fuelgauge->info.previous_fullcap * 105) / 100; + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else if (new_fullcap_data < + (fuelgauge->info.previous_fullcap * 90 / 100)) { + pr_info("%s : [Case 3] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + + new_fullcap_data = + (fuelgauge->info.previous_fullcap * 90) / 100; + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else { + pr_info("%s : [Case 4] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + } + } + + /* 4. Write RepSOC(06h)=100%; */ + fg_write_register(client, SOCREP_REG, (u16)(0x64 << 8)); + + /* 5. Write MixSOC(0Dh)=100%; */ + fg_write_register(client, SOCMIX_REG, (u16)(0x64 << 8)); + + /* 6. Write AVSOC(0Eh)=100%; */ + fg_write_register(client, SOCAV_REG, (u16)(0x64 << 8)); + + /* if pre_update case, skip updating PrevFullCAP value. */ + if (!pre_update) + fuelgauge->info.previous_fullcap = + fg_read_register(client, FULLCAP_REG); + + pr_info("%s : (A) FullCap = 0x%04x, RemCap = 0x%04x\n", __func__, + fg_read_register(client, FULLCAP_REG), + fg_read_register(client, REMCAP_REP_REG)); + + fg_periodic_read(client); + +} + +void fg_check_vf_fullcap_range(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + static int new_vffullcap; + u16 print_flag = 1; + + new_vffullcap = fg_read_register(client, FULLCAP_NOM_REG); + if (new_vffullcap < 0) + new_vffullcap = fuelgauge->info.vfcapacity; + + if (new_vffullcap > + (fuelgauge->info.vfcapacity * 110 / 100)) { + pr_info("%s : [Case 1] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fuelgauge->info.vfcapacity * 110) / 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else if (new_vffullcap < + (fuelgauge->info.vfcapacity * 70 / 100)) { + pr_info("%s : [Case 5] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fuelgauge->info.vfcapacity * 70) / 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else { + if (new_vffullcap > + (fuelgauge->info.previous_vffullcap * 105 / 100)) { + pr_info("%s : [Case 2] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fuelgauge->info.previous_vffullcap * 105) / + 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else if (new_vffullcap < + (fuelgauge->info.previous_vffullcap * 90 / 100)) { + pr_info("%s : [Case 3] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fuelgauge->info.previous_vffullcap * 90) / 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else { + pr_info("%s : [Case 4] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + print_flag = 0; + } + } + + /* delay for register setting (dQacc, dPacc) */ + if (print_flag) + msleep(300); + + fuelgauge->info.previous_vffullcap = + fg_read_register(client, FULLCAP_NOM_REG); + + if (print_flag) + pr_info("%s : VfFullCap(0x%04x), dQacc(0x%04x), dPacc(0x%04x)\n", + __func__, + fg_read_register(client, FULLCAP_NOM_REG), + fg_read_register(client, DQACC_REG), + fg_read_register(client, DPACC_REG)); + +} + +int fg_check_cap_corruption(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int vfsoc = fg_read_vfsoc(client); + int repsoc = fg_read_soc(client); + int mixcap = fg_read_register(client, REMCAP_MIX_REG); + int vfocv = fg_read_register(client, VFOCV_REG); + int remcap = fg_read_register(client, REMCAP_REP_REG); + int fullcapacity = fg_read_register(client, FULLCAP_REG); + int vffullcapacity = fg_read_register(client, FULLCAP_NOM_REG); + u32 temp, temp2, new_vfocv, pr_vfocv; + int ret = 0; + + /* If usgin Jig or low batt compensation flag is set, + * then skip checking. + */ + if (fuelgauge->pdata->check_jig_status()) { + pr_info("%s : Return by Using Jig(%d)\n", __func__, + fuelgauge->pdata->check_jig_status()); + return 0; + } + + if (vfsoc < 0 || repsoc < 0 || mixcap < 0 || vfocv < 0 || + remcap < 0 || fullcapacity < 0 || vffullcapacity < 0) + return 0; + + /* Check full charge learning case. */ + if (((vfsoc >= 90) && ((remcap >= (fullcapacity * 995 / 1000)) && + (remcap <= (fullcapacity * 1005 / 1000)))) || + fuelgauge->info.low_batt_comp_flag || + fuelgauge->info.soc_restart_flag) { + pr_info("%s : RemCap(%d), FullCap(%d), SOC(%d), ", + __func__, (remcap/2), + (fullcapacity/2), repsoc); + pr_info("low_batt_comp_flag(%d), soc_restart_flag(%d)\n", + fuelgauge->info.low_batt_comp_flag, + fuelgauge->info.soc_restart_flag); + fuelgauge->info.previous_repsoc = repsoc; + fuelgauge->info.previous_remcap = remcap; + fuelgauge->info.previous_fullcapacity = fullcapacity; + if (fuelgauge->info.soc_restart_flag) + fuelgauge->info.soc_restart_flag = 0; + + ret = 1; + } + + /* ocv calculation for print */ + temp = (vfocv & 0xFFF) * 78125; + pr_vfocv = temp / 1000000; + + temp = ((vfocv & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + pr_vfocv += (temp2 << 4); + + /* MixCap differ is greater than 265mAh */ + if ((((vfsoc+5) < fuelgauge->info.previous_vfsoc) || + (vfsoc > (fuelgauge->info.previous_vfsoc+5))) || + (((repsoc+5) < fuelgauge->info.previous_repsoc) || + (repsoc > (fuelgauge->info.previous_repsoc+5))) || + (((mixcap+530) < fuelgauge->info.previous_mixcap) || + (mixcap > (fuelgauge->info.previous_mixcap+530)))) { + fg_periodic_read(client); + + pr_info("[FG_Recovery] (B) VfSOC(%d), prevVfSOC(%d), RepSOC(%d), prevRepSOC(%d), MixCap(%d), prevMixCap(%d),VfOCV(0x%04x, %d)\n", + vfsoc, + fuelgauge->info.previous_vfsoc, + repsoc, fuelgauge->info.previous_repsoc, + (mixcap/2), + (fuelgauge->info.previous_mixcap/2), + vfocv, + pr_vfocv); + + mutex_lock(&fuelgauge->fg_lock); + + fg_write_and_verify_register(client, REMCAP_MIX_REG, + fuelgauge->info.previous_mixcap); + fg_write_register(client, VFOCV_REG, + fuelgauge->info.previous_vfocv); + mdelay(200); + + fg_write_and_verify_register(client, REMCAP_REP_REG, + fuelgauge->info.previous_remcap); + vfsoc = fg_read_register(client, VFSOC_REG); + fg_write_register(client, 0x60, 0x0080); + fg_write_and_verify_register(client, 0x48, vfsoc); + fg_write_register(client, 0x60, 0x0000); + + fg_write_and_verify_register(client, 0x45, + (fuelgauge->info.previous_vfcapacity / 4)); + fg_write_and_verify_register(client, 0x46, 0x3200); + fg_write_and_verify_register(client, FULLCAP_REG, + fuelgauge->info.previous_fullcapacity); + fg_write_and_verify_register(client, FULLCAP_NOM_REG, + fuelgauge->info.previous_vfcapacity); + + mutex_unlock(&fuelgauge->fg_lock); + + msleep(200); + + /* ocv calculation for print */ + new_vfocv = fg_read_register(client, VFOCV_REG); + temp = (new_vfocv & 0xFFF) * 78125; + pr_vfocv = temp / 1000000; + + temp = ((new_vfocv & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + pr_vfocv += (temp2 << 4); + + pr_info("[FG_Recovery] (A) newVfSOC(%d), newRepSOC(%d), newMixCap(%d), newVfOCV(0x%04x, %d)\n", + fg_read_vfsoc(client), + fg_read_soc(client), + (fg_read_register(client, REMCAP_MIX_REG)/2), + new_vfocv, + pr_vfocv); + + fg_periodic_read(client); + + ret = 1; + } else { + fuelgauge->info.previous_vfsoc = vfsoc; + fuelgauge->info.previous_repsoc = repsoc; + fuelgauge->info.previous_remcap = remcap; + fuelgauge->info.previous_mixcap = mixcap; + fuelgauge->info.previous_fullcapacity = fullcapacity; + fuelgauge->info.previous_vfcapacity = vffullcapacity; + fuelgauge->info.previous_vfocv = vfocv; + } + + return ret; +} + +void fg_set_full_charged(struct i2c_client *client) +{ + pr_info("[FG_Set_Full] (B) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(client, FULLCAP_REG)/2), + (fg_read_register(client, REMCAP_REP_REG)/2)); + + fg_write_register(client, FULLCAP_REG, + (u16)fg_read_register(client, REMCAP_REP_REG)); + + pr_info("[FG_Set_Full] (A) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(client, FULLCAP_REG)/2), + (fg_read_register(client, REMCAP_REP_REG)/2)); +} + +static void display_low_batt_comp_cnt(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 type_str[10]; + + if (fg_get_battery_type(client) == SDI_BATTERY_TYPE) + sprintf(type_str, "SDI"); + else if (fg_get_battery_type(client) == ATL_BATTERY_TYPE) + sprintf(type_str, "ATL"); + else + sprintf(type_str, "Unknown"); + + pr_info("Check Array(%s) : [%d, %d], [%d, %d], ", type_str, + fuelgauge->info.low_batt_comp_cnt[0][0], + fuelgauge->info.low_batt_comp_cnt[0][1], + fuelgauge->info.low_batt_comp_cnt[1][0], + fuelgauge->info.low_batt_comp_cnt[1][1]); + pr_info("[%d, %d], [%d, %d], [%d, %d]\n", + fuelgauge->info.low_batt_comp_cnt[2][0], + fuelgauge->info.low_batt_comp_cnt[2][1], + fuelgauge->info.low_batt_comp_cnt[3][0], + fuelgauge->info.low_batt_comp_cnt[3][1], + fuelgauge->info.low_batt_comp_cnt[4][0], + fuelgauge->info.low_batt_comp_cnt[4][1]); +} + +static void add_low_batt_comp_cnt(struct i2c_client *client, + int range, int level) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int i; + int j; + + /* Increase the requested count value, and reset others. */ + fuelgauge->info.low_batt_comp_cnt[range-1][level/2]++; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (i == range-1 && j == level/2) + continue; + else + fuelgauge->info.low_batt_comp_cnt[i][j] = 0; + } + } +} + +void prevent_early_poweroff(struct i2c_client *client, + int vcell, int *fg_soc) +{ + int repsoc, repsoc_data = 0; + int read_val; + u8 data[2]; + + if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) { + pr_err("%s: Failed to read SOCREP\n", __func__); + return; + } + repsoc = data[1]; + repsoc_data = ((data[1] << 8) | data[0]); + + if (repsoc_data > POWER_OFF_SOC_HIGH_MARGIN) + return; + + pr_info("%s : soc=%d%%(0x%04x), vcell=%d\n", __func__, + repsoc, repsoc_data, vcell); + if (vcell > POWER_OFF_VOLTAGE_HIGH_MARGIN) { + read_val = fg_read_register(client, FULLCAP_REG); + /* FullCAP * 0.013 */ + fg_write_register(client, REMCAP_REP_REG, + (u16)(read_val * 13 / 1000)); + msleep(200); + *fg_soc = fg_read_soc(client); + pr_info("%s : new soc=%d, vcell=%d\n", __func__, + *fg_soc, vcell); + } +} + +void reset_low_batt_comp_cnt(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + memset(fuelgauge->info.low_batt_comp_cnt, 0, + sizeof(fuelgauge->info.low_batt_comp_cnt)); +} + +static int check_low_batt_comp_condition( + struct i2c_client *client, int *nLevel) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int i; + int j; + int ret = 0; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (fuelgauge->info.low_batt_comp_cnt[i][j] >= + MAX_LOW_BATT_CHECK_CNT) { + display_low_batt_comp_cnt(client); + ret = 1; + *nLevel = j*2 + 1; + break; + } + } + } + + return ret; +} + +static int get_low_batt_threshold(struct i2c_client *client, + int range, int level, int nCurrent) +{ + int ret = 0; + + if (fg_get_battery_type(client) == SDI_BATTERY_TYPE) { + switch (range) { +/* P4W & P8 needs one more level */ +#if defined(CONFIG_MACH_P4W_REV00) || defined(CONFIG_MACH_P4W_REV01) || \ + defined(CONFIG_MACH_P8_REV00) || defined(CONFIG_MACH_P8_REV01) || \ + defined(CONFIG_MACH_P8LTE_REV00) + case 5: + if (level == 1) + ret = SDI_Range5_1_Offset + + ((nCurrent * SDI_Range5_1_Slope) / + 1000); + else if (level == 3) + ret = SDI_Range5_3_Offset + + ((nCurrent * SDI_Range5_3_Slope) / + 1000); + break; +#endif + case 4: + if (level == 1) + ret = SDI_Range4_1_Offset + + ((nCurrent * SDI_Range4_1_Slope) / + 1000); + else if (level == 3) + ret = SDI_Range4_3_Offset + + ((nCurrent * SDI_Range4_3_Slope) / + 1000); + break; + + case 3: + if (level == 1) + ret = SDI_Range3_1_Offset + + ((nCurrent * SDI_Range3_1_Slope) / + 1000); + else if (level == 3) + ret = SDI_Range3_3_Offset + + ((nCurrent * SDI_Range3_3_Slope) / + 1000); + break; + + case 2: + if (level == 1) + ret = SDI_Range2_1_Offset + + ((nCurrent * SDI_Range2_1_Slope) / + 1000); + else if (level == 3) + ret = SDI_Range2_3_Offset + + ((nCurrent * SDI_Range2_3_Slope) / + 1000); + break; + + case 1: + if (level == 1) + ret = SDI_Range1_1_Offset + + ((nCurrent * SDI_Range1_1_Slope) / + 1000); + else if (level == 3) + ret = SDI_Range1_3_Offset + + ((nCurrent * SDI_Range1_3_Slope) / + 1000); + break; + + default: + break; + } + } else if (fg_get_battery_type(client) == + ATL_BATTERY_TYPE) { + switch (range) { + case 4: + if (level == 1) + ret = ATL_Range4_1_Offset + + ((nCurrent * ATL_Range4_1_Slope) / + 1000); + else if (level == 3) + ret = ATL_Range4_3_Offset + + ((nCurrent * ATL_Range4_3_Slope) / + 1000); + break; + + case 3: + if (level == 1) + ret = ATL_Range3_1_Offset + + ((nCurrent * ATL_Range3_1_Slope) / + 1000); + else if (level == 3) + ret = ATL_Range3_3_Offset + + ((nCurrent * ATL_Range3_3_Slope) / + 1000); + break; + + case 2: + if (level == 1) + ret = ATL_Range2_1_Offset + + ((nCurrent * ATL_Range2_1_Slope) / + 1000); + else if (level == 3) + ret = ATL_Range2_3_Offset + + ((nCurrent * ATL_Range2_3_Slope) / + 1000); + break; + + case 1: + if (level == 1) + ret = ATL_Range1_1_Offset + + ((nCurrent * ATL_Range1_1_Slope) / + 1000); + else if (level == 3) + ret = ATL_Range1_3_Offset + + ((nCurrent * ATL_Range1_3_Slope) / + 1000); + break; + + default: + break; + } + } + + return ret; +} + +int low_batt_compensation(struct i2c_client *client, + int fg_soc, int fg_vcell, int fg_current) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int fg_avg_current = 0; + int fg_min_current = 0; + int new_level = 0; + int bCntReset = 0; + + /* Not charging, flag is none, Under 3.60V or 3.45V */ + if (!fuelgauge->info.low_batt_comp_flag + && (fg_vcell <= fuelgauge->info.check_start_vol)) { + fg_avg_current = fg_read_avg_current(client); + fg_min_current = min(fg_avg_current, fg_current); + + if (fg_min_current < CURRENT_RANGE_MAX) { + if (fg_soc >= 2 && + fg_vcell < get_low_batt_threshold(client, + CURRENT_RANGE_MAX_NUM, 1, fg_min_current)) + add_low_batt_comp_cnt(client, + CURRENT_RANGE_MAX_NUM, 1); + else if (fg_soc >= 4 && + fg_vcell < get_low_batt_threshold(client, + CURRENT_RANGE_MAX_NUM, 3, fg_min_current)) + add_low_batt_comp_cnt(client, + CURRENT_RANGE_MAX_NUM, 3); + else + bCntReset = 1; + } +/* P4W & P8 needs more level */ +#if defined(CONFIG_MACH_P4W_REV00) || defined(CONFIG_MACH_P4W_REV01) || \ + defined(CONFIG_MACH_P8_REV00) || defined(CONFIG_MACH_P8_REV01) || \ + defined(CONFIG_MACH_P8LTE_REV00) + else if (fg_min_current >= CURRENT_RANGE5 && + fg_min_current < CURRENT_RANGE4) { + if (fg_soc >= 2 && fg_vcell < + get_low_batt_threshold(client, + 4, 1, fg_min_current)) + add_low_batt_comp_cnt(client, 4, 1); + else if (fg_soc >= 4 && fg_vcell < + get_low_batt_threshold(client, + 4, 3, fg_min_current)) + add_low_batt_comp_cnt(client, 4, 3); + else + bCntReset = 1; + } +#endif + else if (fg_min_current >= CURRENT_RANGE4 && + fg_min_current < CURRENT_RANGE3) { + if (fg_soc >= 2 && fg_vcell < + get_low_batt_threshold(client, + 3, 1, fg_min_current)) + add_low_batt_comp_cnt(client, 3, 1); + else if (fg_soc >= 4 && fg_vcell < + get_low_batt_threshold(client, + 3, 3, fg_min_current)) + add_low_batt_comp_cnt(client, 3, 3); + else + bCntReset = 1; + } else if (fg_min_current >= CURRENT_RANGE3 && + fg_min_current < CURRENT_RANGE2) { + if (fg_soc >= 2 && fg_vcell < + get_low_batt_threshold(client, + 2, 1, fg_min_current)) + add_low_batt_comp_cnt(client, 2, 1); + else if (fg_soc >= 4 && fg_vcell < + get_low_batt_threshold(client, + 2, 3, fg_min_current)) + add_low_batt_comp_cnt(client, 2, 3); + else + bCntReset = 1; + } else if (fg_min_current >= CURRENT_RANGE2 && + fg_min_current < CURRENT_RANGE1) { + if (fg_soc >= 2 && fg_vcell < + get_low_batt_threshold(client, + 1, 1, fg_min_current)) + add_low_batt_comp_cnt(client, 1, 1); + else if (fg_soc >= 4 && fg_vcell < + get_low_batt_threshold(client, + 1, 3, fg_min_current)) + add_low_batt_comp_cnt(client, 1, 3); + else + bCntReset = 1; + } + + + if (check_low_batt_comp_condition(client, &new_level)) { +#if defined(CONFIG_MACH_P8_REV00) || \ + defined(CONFIG_MACH_P8_REV01) || \ + defined(CONFIG_MACH_P8LTE_REV00) + /* Disable 3% low battery compensation (only for P8s) */ + /* duplicated action with 1% low battery compensation */ + if (new_level < 2) +#endif + fg_low_batt_compensation(client, new_level); + reset_low_batt_comp_cnt(client); + } + + if (bCntReset) + reset_low_batt_comp_cnt(client); + + /* if compensation finished, then read SOC again!!*/ + if (fuelgauge->info.low_batt_comp_flag) { + pr_info("%s : MIN_CURRENT(%d), AVG_CURRENT(%d), CURRENT(%d), SOC(%d), VCELL(%d)\n", + __func__, fg_min_current, fg_avg_current, + fg_current, fg_soc, fg_vcell); +#if defined(CONFIG_MACH_P8_REV00) || \ + defined(CONFIG_MACH_P8_REV01) || \ + defined(CONFIG_MACH_P8LTE_REV00) + /* Do not update soc right after low battery compensation */ + /* to prevent from powering-off suddenly (only for P8s) */ + pr_info("%s : SOC is set to %d\n", + __func__, fg_read_soc(client)); +#else + fg_soc = fg_read_soc(client); + pr_info("%s : SOC is set to %d\n", __func__, fg_soc); +#endif + } + } + +#if defined(CONFIG_MACH_P4W_REV00) || defined(CONFIG_MACH_P4W_REV01) || \ + defined(CONFIG_MACH_P2_REV00) || defined(CONFIG_MACH_P2_REV01) || \ + defined(CONFIG_MACH_P2_REV02) + /* Prevent power off over 3500mV */ + prevent_early_poweroff(fg_vcell, &fg_soc); +#endif + + return fg_soc; +} + +static void fg_set_battery_type(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + u16 data; + u8 type_str[10]; + + data = fg_read_register(client, DESIGNCAP_REG); + + if ((data == sdi_vfcapacity) || (data == sdi_vfcapacity-1)) + fuelgauge->info.battery_type = SDI_BATTERY_TYPE; + else if ((data == atl_vfcapacity) || (data == atl_vfcapacity-1)) + fuelgauge->info.battery_type = ATL_BATTERY_TYPE; + else { + pr_info("%s : Unknown battery is set to SDI type.\n", __func__); + fuelgauge->info.battery_type = SDI_BATTERY_TYPE; + } + + if (fuelgauge->info.battery_type == SDI_BATTERY_TYPE) + sprintf(type_str, "SDI"); + else if (fuelgauge->info.battery_type == ATL_BATTERY_TYPE) + sprintf(type_str, "ATL"); + else + sprintf(type_str, "Unknown"); + + pr_info("%s : DesignCAP(0x%04x), Battery type(%s)\n", + __func__, data, type_str); + + switch (fuelgauge->info.battery_type) { + case ATL_BATTERY_TYPE: + fuelgauge->info.capacity = atl_capacity; + fuelgauge->info.vfcapacity = atl_vfcapacity; + break; + + case SDI_BATTERY_TYPE: + default: + fuelgauge->info.capacity = sdi_capacity; + fuelgauge->info.vfcapacity = sdi_vfcapacity; + break; + } + + /* If not initialized yet, then init threshold values. */ + if (!fuelgauge->info.check_start_vol) { + if (fuelgauge->info.battery_type == SDI_BATTERY_TYPE) + fuelgauge->info.check_start_vol = + sdi_low_bat_comp_start_vol; + else if (fuelgauge->info.battery_type == ATL_BATTERY_TYPE) + fuelgauge->info.check_start_vol = + atl_low_bat_comp_start_vol; + } + +} + +int get_fuelgauge_value(struct i2c_client *client, int data) +{ + int ret; + + switch (data) { + case FG_LEVEL: + ret = fg_read_soc(client); + break; + + case FG_TEMPERATURE: + ret = fg_read_temp(client); + break; + + case FG_VOLTAGE: + ret = fg_read_vcell(client); + break; + + case FG_CURRENT: + ret = fg_read_current(client); + break; + + case FG_CURRENT_AVG: + ret = fg_read_avg_current(client); + break; + + case FG_BATTERY_TYPE: + ret = fg_get_battery_type(client); + break; + + case FG_CHECK_STATUS: + ret = fg_check_status_reg(client); + break; + + case FG_VF_SOC: + ret = fg_read_vfsoc(client); + break; + + default: + ret = -1; + break; + } + + return ret; +} + +static bool check_UV_charging_case(struct i2c_client *client) +{ + int fg_vcell = get_fuelgauge_value(client, FG_VOLTAGE); + int fg_current = get_fuelgauge_value(client, FG_CURRENT); + int threshold = 0; + + if (get_fuelgauge_value(client, FG_BATTERY_TYPE) + == SDI_BATTERY_TYPE) + threshold = 3300 + ((fg_current * 17) / 100); + else if (get_fuelgauge_value(client, FG_BATTERY_TYPE) + == ATL_BATTERY_TYPE) + threshold = 3300 + ((fg_current * 13) / 100); + + if (fg_vcell <= threshold) + return 1; + else + return 0; +} + +static int get_fuelgauge_soc(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + union power_supply_propval value; + int fg_soc; + int fg_vfsoc; + int fg_vcell; + int fg_current; + int avg_current; + int recover_flag = 0; + + recover_flag = fg_check_cap_corruption(client); + + /* check VFcapacity every five minutes */ + if (!(fuelgauge->info.fg_chk_cnt++ % 10)) { + fg_check_vf_fullcap_range(client); + fuelgauge->info.fg_chk_cnt = 1; + } + + fg_soc = get_fuelgauge_value(client, FG_LEVEL); + if (fg_soc < 0) { + pr_info("Can't read soc!!!"); + fg_soc = fuelgauge->info.soc; + } + + if (!fuelgauge->pdata->check_jig_status() && + !fuelgauge->info.low_batt_comp_flag) { + if (((fg_soc+5) < fuelgauge->info.previous_repsoc) || + (fg_soc > (fuelgauge->info.previous_repsoc+5))) + fuelgauge->info.fg_skip = 1; + } + + /* skip one time (maximum 30 seconds) because of corruption. */ + if (fuelgauge->info.fg_skip) { + pr_info("%s: skip update until corruption check " + "is done (fg_skip_cnt:%d)\n", + __func__, ++fuelgauge->info.fg_skip_cnt); + fg_soc = fuelgauge->info.soc; + if (recover_flag || fuelgauge->info.fg_skip_cnt > 10) { + fuelgauge->info.fg_skip = 0; + fuelgauge->info.fg_skip_cnt = 0; + } + } + + if (fuelgauge->info.low_batt_boot_flag) { + fg_soc = 0; + + if (fuelgauge->pdata->check_cable_callback() != + POWER_SUPPLY_TYPE_BATTERY && + !check_UV_charging_case(client)) { + fg_adjust_capacity(client); + fuelgauge->info.low_batt_boot_flag = 0; + } + + if (fuelgauge->pdata->check_cable_callback() == + POWER_SUPPLY_TYPE_BATTERY) + fuelgauge->info.low_batt_boot_flag = 0; + } + + fg_vcell = get_fuelgauge_value(client, FG_VOLTAGE); + fg_current = get_fuelgauge_value(client, FG_CURRENT); + avg_current = get_fuelgauge_value(client, FG_CURRENT_AVG); + fg_vfsoc = get_fuelgauge_value(client, FG_VF_SOC); + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + /* P4-Creative does not set full flag by force */ +#if !defined(CONFIG_MACH_P4W_REV00) && !defined(CONFIG_MACH_P4W_REV01) + /* Algorithm for reducing time to fully charged (from MAXIM) */ + if (value.intval != SEC_BATTERY_CHARGING_NONE && + value.intval != SEC_BATTERY_CHARGING_RECHARGING && + fuelgauge->cable_type != POWER_SUPPLY_TYPE_USB && + /* Skip when first check after boot up */ + !fuelgauge->info.is_first_check && + (fg_vfsoc > VFSOC_FOR_FULLCAP_LEARNING && + (fg_current > LOW_CURRENT_FOR_FULLCAP_LEARNING && + fg_current < HIGH_CURRENT_FOR_FULLCAP_LEARNING) && + (avg_current > LOW_AVGCURRENT_FOR_FULLCAP_LEARNING && + avg_current < HIGH_AVGCURRENT_FOR_FULLCAP_LEARNING))) { + + if (fuelgauge->info.full_check_flag == 2) { + pr_info("%s: force fully charged SOC !! (%d)", + __func__, fuelgauge->info.full_check_flag); + fg_set_full_charged(client); + fg_soc = get_fuelgauge_value(client, FG_LEVEL); + } else if (fuelgauge->info.full_check_flag < 2) + pr_info("%s: full_check_flag (%d)", + __func__, fuelgauge->info.full_check_flag); + + /* prevent overflow */ + if (fuelgauge->info.full_check_flag++ > 10000) + fuelgauge->info.full_check_flag = 3; + } else + fuelgauge->info.full_check_flag = 0; +#endif + + /* Checks vcell level and tries to compensate SOC if needed.*/ + /* If jig cable is connected, then skip low batt compensation check. */ + if (!fuelgauge->pdata->check_jig_status() && + value.intval == SEC_BATTERY_CHARGING_NONE) + fg_soc = low_batt_compensation( + client, fg_soc, fg_vcell, fg_current); + + if (fuelgauge->info.is_first_check) + fuelgauge->info.is_first_check = false; + + fuelgauge->info.soc = fg_soc; + return fg_soc; +} + +static void full_comp_work_handler(struct work_struct *work) +{ + struct sec_fg_info *fg_info = + container_of(work, struct sec_fg_info, full_comp_work.work); + struct sec_fuelgauge_info *fuelgauge = + container_of(fg_info, struct sec_fuelgauge_info, info); + int avg_current; + union power_supply_propval value; + + avg_current = get_fuelgauge_value(fuelgauge->client, FG_CURRENT_AVG); + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + if (avg_current >= 25) { + cancel_delayed_work(&fuelgauge->info.full_comp_work); + schedule_delayed_work(&fuelgauge->info.full_comp_work, 100); + } else { + pr_info("%s: full charge compensation start (avg_current %d)\n", + __func__, avg_current); + fg_fullcharged_compensation(fuelgauge->client, + (int)(value.intval == + SEC_BATTERY_CHARGING_RECHARGING), 0); + } +} + +bool sec_hal_fg_init(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = + i2c_get_clientdata(client); + + fuelgauge->info.is_first_check = true; + + fg_set_battery_type(client); + + /* Init parameters to prevent wrong compensation. */ + fuelgauge->info.previous_fullcap = + fg_read_register(client, FULLCAP_REG); + fuelgauge->info.previous_vffullcap = + fg_read_register(client, FULLCAP_NOM_REG); + /* Init FullCAP of first full charging. */ + fuelgauge->info.full_charged_cap = + fuelgauge->info.previous_fullcap; + + fuelgauge->info.previous_vfsoc = + fg_read_vfsoc(client); + fuelgauge->info.previous_repsoc = + fg_read_soc(client); + fuelgauge->info.previous_remcap = + fg_read_register(client, REMCAP_REP_REG); + fuelgauge->info.previous_mixcap = + fg_read_register(client, REMCAP_MIX_REG); + fuelgauge->info.previous_vfocv = + fg_read_register(client, VFOCV_REG); + fuelgauge->info.previous_fullcapacity = + fuelgauge->info.previous_fullcap; + fuelgauge->info.previous_vfcapacity = + fuelgauge->info.previous_vffullcap; + + fg_read_model_data(client); + fg_periodic_read(client); + + if (fuelgauge->pdata->check_cable_callback() == + POWER_SUPPLY_TYPE_BATTERY && + check_UV_charging_case(client)) + fuelgauge->info.low_batt_boot_flag = 1; + + INIT_DELAYED_WORK(&fuelgauge->info.full_comp_work, + full_comp_work_handler); + + return true; +} + +bool sec_hal_fg_suspend(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_resume(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc) +{ + return true; +} + +bool sec_hal_fg_is_fuelalerted(struct i2c_client *client) +{ + return false; +} + +bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) +{ + return true; +} + +bool sec_hal_fg_full_charged(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = + i2c_get_clientdata(client); + union power_supply_propval value; + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + /* full charge compensation algorithm by MAXIM */ + fg_fullcharged_compensation(client, + (int)(value.intval == SEC_BATTERY_CHARGING_RECHARGING), 1); + + cancel_delayed_work(&fuelgauge->info.full_comp_work); + schedule_delayed_work(&fuelgauge->info.full_comp_work, 100); + + return false; +} + +bool sec_hal_fg_reset(struct i2c_client *client) +{ + if (!fg_reset_soc(client)) + return true; + else + return false; +} + +bool sec_hal_fg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_fuelgauge_value(client, FG_VOLTAGE); + break; + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (val->intval) { + case SEC_BATTEY_VOLTAGE_AVERAGE: + val->intval = 0; + break; + case SEC_BATTEY_VOLTAGE_OCV: + val->intval = fg_read_vfocv(client); + break; + } + break; + /* Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_fuelgauge_value(client, FG_CURRENT); + break; + /* Average Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = get_fuelgauge_value(client, FG_CURRENT_AVG); + break; + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_fuelgauge_soc(client); + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = get_fuelgauge_value(client, FG_TEMPERATURE); + break; + default: + return false; + } + return true; +} + +bool sec_hal_fg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_fuelgauge_info *fuelgauge = + i2c_get_clientdata(client); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval != POWER_SUPPLY_TYPE_BATTERY) { + fuelgauge->info.low_batt_comp_flag = 0; + reset_low_batt_comp_cnt(client); + } + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + break; + default: + return false; + } + return true; +} + +ssize_t sec_hal_fg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int i = 0; + char *str = NULL; + + switch (offset) { +/* case FG_REG: */ +/* break; */ + case FG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%02x%02x\n", + fg->reg_data[1], fg->reg_data[0]); + break; + case FG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + fg_read_regs(fg->client, str); + pr_debug("%s: %s\n", __func__, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_hal_fg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int ret = 0; + int x = 0; + u8 data[2]; + + switch (offset) { + case FG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + fg->reg_addr = x; + fg_i2c_read(fg->client, + fg->reg_addr, fg->reg_data, 2); + pr_debug("%s: (read) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, + fg->reg_data[1], fg->reg_data[0]); + } + ret = count; + break; + case FG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data[0] = (x & 0xFF00) >> 8; + data[1] = (x & 0x00FF); + pr_debug("%s: (write) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, data[1], data[0]); + fg_i2c_write(fg->client, + fg->reg_addr, data, 2); + } + ret = count; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#endif + diff --git a/drivers/battery/max17047_fuelgauge.c b/drivers/battery/max17047_fuelgauge.c new file mode 100644 index 0000000..ae462a6 --- /dev/null +++ b/drivers/battery/max17047_fuelgauge.c @@ -0,0 +1,1210 @@ +/* + * max17047_fuelgauge.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@samsung.com> + * + * based on max17040_battery.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/battery/samsung_battery.h> +#include <linux/battery/max17047_fuelgauge.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <plat/gpio-cfg.h> +#include <linux/rtc.h> +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#endif +/* adjust full soc */ +#define FULL_SOC_DEFAULT 9850 +#define FULL_SOC_LOW 9700 +#define FULL_SOC_HIGH 10000 +#define KEEP_SOC_DEFAULT 50 /* 0.5% */ +#endif + +/* TRIM ERROR DETECTION */ +#define USE_TRIM_ERROR_DETECTION + +/* MAX17047 Registers. */ +#define MAX17047_REG_STATUS 0x00 +#define MAX17047_REG_VALRT_TH 0x01 +#define MAX17047_REG_TALRT_TH 0x02 +#define MAX17047_REG_SALRT_TH 0x03 +#define MAX17047_REG_VCELL 0x09 +#define MAX17047_REG_TEMPERATURE 0x08 +#define MAX17047_REG_AVGVCELL 0x19 +#define MAX17047_REG_CONFIG 0x1D +#define MAX17047_REG_VERSION 0x21 +#define MAX17047_REG_LEARNCFG 0x28 +#define MAX17047_REG_FILTERCFG 0x29 +#define MAX17047_REG_MISCCFG 0x2B +#define MAX17047_REG_CGAIN 0x2E +#define MAX17047_REG_RCOMP 0x38 +#define MAX17047_REG_VFOCV 0xFB +#define MAX17047_REG_SOC_VF 0xFF + +/* Polling work */ +#undef DEBUG_FUELGAUGE_POLLING +#define MAX17047_POLLING_INTERVAL 10000 + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +static void max17047_adjust_fullsoc(struct i2c_client *client); +#endif + +struct max17047_fuelgauge_data { + struct i2c_client *client; + struct max17047_platform_data *pdata; + + struct power_supply fuelgauge; + + /* workqueue */ + struct delayed_work update_work; +#ifdef DEBUG_FUELGAUGE_POLLING + struct delayed_work polling_work; +#endif + + /* mutex */ + struct mutex irq_lock; + + /* wakelock */ + struct wake_lock update_wake_lock; + + unsigned int irq; + + unsigned int vcell; + unsigned int avgvcell; + unsigned int vfocv; + unsigned int soc; + unsigned int rawsoc; + unsigned int temperature; + +#if defined(CONFIG_TARGET_LOCALE_KOR) + int full_soc; +#ifdef CONFIG_DEBUG_FS + struct dentry *fg_debugfs_dir; +#endif +#elif defined(CONFIG_MACH_M0_CTC) + int full_soc; +#endif + +#ifdef CONFIG_HIBERNATION + u8 *reg_dump; +#endif + +}; + +static int max17047_i2c_read(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + if (ret < 0) + pr_err("%s: err %d, reg: 0x%02x\n", __func__, ret, reg); + + return ret; +} + +static int max17047_i2c_write(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + if (ret < 0) + pr_err("%s: err %d, reg: 0x%02x, data: 0x%x%x\n", __func__, + ret, reg, buf[0], buf[1]); + + return ret; +} + +static void max17047_test_read(struct max17047_fuelgauge_data *fg_data) +{ + int reg; + u8 data[2]; + int i; + u8 buf[673]; + + struct timespec ts; + struct rtc_time tm; + pr_info("%s\n", __func__); + + getnstimeofday(&ts); + rtc_time_to_tm(ts.tv_sec, &tm); + + pr_info("%s: %d/%d/%d %02d:%02d\n", __func__, + tm.tm_mday, + tm.tm_mon + 1, + tm.tm_year + 1900, + tm.tm_hour, + tm.tm_min); + + i = 0; + for (reg = 0; reg < 0x50; reg++) { + if (!(reg & 0xf)) + i += sprintf(buf + i, "\n%02x| ", reg); + max17047_i2c_read(fg_data->client, reg, data); + i += sprintf(buf + i, "%02x%02x ", data[1], data[0]); + } + for (reg = 0xe0; reg < 0x100; reg++) { + if (!(reg & 0xf)) + i += sprintf(buf + i, "\n%02x| ", reg); + max17047_i2c_read(fg_data->client, reg, data); + i += sprintf(buf + i, "%02x%02x ", data[1], data[0]); + } + + pr_info(" 0 1 2 3 4 5 6 7"); + pr_cont(" 8 9 a b c d e f"); + pr_cont("%s\n", buf); +} + +static int max17047_get_temperature(struct i2c_client *client) +{ + /* If temperature gauging from fuelgauge, implement here */ + + return 300; +} + +/* max17047_get_XXX(); Return current value and update data value */ +static int max17047_get_vfocv(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + u32 vfocv; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_VFOCV, data); + if (ret < 0) + return ret; + + vfocv = fg_data->vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + pr_debug("%s: VFOCV(0x%02x%02x, %d)\n", __func__, + data[1], data[0], vfocv); + return vfocv * 1000; +} + +static int max17047_get_vcell(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + u32 vcell; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_VCELL, data); + if (ret < 0) + return ret; + + vcell = fg_data->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + pr_debug("%s: VCELL(0x%02x%02x, %d)\n", __func__, + data[1], data[0], vcell); + return vcell; +} + +static int max17047_get_avgvcell(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + u32 avgvcell; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_AVGVCELL, data); + if (ret < 0) + return ret; + + avgvcell = fg_data->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + pr_debug("%s: AVGVCELL(0x%02x%02x, %d)\n", __func__, + data[1], data[0], avgvcell); + return avgvcell; +} + +static int max17047_get_rawsoc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + int rawsoc; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_SOC_VF, data); + if (ret < 0) + return ret; + + rawsoc = fg_data->rawsoc = (data[1] * 100) + (data[0] * 100 / 256); + + pr_debug("%s: RAWSOC(0x%02x%02x, %d)\n", __func__, + data[1], data[0], rawsoc); + return rawsoc; +} + +static int max17047_get_soc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + int rawsoc, soc; + pr_debug("%s\n", __func__); + + rawsoc = max17047_get_rawsoc(fg_data->client); + +#if defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) + soc = fg_data->soc + = (rawsoc < 300) ? 0 : ((rawsoc - 300) * 100 / 9200) + 1; +#else +#if defined(CONFIG_MACH_C1_KOR_SKT) || \ + defined(CONFIG_MACH_C1_KOR_KT) || \ + defined(CONFIG_MACH_C1_KOR_LGT) + if (fg_data->full_soc <= 0) + fg_data->full_soc = FULL_SOC_DEFAULT; + + soc = fg_data->soc = + ((rawsoc < 0) ? 0 : (min((rawsoc * 100 / + fg_data->full_soc), 100))); +#elif defined(CONFIG_MACH_M0_KOR_SKT) || \ + defined(CONFIG_MACH_M0_KOR_KT) + if (fg_data->full_soc <= 0) + fg_data->full_soc = FULL_SOC_DEFAULT; + + soc = fg_data->soc = + ((rawsoc < 29) ? 0 : (min(((rawsoc - 29) * 100 / + (fg_data->full_soc - 29)), 100))); +#elif defined(CONFIG_MACH_M0_CTC) + if (fg_data->full_soc <= 0) + fg_data->full_soc = FULL_SOC_DEFAULT; + + soc = fg_data->soc = + ((rawsoc < 29) ? 0 : (min(((rawsoc - 29) * 100 / + (fg_data->full_soc - 29)), 100))); +#else + /* prevent minus value and add 0.5% up raw soc */ + soc = fg_data->soc = + ((rawsoc < 29) ? 0 : (min(((rawsoc - 29) * 100 / + (10000 - 79)), 100))); +#endif +#endif + + pr_debug("%s: SOC(%d, %d)\n", __func__, soc, rawsoc); + return soc; +} + +static void max17047_reset_soc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + u8 data[2]; + pr_info("%s: Before quick-start - " + "VFOCV(%d), VFSOC(%d), SOC(%d)\n", __func__, + max17047_get_vfocv(client), + max17047_get_rawsoc(client), + max17047_get_soc(client)); + max17047_test_read(fg_data); + + if (max17047_i2c_read(client, MAX17047_REG_MISCCFG, data) < 0) + return; + + /* Set bit10 makes quick start */ + data[1] |= (0x1 << 2); + max17047_i2c_write(client, MAX17047_REG_MISCCFG, data); + + msleep(500); + + pr_info("%s: After quick-start - " + "VFOCV(%d), VFSOC(%d), SOC(%d)\n", __func__, + max17047_get_vfocv(client), + max17047_get_rawsoc(client), + max17047_get_soc(client)); + max17047_test_read(fg_data); + + return; +} + +/* SOC% alert, disabled(0xFF00) */ +static void max17047_set_salrt(struct max17047_fuelgauge_data *fg_data, + u8 min, u8 max) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: min(%d%%), max(%d%%)\n", __func__, min, max); + + i2c_data[1] = max; + i2c_data[0] = min; + max17047_i2c_write(client, MAX17047_REG_SALRT_TH, i2c_data); + + max17047_i2c_read(client, MAX17047_REG_SALRT_TH, i2c_data); + if ((i2c_data[0] != min) || (i2c_data[1] != max)) + pr_err("%s: SALRT_TH is not valid (0x%02d%02d ? 0x%02d%02d)\n", + __func__, i2c_data[1], i2c_data[0], max, min); +} + +/* Temperature alert, disabled(0x7F80) */ +static void max17047_set_talrt(struct max17047_fuelgauge_data *fg_data, + u8 min, u8 max) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: min(0x%02x), max(0x%02x)\n", __func__, min, max); + + i2c_data[1] = max; + i2c_data[0] = min; + max17047_i2c_write(client, MAX17047_REG_TALRT_TH, i2c_data); + + max17047_i2c_read(client, MAX17047_REG_TALRT_TH, i2c_data); + if ((i2c_data[0] != min) || (i2c_data[1] != max)) + pr_err("%s: TALRT_TH is not valid (0x%02d%02d ? 0x%02d%02d)\n", + __func__, i2c_data[1], i2c_data[0], max, min); +} + +/* Voltage alert, disabled(0xFF00) */ +static void max17047_set_valrt(struct max17047_fuelgauge_data *fg_data, + u8 min, u8 max) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: min(%dmV), max(%dmV)\n", __func__, min * 20, max * 20); + + i2c_data[1] = max; + i2c_data[0] = min; + max17047_i2c_write(client, MAX17047_REG_VALRT_TH, i2c_data); + + max17047_i2c_read(client, MAX17047_REG_VALRT_TH, i2c_data); + if ((i2c_data[0] != min) || (i2c_data[1] != max)) + pr_err("%s: VALRT_TH is not valid (0x%02d%02d ? 0x%02d%02d)\n", + __func__, i2c_data[1], i2c_data[0], max, min); +} + +static void max17047_alert_init(struct max17047_fuelgauge_data *fg_data) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_debug("%s\n", __func__); + + /* SALRT Threshold setting */ + /* min 1%, max disable */ + max17047_set_salrt(fg_data, 0x01, 0xFF); + + /* TALRT Threshold setting */ + /* min disable, max disable */ + max17047_set_talrt(fg_data, 0x80, 0x7F); + + /* VALRT Threshold setting */ + /* min disable, max disable */ + max17047_set_valrt(fg_data, 0x00, 0xFF); + + /* Enable SOC alerts */ + max17047_i2c_read(client, MAX17047_REG_CONFIG, i2c_data); + i2c_data[0] |= (0x1 << 2); + max17047_i2c_write(client, MAX17047_REG_CONFIG, i2c_data); +} + +static void max17047_reg_init(struct max17047_fuelgauge_data *fg_data) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_debug("%s\n", __func__); + + +#if defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) + i2c_data[1] = 0x00; + i2c_data[0] = 0x00; + max17047_i2c_write(client, MAX17047_REG_CGAIN, i2c_data); + + i2c_data[1] = 0x00; + i2c_data[0] = 0x03; + max17047_i2c_write(client, MAX17047_REG_MISCCFG, i2c_data); + + i2c_data[1] = 0x00; + i2c_data[0] = 0x07; + max17047_i2c_write(client, MAX17047_REG_LEARNCFG, i2c_data); + + i2c_data[1] = 0x00; + i2c_data[0] = 0x50; + max17047_i2c_write(client, MAX17047_REG_RCOMP, i2c_data); +#else /* Use MG1 */ + i2c_data[1] = 0x00; + i2c_data[0] = 0x00; + max17047_i2c_write(client, MAX17047_REG_CGAIN, i2c_data); + + i2c_data[1] = 0x00; + i2c_data[0] = 0x03; + max17047_i2c_write(client, MAX17047_REG_MISCCFG, i2c_data); + + i2c_data[1] = 0x07; + i2c_data[0] = 0x00; + max17047_i2c_write(client, MAX17047_REG_LEARNCFG, i2c_data); +#endif +} + +static void max17047_update_work(struct work_struct *work) +{ + struct max17047_fuelgauge_data *fg_data = container_of(work, + struct max17047_fuelgauge_data, + update_work.work); + struct power_supply *battery_psy; + struct i2c_client *client = fg_data->client; + union power_supply_propval value; + pr_debug("%s\n", __func__); + +#ifdef CONFIG_SLP + battery_psy = &fg_data->fuelgauge; +#else + battery_psy = power_supply_get_by_name("battery"); +#endif + + max17047_get_vcell(client); + max17047_get_vfocv(client); + max17047_get_avgvcell(client); + max17047_get_rawsoc(client); + max17047_get_soc(client); + + pr_info("%s: VCELL(%d), VFOCV(%d), AVGVCELL(%d), RAWSOC(%d), SOC(%d)\n", + __func__, fg_data->vcell, + fg_data->vfocv, fg_data->avgvcell, + fg_data->rawsoc, fg_data->soc); + + max17047_test_read(fg_data); + + if (!battery_psy || !battery_psy->set_property) { + pr_err("%s: fail to get battery power supply\n", __func__); + mutex_unlock(&fg_data->irq_lock); + return; + } + + battery_psy->set_property(battery_psy, + POWER_SUPPLY_PROP_STATUS, + &value); + + wake_lock_timeout(&fg_data->update_wake_lock, HZ); +} + +#ifdef DEBUG_FUELGAUGE_POLLING +static void max17047_polling_work(struct work_struct *work) +{ + struct max17047_fuelgauge_data *fg_data = container_of(work, + struct max17047_fuelgauge_data, + polling_work.work); + int reg; + int i; + u8 data[2]; + u8 buf[512]; + + max17047_get_vcell(fg_data->client); + max17047_get_vfocv(fg_data->client); + max17047_get_avgvcell(fg_data->client); + max17047_get_rawsoc(fg_data->client); + max17047_get_soc(fg_data->client); + + pr_info("%s: VCELL(%d), VFOCV(%d), AVGVCELL(%d), RAWSOC(%d), SOC(%d)\n", + __func__, fg_data->vcell, + fg_data->vfocv, fg_data->avgvcell, + fg_data->rawsoc, fg_data->soc); + + max17047_test_read(fg_data); + + schedule_delayed_work(&fg_data->polling_work, + msecs_to_jiffies(MAX17047_POLLING_INTERVAL)); +} +#endif + +static enum power_supply_property max17047_fuelgauge_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +#ifdef USE_TRIM_ERROR_DETECTION +/* Temp: Init max17047 sample has trim value error. For detecting that. */ +#define TRIM_ERROR_DETECT_VOLTAGE1 2500000 +#define TRIM_ERROR_DETECT_VOLTAGE2 3600000 +static int max17047_detect_trim_error(struct max17047_fuelgauge_data *fg_data) +{ + int vcell, soc; + + vcell = max17047_get_vcell(fg_data->client); + soc = max17047_get_soc(fg_data->client); + + if (((vcell < TRIM_ERROR_DETECT_VOLTAGE1) || + (vcell == TRIM_ERROR_DETECT_VOLTAGE2)) && (soc == 0)) { + pr_debug("%s: (maybe)It's a trim error version. " + "VCELL(%d), SOC(%d)\n", __func__, vcell, soc); + return 1; + } + + return 0; +} +#endif + +static int max17047_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17047_fuelgauge_data *fg_data = container_of(psy, + struct max17047_fuelgauge_data, + fuelgauge); + +#ifdef USE_TRIM_ERROR_DETECTION + if (max17047_detect_trim_error(fg_data)) { + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = 4200000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = 99; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = 300; + break; + default: + return -EINVAL; + } + + return 0; + } +#endif + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (val->intval) { + case VOLTAGE_TYPE_VCELL: + val->intval = max17047_get_vcell(fg_data->client); + break; + case VOLTAGE_TYPE_VFOCV: + val->intval = max17047_get_vfocv(fg_data->client); + break; + default: + val->intval = max17047_get_vcell(fg_data->client); + break; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = max17047_get_avgvcell(fg_data->client); + break; + case POWER_SUPPLY_PROP_CAPACITY: + switch (val->intval) { + case SOC_TYPE_ADJUSTED: + val->intval = max17047_get_soc(fg_data->client); + break; + case SOC_TYPE_RAW: + val->intval = max17047_get_rawsoc(fg_data->client); + break; +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + case SOC_TYPE_FULL: + val->intval = fg_data->full_soc; + break; +#endif + default: + val->intval = max17047_get_soc(fg_data->client); + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = max17047_get_temperature(fg_data->client); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int max17047_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17047_fuelgauge_data *fg_data = container_of(psy, + struct max17047_fuelgauge_data, + fuelgauge); + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + max17047_reset_soc(fg_data->client); + break; +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + case POWER_SUPPLY_PROP_STATUS: + if (val->intval != POWER_SUPPLY_STATUS_FULL) + return -EINVAL; + pr_info("%s: charger full state!\n", __func__); + /* adjust full soc */ + max17047_adjust_fullsoc(fg_data->client); + break; +#endif + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t max17047_fuelgauge_isr(int irq, void *data) +{ + struct max17047_fuelgauge_data *fg_data = data; + struct i2c_client *client = fg_data->client; + struct power_supply *battery_psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + u8 i2c_data[2]; + pr_info("%s: irq(%d)\n", __func__, irq); + mutex_lock(&fg_data->irq_lock); + + max17047_i2c_read(client, MAX17047_REG_STATUS, i2c_data); + pr_info("%s: MAX17047_REG_STATUS(0x%02x%02x)\n", __func__, + i2c_data[1], i2c_data[0]); + + wake_lock(&fg_data->update_wake_lock); + schedule_delayed_work(&fg_data->update_work, msecs_to_jiffies(1000)); + + mutex_unlock(&fg_data->irq_lock); + return IRQ_HANDLED; +} + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +static void max17047_adjust_fullsoc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + int prev_full_soc = fg_data->full_soc; + int temp_soc = max17047_get_rawsoc(fg_data->client); + int keep_soc = 0; + + if (temp_soc < 0) { + pr_err("%s : fg data error!(%d)\n", __func__, temp_soc); + fg_data->full_soc = FULL_SOC_DEFAULT; + return; + } + + if (temp_soc < FULL_SOC_LOW) + fg_data->full_soc = FULL_SOC_LOW; + else if (temp_soc > FULL_SOC_HIGH) { + keep_soc = FULL_SOC_HIGH / 100; + fg_data->full_soc = (FULL_SOC_HIGH - keep_soc); + } else { + keep_soc = temp_soc / 100; + if (temp_soc > (FULL_SOC_LOW + keep_soc)) + fg_data->full_soc = temp_soc - keep_soc; + else + fg_data->full_soc = FULL_SOC_LOW; + } + + if (prev_full_soc != fg_data->full_soc) + pr_info("%s : full_soc = %d, keep_soc = %d\n", __func__, + fg_data->full_soc, keep_soc); +} +#endif + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS +static int max17047_debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t max17047_debugfs_read_registers(struct file *filp, + char __user *buffer, size_t count, loff_t *ppos) +{ + struct max17047_fuelgauge_data *fg_data = filp->private_data; + struct i2c_client *client = NULL; + u8 i2c_data[2]; + int reg = 0; + char *buf; + size_t len = 0; + ssize_t ret; + + if (!fg_data) { + pr_err("%s : fg_data is null\n", __func__); + return 0; + } + + client = fg_data->client; + + if (*ppos != 0) + return 0; + + if (count < sizeof(buf)) + return -ENOSPC; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + reg = MAX17047_REG_STATUS; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "status(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_CONFIG; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "config(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_RCOMP; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "rcomp(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_CGAIN; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "cgain(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_SALRT_TH; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "salrt(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_MISCCFG; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "misc(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = 0x39; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "tempc0(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = 0x0F; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "remCap(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = 0x10; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "fullCap(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + + ret = simple_read_from_buffer(buffer, len, ppos, buf, PAGE_SIZE); + kfree(buf); + + return ret; +} + +static const struct file_operations max17047_debugfs_fops = { + .owner = THIS_MODULE, + .open = max17047_debugfs_open, + .read = max17047_debugfs_read_registers, +}; + +static ssize_t max17047_debugfs_read_defaultdata(struct file *filp, + char __user *buffer, size_t count, loff_t *ppos) +{ + struct max17047_fuelgauge_data *fg_data = filp->private_data; + struct i2c_client *client = NULL; + u8 i2c_data[2]; + int reg = 0; + char *buf; + size_t len = 0; + ssize_t ret; + + if (!fg_data) { + pr_err("%s : fg_data is null\n", __func__); + return 0; + } + + client = fg_data->client; + + if (*ppos != 0) + return 0; + + if (count < sizeof(buf)) + return -ENOSPC; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + reg = MAX17047_REG_RCOMP; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "rcomp=%02x%02x ", i2c_data[1], i2c_data[0]); + + len += snprintf(buf + len, PAGE_SIZE - len, + "fsoc=%d", fg_data->full_soc); + + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + + ret = simple_read_from_buffer(buffer, len, ppos, buf, PAGE_SIZE); + kfree(buf); + + return ret; +} + +static const struct file_operations max17047_debugfs_fops2 = { + .owner = THIS_MODULE, + .open = max17047_debugfs_open, + .read = max17047_debugfs_read_defaultdata, +}; +#endif +#endif + +static int __devinit max17047_fuelgauge_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17047_fuelgauge_data *fg_data; + int ret; + u8 i2c_data[2]; +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + int rawsoc; + int firstsoc; +#endif + pr_info("%s: max17047 Fuel gauge Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + fg_data = kzalloc(sizeof(struct max17047_fuelgauge_data), GFP_KERNEL); + if (!fg_data) + return -ENOMEM; + + fg_data->client = client; + fg_data->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, fg_data); + + mutex_init(&fg_data->irq_lock); + + wake_lock_init(&fg_data->update_wake_lock, WAKE_LOCK_SUSPEND, + "fuel-update"); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + /* Initialize full_soc, set this before fisrt SOC reading */ + fg_data->full_soc = FULL_SOC_DEFAULT; + /* first full_soc update */ + rawsoc = max17047_get_rawsoc(fg_data->client); + if (rawsoc > FULL_SOC_DEFAULT) + max17047_adjust_fullsoc(client); + firstsoc = max17047_get_soc(client); + pr_info("%s: rsoc=%d, fsoc=%d, soc=%d\n", __func__, + rawsoc, fg_data->full_soc, firstsoc); +#endif + + if (fg_data->pdata->psy_name) + fg_data->fuelgauge.name = + fg_data->pdata->psy_name; + else + fg_data->fuelgauge.name = "max17047-fuelgauge"; + + fg_data->fuelgauge.type = POWER_SUPPLY_TYPE_BATTERY; + fg_data->fuelgauge.properties = max17047_fuelgauge_props; + fg_data->fuelgauge.num_properties = + ARRAY_SIZE(max17047_fuelgauge_props); + fg_data->fuelgauge.get_property = max17047_get_property; + fg_data->fuelgauge.set_property = max17047_set_property; + + ret = power_supply_register(&client->dev, &fg_data->fuelgauge); + if (ret) { + pr_err("%s: failed power supply register\n", __func__); + goto err_psy_reg_fg; + } + + /* Initialize fuelgauge registers */ + max17047_reg_init(fg_data); + + /* Initialize fuelgauge alert */ + max17047_alert_init(fg_data); + + /* Request IRQ */ + fg_data->irq = gpio_to_irq(fg_data->pdata->irq_gpio); + ret = gpio_request(fg_data->pdata->irq_gpio, "fuelgauge-irq"); + if (ret) { + pr_err("%s: failed requesting gpio %d\n", __func__, + fg_data->pdata->irq_gpio); + goto err_irq; + } + gpio_direction_input(fg_data->pdata->irq_gpio); + gpio_free(fg_data->pdata->irq_gpio); + + ret = request_threaded_irq(fg_data->irq, NULL, + max17047_fuelgauge_isr, IRQF_TRIGGER_FALLING, + "max17047-alert", fg_data); + if (ret < 0) { + pr_err("%s: fail to request max17047 irq: %d: %d\n", + __func__, fg_data->irq, ret); + goto err_irq; + } + + ret = enable_irq_wake(fg_data->irq); + if (ret < 0) { + pr_err("%s: failed enable irq wake %d\n", __func__, + fg_data->irq); + goto err_enable_irq; + } + + INIT_DELAYED_WORK_DEFERRABLE(&fg_data->update_work, + max17047_update_work); +#ifdef DEBUG_FUELGAUGE_POLLING + INIT_DELAYED_WORK_DEFERRABLE(&fg_data->polling_work, + max17047_polling_work); + schedule_delayed_work(&fg_data->polling_work, 0); +#else + max17047_test_read(fg_data); +#endif + + max17047_i2c_read(client, MAX17047_REG_VERSION, i2c_data); + pr_info("max17047 fuelgauge(rev.%d%d) initialized.\n", i2c_data[0], i2c_data[1]); + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS + fg_data->fg_debugfs_dir = + debugfs_create_dir("fg_debug", NULL); + if (fg_data->fg_debugfs_dir) { + if (!debugfs_create_file("max17047_regs", 0644, + fg_data->fg_debugfs_dir, + fg_data, &max17047_debugfs_fops)) + pr_err("%s : debugfs_create_file, error\n", __func__); + if (!debugfs_create_file("default_data", 0644, + fg_data->fg_debugfs_dir, + fg_data, &max17047_debugfs_fops2)) + pr_err("%s : debugfs_create_file2, error\n", __func__); + } else + pr_err("%s : debugfs_create_dir, error\n", __func__); +#endif +#endif + + return 0; + +err_enable_irq: + free_irq(fg_data->irq, fg_data); +err_irq: + power_supply_unregister(&fg_data->fuelgauge); +err_psy_reg_fg: + wake_lock_destroy(&fg_data->update_wake_lock); + mutex_destroy(&fg_data->irq_lock); + kfree(fg_data); + return ret; +} + +static int __devexit max17047_fuelgauge_remove(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + + wake_lock_destroy(&fg_data->update_wake_lock); + free_irq(fg_data->irq, fg_data); + power_supply_unregister(&fg_data->fuelgauge); +#ifdef DEBUG_FUELGAUGE_POLLING + cancel_delayed_work(&fg_data->polling_work); +#endif + cancel_delayed_work(&fg_data->update_work); + mutex_destroy(&fg_data->irq_lock); + kfree(fg_data); + + return 0; +} + +#ifdef CONFIG_PM +static int max17047_fuelgauge_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + struct power_supply *psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + int charge_state, voltage_max, voltage_min; + int valrt_vol; + pr_info("%s\n", __func__); + +#ifdef DEBUG_FUELGAUGE_POLLING + cancel_delayed_work(&fg_data->polling_work); +#endif + +#if !defined(CONFIG_SLP) + /* default disable */ + valrt_vol = 0; + + /* voltage alert recharge voltage */ + if (!psy) { + pr_err("%s: fail to get battery psy\n", __func__); + return 0; + } + psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + charge_state = value.intval; + + if (charge_state == POWER_SUPPLY_STATUS_FULL) { + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + &value); + voltage_max = value.intval; + + /* valrt voltage set as recharge voltage */ + valrt_vol = voltage_max - RECHG_DROP_VALUE; + } else { + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + &value); + voltage_min = value.intval; + + /* valrt voltage set as min voltage - 50mV */ + valrt_vol = voltage_min - 50000; + } + + pr_info("%s: charge state(%d), vcell(%d), valrt(%d)\n", __func__, + charge_state, fg_data->vcell, valrt_vol); + + /* set voltage alert */ + max17047_set_valrt(fg_data, (valrt_vol / 1000 / 20), 0xFF); +#endif + + return 0; +} + +static int max17047_fuelgauge_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + pr_info("%s\n", __func__); + +#if !defined(CONFIG_SLP) + /* min disable, max disable */ + max17047_set_valrt(fg_data, 0x00, 0xFF); +#endif + +#ifdef DEBUG_FUELGAUGE_POLLING + schedule_delayed_work(&fg_data->polling_work, 0); +#endif + + return 0; +} +#else +#define max17047_fuelgauge_suspend NULL +#define max17047_fuelgauge_resume NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17047_fuelgauge_id[] = { + {"max17047-fuelgauge", 0}, + {} +}; + +#ifdef CONFIG_HIBERNATION +static const u16 save_addr[] = { + MAX17047_REG_VALRT_TH, + MAX17047_REG_TALRT_TH, + MAX17047_REG_SALRT_TH, + + MAX17047_REG_TEMPERATURE, + MAX17047_REG_CONFIG, + + MAX17047_REG_LEARNCFG, + MAX17047_REG_FILTERCFG, + MAX17047_REG_MISCCFG, + MAX17047_REG_CGAIN, + MAX17047_REG_RCOMP, + MAX17047_REG_SOC_VF, +}; + + +static int max17047_freeze(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data + = i2c_get_clientdata(client); + int i, j; + + if (fg_data->reg_dump) { + dev_err(dev, "Register dump is not clean.\n"); + return -EINVAL; + } + + fg_data->reg_dump = kzalloc(sizeof(u16) * ARRAY_SIZE(save_addr), + GFP_KERNEL); + if (!fg_data->reg_dump) { + dev_err(dev, "Cannot allocate memory for hibernation dump.\n"); + return -ENOMEM; + } + + for (i = 0, j = 0; i < ARRAY_SIZE(save_addr); i++, j += 2) + max17047_i2c_read(client, save_addr[i] + , &(fg_data->reg_dump[j])); + + return 0; +} + +static int max17047_restore(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data + = i2c_get_clientdata(client); + int i, j; + + if (!fg_data->reg_dump) { + dev_err(dev, "Cannot allocate memory for hibernation dump.\n"); + return -ENOMEM; + } + + for (i = 0, j = 0; i < ARRAY_SIZE(save_addr); i++, j += 2) + max17047_i2c_write(client, save_addr[i] + , &(fg_data->reg_dump[j])); + + kfree(fg_data->reg_dump); + fg_data->reg_dump = NULL; + + return 0; +} +#endif + + + +#ifdef CONFIG_PM +const struct dev_pm_ops max17047_pm = { + .suspend = max17047_fuelgauge_suspend, + .resume = max17047_fuelgauge_resume, +#ifdef CONFIG_HIBERNATION + .freeze = max17047_freeze, + .thaw = max17047_restore, + .restore = max17047_restore, +#endif +}; +#endif + + +MODULE_DEVICE_TABLE(i2c, max17047_fuelgauge_id); + +static struct i2c_driver max17047_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max17047-fuelgauge", + .pm = &max17047_pm, + + }, + .probe = max17047_fuelgauge_i2c_probe, + .remove = __devexit_p(max17047_fuelgauge_remove), + .id_table = max17047_fuelgauge_id, +}; + +static int __init max17047_fuelgauge_init(void) +{ + return i2c_add_driver(&max17047_i2c_driver); +} + +static void __exit max17047_fuelgauge_exit(void) +{ + i2c_del_driver(&max17047_i2c_driver); +} + +module_init(max17047_fuelgauge_init); +module_exit(max17047_fuelgauge_exit); + +MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>"); +MODULE_DESCRIPTION("max17047 Fuel gauge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/max17050_fuelgauge.c b/drivers/battery/max17050_fuelgauge.c new file mode 100644 index 0000000..2f2a3c3 --- /dev/null +++ b/drivers/battery/max17050_fuelgauge.c @@ -0,0 +1,2192 @@ +/* + * max17050_fuelgauge.c + * Samsung MAX17050 Fuel Gauge Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG + +#include <linux/battery/sec_fuelgauge.h> + +#ifdef CONFIG_FUELGAUGE_MAX17050_VOLTAGE_TRACKING +static int max17050_write_reg(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + + return ret; +} + +static int max17050_read_reg(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + + return ret; +} + +static void max17050_write_reg_array(struct i2c_client *client, + u8 *buf, int size) +{ + int i; + + for (i = 0; i < size; i += 3) + max17050_write_reg(client, (u8) (*(buf + i)), (buf + i) + 1); +} + +static void max17050_init_regs(struct i2c_client *client) +{ + u8 data[2]; + + if (max17050_read_reg(client, MAX17050_REG_FILTERCFG, data) < 0) + return; + + /* Clear average vcell (12 sec) */ + data[0] &= 0x8f; + + max17050_write_reg(client, MAX17050_REG_FILTERCFG, data); +} + +static void max17050_get_version(struct i2c_client *client) +{ + u8 data[2]; + + if (max17050_read_reg(client, MAX17050_REG_VERSION, data) < 0) + return; + + dev_dbg(&client->dev, "MAX17050 Fuel-Gauge Ver %d%d\n", + data[0], data[1]); +} + +static void max17050_alert_init(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + + /* SALRT Threshold setting */ + data[0] = fuelgauge->pdata->fuel_alert_soc; + data[1] = 0xff; + max17050_write_reg(client, MAX17050_REG_SALRT_TH, data); + + /* VALRT Threshold setting */ + data[0] = 0x00; + data[1] = 0xff; + max17050_write_reg(client, MAX17050_REG_VALRT_TH, data); + + /* TALRT Threshold setting */ + data[0] = 0x80; + data[1] = 0x7f; + max17050_write_reg(client, MAX17050_REG_TALRT_TH, data); +} + +static bool max17050_check_status(struct i2c_client *client) +{ + u8 data[2]; + bool ret = false; + + /* check if Smn was generated */ + if (max17050_read_reg(client, MAX17050_REG_STATUS, data) < 0) + return ret; + + dev_info(&client->dev, "%s: status_reg(%02x%02x)\n", + __func__, data[1], data[0]); + + /* minimum SOC threshold exceeded. */ + if (data[1] & (0x1 << 2)) + ret = true; + + /* clear status reg */ + if (!ret) { + data[1] = 0; + max17050_write_reg(client, MAX17050_REG_STATUS, data); + msleep(200); + } + + return ret; +} + +static int max17050_set_temperature(struct i2c_client *client, int temperature) +{ + u8 data[2]; + + data[0] = 0; + data[1] = temperature; + max17050_write_reg(client, MAX17050_REG_TEMPERATURE, data); + + dev_dbg(&client->dev, "%s: temperature to (%d)\n", + __func__, temperature); + + return temperature; +} + +static int max17050_get_temperature(struct i2c_client *client) +{ + u8 data[2]; + s32 temperature = 0; + + if (max17050_read_reg(client, MAX17050_REG_TEMPERATURE, data) < 0) + return -ERANGE; + + /* data[] store 2's compliment format number */ + if (data[1] & (0x1 << 7)) { + /* Negative */ + temperature = ((~(data[1])) & 0xFF) + 1; + temperature *= (-1000); + } else { + temperature = data[1] & 0x7F; + temperature *= 1000; + temperature += data[0] * 39 / 10; + } + + dev_dbg(&client->dev, "%s: temperature (%d)\n", + __func__, temperature); + + return temperature; +} + +/* soc should be 0.1% unit */ +static int max17050_get_soc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (max17050_read_reg(client, MAX17050_REG_SOC_VF, data) < 0) + return -EINVAL; + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + dev_dbg(&client->dev, "%s: raw capacity (%d)\n", __func__, soc); + + return min(soc, 1000); +} + +static int max17050_get_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + + if (max17050_read_reg(client, MAX17050_REG_VFOCV, data) < 0) + return -EINVAL; + + vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + dev_dbg(&client->dev, "%s: vfocv (%d)\n", __func__, vfocv); + + return vfocv; +} + +static int max17050_get_vcell(struct i2c_client *client) +{ + u8 data[2]; + u32 vcell = 0; + + if (max17050_read_reg(client, MAX17050_REG_VCELL, data) < 0) + return -EINVAL; + + vcell = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + dev_dbg(&client->dev, "%s: vcell (%d)\n", __func__, vcell); + + return vcell; +} + +static int max17050_get_avgvcell(struct i2c_client *client) +{ + u8 data[2]; + u32 avgvcell = 0; + + if (max17050_read_reg(client, MAX17050_REG_AVGVCELL, data) < 0) + return -EINVAL; + + avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + dev_dbg(&client->dev, "%s: avgvcell (%d)\n", __func__, avgvcell); + + return avgvcell; +} + +bool sec_hal_fg_init(struct i2c_client *client) +{ + /* initialize fuel gauge registers */ + max17050_init_regs(client); + + max17050_get_version(client); + + return true; +} + +bool sec_hal_fg_suspend(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_resume(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + + /* 1. Set max17050 alert configuration. */ + max17050_alert_init(client); + + if (max17050_read_reg(client, MAX17050_REG_CONFIG, data) + < 0) + return -1; + + /*Enable Alert (Aen = 1) */ + data[0] |= (0x1 << 2); + + max17050_write_reg(client, MAX17050_REG_CONFIG, data); + + dev_dbg(&client->dev, "%s: config_reg(%02x%02x) irq(%d)\n", + __func__, data[1], data[0], fuelgauge->pdata->fg_gpio_irq); + + return true; +} + +bool sec_hal_fg_is_fuelalerted(struct i2c_client *client) +{ + return max17050_check_status(client); +} + +bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) +{ + struct sec_fuelgauge_info *fuelgauge = irq_data; + u8 data[2]; + + /* update SOC */ + /* max17050_get_soc(fuelgauge->client); */ + + if (is_fuel_alerted) { + if (max17050_read_reg(fuelgauge->client, + MAX17050_REG_CONFIG, data) < 0) + return false; + + data[1] |= (0x1 << 3); + + max17050_write_reg(fuelgauge->client, + MAX17050_REG_CONFIG, data); + + dev_info(&fuelgauge->client->dev, + "%s: Fuel-alert Alerted!! (%02x%02x)\n", + __func__, data[1], data[0]); + } else { + if (max17050_read_reg(fuelgauge->client, + MAX17050_REG_CONFIG, data) + < 0) + return false; + + data[1] &= (~(0x1 << 3)); + + max17050_write_reg(fuelgauge->client, + MAX17050_REG_CONFIG, data); + + dev_info(&fuelgauge->client->dev, + "%s: Fuel-alert Released!! (%02x%02x)\n", + __func__, data[1], data[0]); + } + + max17050_read_reg(fuelgauge->client, MAX17050_REG_VCELL, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_VCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_TEMPERATURE, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_TEMPERATURE(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_CONFIG, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_CONFIG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_VFOCV, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_VFOCV(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_SOC_VF, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_SOC_VF(%02x%02x)\n", + __func__, data[1], data[0]); + + dev_dbg(&fuelgauge->client->dev, + "%s: FUEL GAUGE IRQ (%d)\n", + __func__, + gpio_get_value(irq_to_gpio(fuelgauge->pdata->fg_gpio_irq))); + +#if 0 + max17050_read_reg(fuelgauge->client, MAX17050_REG_STATUS, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_STATUS(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_VALRT_TH, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_VALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_TALRT_TH, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_TALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_SALRT_TH, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_SALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_AVGVCELL, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_AVGVCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_VERSION, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_VERSION(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_LEARNCFG, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_LEARNCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_MISCCFG, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_MISCCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_CGAIN, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_CGAIN(%02x%02x)\n", + __func__, data[1], data[0]); + + max17050_read_reg(fuelgauge->client, MAX17050_REG_RCOMP, data); + dev_dbg(&fuelgauge->client->dev, + "%s: MAX17050_REG_RCOMP(%02x%02x)\n", + __func__, data[1], data[0]); +#endif + + return true; +} + +bool sec_hal_fg_full_charged(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = max17050_get_vcell(client); + break; + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (val->intval) { + case SEC_BATTEY_VOLTAGE_AVERAGE: + val->intval = max17050_get_avgvcell(client); + break; + case SEC_BATTEY_VOLTAGE_OCV: + val->intval = max17050_get_vfocv(client); + break; + } + break; + /* Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = 0; + break; + /* Average Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = 0; + break; + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = max17050_get_soc(client); + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = max17050_get_temperature(client); + break; + default: + return false; + } + return true; +} + +bool sec_hal_fg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + switch (psp) { + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + max17050_set_temperature(client, val->intval); + break; + default: + return false; + } + return true; +} + +ssize_t sec_hal_fg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int i = 0; + + switch (offset) { +/* case FG_REG: */ +/* break; */ + case FG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%02x%02x\n", + fg->reg_data[1], fg->reg_data[0]); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_hal_fg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int ret = 0; + int x = 0; + u8 data[2]; + + switch (offset) { + case FG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + fg->reg_addr = x; + max17050_read_reg(fg->client, + fg->reg_addr, fg->reg_data); + dev_dbg(&fg->client->dev, + "%s: (read) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, + fg->reg_data[1], fg->reg_data[0]); + ret = count; + } + break; + case FG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data[0] = (x & 0xFF00) >> 8; + data[1] = (x & 0x00FF); + dev_dbg(&fg->client->dev, + "%s: (write) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, data[1], data[0]); + max17050_write_reg(fg->client, + fg->reg_addr, data); + ret = count; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#endif + +#ifdef CONFIG_FUELGAUGE_MAX17050_COULOMB_COUNTING +static int fg_i2c_read(struct i2c_client *client, + u8 reg, u8 *data, u8 length) +{ + s32 value; + + value = i2c_smbus_read_i2c_block_data(client, reg, length, data); + if (value < 0 || value != length) { + dev_err(&client->dev, "%s: Error(%d)\n", + __func__, value); + return -1; + } + + return 0; +} + +static int fg_i2c_write(struct i2c_client *client, + u8 reg, u8 *data, u8 length) +{ + s32 value; + + value = i2c_smbus_write_i2c_block_data(client, reg, length, data); + if (value < 0) { + dev_err(&client->dev, "%s: Error(%d)\n", + __func__, value); + return -1; + } + + return 0; +} + +static int fg_read_register(struct i2c_client *client, + u8 addr) +{ + u8 data[2]; + + if (fg_i2c_read(client, addr, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read addr(0x%x)\n", + __func__, addr); + return -1; + } + + return (data[1] << 8) | data[0]; +} + +static int fg_write_register(struct i2c_client *client, + u8 addr, u16 w_data) +{ + u8 data[2]; + + data[0] = w_data & 0xFF; + data[1] = w_data >> 8; + + if (fg_i2c_write(client, addr, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to write addr(0x%x)\n", + __func__, addr); + return -1; + } + + return 0; +} + +static int fg_read_16register(struct i2c_client *client, + u8 addr, u16 *r_data) +{ + u8 data[32]; + int i = 0; + + if (fg_i2c_read(client, addr, data, 32) < 0) { + dev_err(&client->dev, "%s: Failed to read addr(0x%x)\n", + __func__, addr); + return -1; + } + + for (i = 0; i < 16; i++) + r_data[i] = (data[2 * i + 1] << 8) | data[2 * i]; + + return 0; +} + +static void fg_write_and_verify_register(struct i2c_client *client, + u8 addr, u16 w_data) +{ + u16 r_data; + u8 retry_cnt = 2; + + while (retry_cnt) { + fg_write_register(client, addr, w_data); + r_data = fg_read_register(client, addr); + + if (r_data != w_data) { + dev_err(&client->dev, + "%s: verification failed (addr: 0x%x, w_data: 0x%x, r_data: 0x%x)\n", + __func__, addr, w_data, r_data); + retry_cnt--; + } else + break; + } +} + +static void fg_test_print(struct i2c_client *client) +{ + u8 data[2]; + u32 average_vcell; + u16 w_data; + u32 temp; + u32 temp2; + u16 reg_data; + + if (fg_i2c_read(client, AVR_VCELL_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read VCELL\n", __func__); + return; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + average_vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + average_vcell += (temp2 << 4); + + dev_info(&client->dev, "%s: AVG_VCELL(%d), data(0x%04x)\n", __func__, + average_vcell, (data[1]<<8) | data[0]); + + reg_data = fg_read_register(client, FULLCAP_REG); + dev_info(&client->dev, "%s: FULLCAP(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + + reg_data = fg_read_register(client, REMCAP_REP_REG); + dev_info(&client->dev, "%s: REMCAP_REP(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + + reg_data = fg_read_register(client, REMCAP_MIX_REG); + dev_info(&client->dev, "%s: REMCAP_MIX(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); + + reg_data = fg_read_register(client, REMCAP_AV_REG); + dev_info(&client->dev, "%s: REMCAP_AV(%d), data(0x%04x)\n", __func__, + reg_data/2, reg_data); +} + +static void fg_periodic_read(struct i2c_client *client) +{ + u8 reg; + int i; + int data[0x10]; + char *str = NULL; + + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return; + + for (i = 0; i < 16; i++) { + for (reg = 0; reg < 0x10; reg++) + data[reg] = fg_read_register(client, reg + i * 0x10); + + sprintf(str+strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + sprintf(str+strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + if (i == 4) + i = 13; + } + + dev_info(&client->dev, "%s", str); + + kfree(str); +} + +static void fg_read_regs(struct i2c_client *client, char *str) +{ + int data = 0; + u32 addr = 0; + + for (addr = 0; addr <= 0x4f; addr++) { + data = fg_read_register(client, addr); + sprintf(str+strlen(str), "0x%04x, ", data); + } + + /* "#" considered as new line in application */ + sprintf(str+strlen(str), "#"); + + for (addr = 0xe0; addr <= 0xff; addr++) { + data = fg_read_register(client, addr); + sprintf(str+strlen(str), "0x%04x, ", data); + } +} + +static int fg_read_vcell(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + u32 vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VCELL_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vcell += (temp2 << 4); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + dev_info(&client->dev, "%s: VCELL(%d), data(0x%04x)\n", + __func__, vcell, (data[1]<<8) | data[0]); + + return vcell; +} + +static int fg_read_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VFOCV_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read VFOCV\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vfocv = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vfocv += (temp2 << 4); + + return vfocv; +} + +static int fg_check_battery_present(struct i2c_client *client) +{ + u8 status_data[2]; + int ret = 1; + + /* 1. Check Bst bit */ + if (fg_i2c_read(client, STATUS_REG, status_data, 2) < 0) { + dev_err(&client->dev, + "%s: Failed to read STATUS_REG\n", __func__); + return 0; + } + + if (status_data[0] & (0x1 << 3)) { + dev_info(&client->dev, + "%s: addr(0x01), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + dev_info(&client->dev, "%s: battery is absent!!\n", __func__); + ret = 0; + } + + return ret; +} + + +static int fg_read_temp(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + int temper = 0; + int i; + + if (fg_check_battery_present(client)) { + if (fg_i2c_read(client, TEMPERATURE_REG, data, 2) < 0) { + dev_err(&client->dev, + "%s: Failed to read TEMPERATURE_REG\n", + __func__); + return -1; + } + + if (data[1]&(0x1 << 7)) { + temper = ((~(data[1]))&0xFF)+1; + temper *= (-1000); + } else { + temper = data[1] & 0x7f; + temper *= 1000; + temper += data[0] * 39 / 10; + + /* Adjust temperature */ + for (i = 0; i < TEMP_RANGE_MAX_NUM-1; i++) { + if ((temper >= get_battery_data(fuelgauge). + temp_adjust_table[i][RANGE]) && + (temper < get_battery_data(fuelgauge). + temp_adjust_table[i+1][RANGE])) { + temper = (temper * + get_battery_data(fuelgauge). + temp_adjust_table[i][SLOPE] / + 100) - + get_battery_data(fuelgauge). + temp_adjust_table[i][OFFSET]; + } + } + if (i == TEMP_RANGE_MAX_NUM-1) + dev_dbg(&client->dev, + "%s : No adjustment for temperature\n", + __func__); + } + } else + temper = 20000; + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + dev_info(&client->dev, "%s: TEMPERATURE(%d), data(0x%04x)\n", + __func__, temper, (data[1]<<8) | data[0]); + + return temper/100; +} + +/* soc should be 0.1% unit */ +static int fg_read_vfsoc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (fg_i2c_read(client, VFSOC_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read VFSOC\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.1% unit */ +static int fg_read_avsoc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (fg_i2c_read(client, SOCAV_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read AVSOC\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.1% unit */ +static int fg_read_soc(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + int soc; + + if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read SOCREP\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + dev_dbg(&client->dev, "%s: raw capacity (%d)\n", __func__, soc); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + dev_dbg(&client->dev, "%s: raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1]<<8) | data[0]); + + return min(soc, 1000); +} + +static int fg_read_current(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data1[2], data2[2]; + u32 temp, sign; + s32 i_current; + s32 avg_current; + + if (fg_i2c_read(client, CURRENT_REG, data1, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read CURRENT\n", + __func__); + return -1; + } + + if (fg_i2c_read(client, AVG_CURRENT_REG, data2, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read AVERAGE CURRENT\n", + __func__); + return -1; + } + + temp = ((data1[1]<<8) | data1[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + i_current = temp * 15625 / 100000; + if (sign) + i_current *= -1; + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + avg_current = temp * 15625 / 100000; + if (sign) + avg_current *= -1; + + if (!(fuelgauge->info.pr_cnt++ % PRINT_COUNT)) { + fg_test_print(client); + dev_info(&client->dev, "%s: CURRENT(%dmA), AVG_CURRENT(%dmA)\n", + __func__, i_current, avg_current); + fuelgauge->info.pr_cnt = 1; + /* Read max17050's all registers every 5 minute. */ + fg_periodic_read(client); + } + + return i_current; +} + +static int fg_read_avg_current(struct i2c_client *client) +{ + u8 data2[2]; + u32 temp, sign; + s32 avg_current; + + if (fg_i2c_read(client, AVG_CURRENT_REG, data2, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read AVERAGE CURRENT\n", + __func__); + return -1; + } + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + avg_current = temp * 15625 / 100000; + + if (sign) + avg_current *= -1; + + return avg_current; +} + +int fg_reset_soc(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + u8 data[2]; + int vfocv, fullcap; + + /* delay for current stablization */ + msleep(500); + + dev_info(&client->dev, + "%s: Before quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vcell(client), fg_read_vfocv(client), + fg_read_vfsoc(client), fg_read_soc(client)); + dev_info(&client->dev, + "%s: Before quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(client), + fg_read_avg_current(client)); + + if (!fuelgauge->pdata->check_jig_status()) { + dev_info(&client->dev, + "%s : Return by No JIG_ON signal\n", __func__); + return 0; + } + + fg_write_register(client, CYCLES_REG, 0); + + if (fg_i2c_read(client, MISCCFG_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read MiscCFG\n", __func__); + return -1; + } + + data[1] |= (0x1 << 2); + if (fg_i2c_write(client, MISCCFG_REG, data, 2) < 0) { + dev_err(&client->dev, + "%s: Failed to write MiscCFG\n", __func__); + return -1; + } + + msleep(250); + fg_write_register(client, FULLCAP_REG, + get_battery_data(fuelgauge).Capacity); + msleep(500); + + dev_info(&client->dev, + "%s: After quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vcell(client), fg_read_vfocv(client), + fg_read_vfsoc(client), fg_read_soc(client)); + dev_info(&client->dev, + "%s: After quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(client), + fg_read_avg_current(client)); + fg_write_register(client, CYCLES_REG, 0x00a0); + +/* P8 is not turned off by Quickstart @3.4V + * (It's not a problem, depend on mode data) + * Power off for factory test(File system, etc..) */ + vfocv = fg_read_vfocv(client); + if (vfocv < POWER_OFF_VOLTAGE_LOW_MARGIN) { + dev_info(&client->dev, "%s: Power off condition(%d)\n", + __func__, vfocv); + + fullcap = fg_read_register(client, FULLCAP_REG); + /* FullCAP * 0.009 */ + fg_write_register(client, REMCAP_REP_REG, + (u16)(fullcap * 9 / 1000)); + msleep(200); + dev_info(&client->dev, "%s: new soc=%d, vfocv=%d\n", __func__, + fg_read_soc(client), vfocv); + } + + dev_info(&client->dev, + "%s: Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vfocv(client), + fg_read_vfsoc(client), fg_read_soc(client)); + + return 0; +} + + +int fg_reset_capacity_by_jig_connection(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + dev_info(&client->dev, + "%s: DesignCap = Capacity - 1 (Jig Connection)\n", __func__); + + return fg_write_register(client, DESIGNCAP_REG, + get_battery_data(fuelgauge).Capacity-1); +} + +int fg_adjust_capacity(struct i2c_client *client) +{ + u8 data[2]; + + data[0] = 0; + data[1] = 0; + + /* 1. Write RemCapREP(05h)=0; */ + if (fg_i2c_write(client, REMCAP_REP_REG, data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to write RemCap_REP\n", + __func__); + return -1; + } + msleep(200); + + dev_info(&client->dev, "%s: After adjust - RepSOC(%d)\n", __func__, + fg_read_soc(client)); + + return 0; +} + +void fg_low_batt_compensation(struct i2c_client *client, u32 level) +{ + int read_val; + u32 temp; + + dev_info(&client->dev, "%s: Adjust SOCrep to %d!!\n", + __func__, level); + + read_val = fg_read_register(client, FULLCAP_REG); + if (read_val < 0) + return; + + if (read_val > 2) /* 3% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0301 */ + temp = read_val * (level*100 + 1) / 10000; + else /* 1% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0090 */ + temp = read_val * (level*90) / 10000; + fg_write_register(client, REMCAP_REP_REG, (u16)temp); +} + +static void fg_read_model_data(struct i2c_client *client) +{ + u16 data0[16], data1[16], data2[16]; + int i; + int relock_check; + + dev_info(&client->dev, "[FG_Model] "); + + /* Unlock model access */ + fg_write_register(client, 0x62, 0x0059); + fg_write_register(client, 0x63, 0x00C4); + + /* Read model data */ + fg_read_16register(client, 0x80, data0); + fg_read_16register(client, 0x90, data1); + fg_read_16register(client, 0xa0, data2); + + /* Print model data */ + for (i = 0; i < 16; i++) + dev_info(&client->dev, "0x%04x, ", data0[i]); + + for (i = 0; i < 16; i++) + dev_info(&client->dev, "0x%04x, ", data1[i]); + + for (i = 0; i < 16; i++) { + if (i == 15) + dev_info(&client->dev, "0x%04x", data2[i]); + else + dev_info(&client->dev, "0x%04x, ", data2[i]); + } + + do { + relock_check = 0; + /* Lock model access */ + fg_write_register(client, 0x62, 0x0000); + fg_write_register(client, 0x63, 0x0000); + + /* Read model data again */ + fg_read_16register(client, 0x80, data0); + fg_read_16register(client, 0x90, data1); + fg_read_16register(client, 0xa0, data2); + + for (i = 0; i < 16; i++) { + if (data0[i] || data1[i] || data2[i]) { + dev_dbg(&client->dev, + "%s: data is non-zero, lock again!!\n", + __func__); + relock_check = 1; + } + } + } while (relock_check); + +} + +static int fg_check_status_reg(struct i2c_client *client) +{ + u8 status_data[2]; + int ret = 0; + + /* 1. Check Smn was generatedread */ + if (fg_i2c_read(client, STATUS_REG, status_data, 2) < 0) { + dev_err(&client->dev, "%s: Failed to read STATUS_REG\n", + __func__); + return -1; + } + dev_info(&client->dev, "%s: addr(0x00), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + + if (status_data[1] & (0x1 << 2)) + ret = 1; + + /* 2. clear Status reg */ + status_data[1] = 0; + if (fg_i2c_write(client, STATUS_REG, status_data, 2) < 0) { + dev_info(&client->dev, "%s: Failed to write STATUS_REG\n", + __func__); + return -1; + } + + return ret; +} + +int get_fuelgauge_value(struct i2c_client *client, int data) +{ + int ret; + + switch (data) { + case FG_LEVEL: + ret = fg_read_soc(client); + break; + + case FG_TEMPERATURE: + ret = fg_read_temp(client); + break; + + case FG_VOLTAGE: + ret = fg_read_vcell(client); + break; + + case FG_CURRENT: + ret = fg_read_current(client); + break; + + case FG_CURRENT_AVG: + ret = fg_read_avg_current(client); + break; + + case FG_CHECK_STATUS: + ret = fg_check_status_reg(client); + break; + + case FG_VF_SOC: + ret = fg_read_vfsoc(client); + break; + + case FG_AV_SOC: + ret = fg_read_avsoc(client); + break; + + default: + ret = -1; + break; + } + + return ret; +} + +int fg_alert_init(struct i2c_client *client, int soc) +{ + u8 misccgf_data[2]; + u8 salrt_data[2]; + u8 config_data[2]; + u8 valrt_data[2]; + u8 talrt_data[2]; + u16 read_data = 0; + + /* Using RepSOC */ + if (fg_i2c_read(client, MISCCFG_REG, misccgf_data, 2) < 0) { + dev_err(&client->dev, + "%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + misccgf_data[0] = misccgf_data[0] & ~(0x03); + + if (fg_i2c_write(client, MISCCFG_REG, misccgf_data, 2) < 0) { + dev_info(&client->dev, + "%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } + + /* SALRT Threshold setting */ + salrt_data[1] = 0xff; + salrt_data[0] = soc; + if (fg_i2c_write(client, SALRT_THRESHOLD_REG, salrt_data, 2) < 0) { + dev_info(&client->dev, + "%s: Failed to write SALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + /* Reset VALRT Threshold setting (disable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = 0x00; + if (fg_i2c_write(client, VALRT_THRESHOLD_REG, valrt_data, 2) < 0) { + dev_info(&client->dev, + "%s: Failed to write VALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = fg_read_register(client, (u8)VALRT_THRESHOLD_REG); + if (read_data != 0xff00) + dev_err(&client->dev, + "%s: VALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + /* Reset TALRT Threshold setting (disable) */ + talrt_data[1] = 0x7F; + talrt_data[0] = 0x80; + if (fg_i2c_write(client, TALRT_THRESHOLD_REG, talrt_data, 2) < 0) { + dev_info(&client->dev, + "%s: Failed to write TALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = fg_read_register(client, (u8)TALRT_THRESHOLD_REG); + if (read_data != 0x7f80) + dev_err(&client->dev, + "%s: TALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + mdelay(100); + + /* Enable SOC alerts */ + if (fg_i2c_read(client, CONFIG_REG, config_data, 2) < 0) { + dev_err(&client->dev, + "%s: Failed to read CONFIG_REG\n", __func__); + return -1; + } + config_data[0] = config_data[0] | (0x1 << 2); + + if (fg_i2c_write(client, CONFIG_REG, config_data, 2) < 0) { + dev_info(&client->dev, + "%s: Failed to write CONFIG_REG\n", __func__); + return -1; + } + + return 1; +} + +void fg_fullcharged_compensation(struct i2c_client *client, + u32 is_recharging, bool pre_update) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + static int new_fullcap_data; + + dev_info(&client->dev, "%s: is_recharging(%d), pre_update(%d)\n", + __func__, is_recharging, pre_update); + + new_fullcap_data = + fg_read_register(client, FULLCAP_REG); + if (new_fullcap_data < 0) + new_fullcap_data = get_battery_data(fuelgauge).Capacity; + + /* compare with initial capacity */ + if (new_fullcap_data > + (get_battery_data(fuelgauge).Capacity * 110 / 100)) { + dev_info(&client->dev, + "%s: [Case 1] capacity = 0x%04x, NewFullCap = 0x%04x\n", + __func__, get_battery_data(fuelgauge).Capacity, + new_fullcap_data); + + new_fullcap_data = + (get_battery_data(fuelgauge).Capacity * 110) / 100; + + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else if (new_fullcap_data < + (get_battery_data(fuelgauge).Capacity * 50 / 100)) { + dev_info(&client->dev, + "%s: [Case 5] capacity = 0x%04x, NewFullCap = 0x%04x\n", + __func__, get_battery_data(fuelgauge).Capacity, + new_fullcap_data); + + new_fullcap_data = + (get_battery_data(fuelgauge).Capacity * 50) / 100; + + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else { + /* compare with previous capacity */ + if (new_fullcap_data > + (fuelgauge->info.previous_fullcap * 110 / 100)) { + dev_info(&client->dev, + "%s: [Case 2] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + + new_fullcap_data = + (fuelgauge->info.previous_fullcap * 110) / 100; + + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else if (new_fullcap_data < + (fuelgauge->info.previous_fullcap * 90 / 100)) { + dev_info(&client->dev, + "%s: [Case 3] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + + new_fullcap_data = + (fuelgauge->info.previous_fullcap * 90) / 100; + + fg_write_register(client, REMCAP_REP_REG, + (u16)(new_fullcap_data)); + fg_write_register(client, FULLCAP_REG, + (u16)(new_fullcap_data)); + } else { + dev_info(&client->dev, + "%s: [Case 4] previous_fullcap = 0x%04x, NewFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_fullcap, + new_fullcap_data); + } + } + + /* 4. Write RepSOC(06h)=100%; */ + fg_write_register(client, SOCREP_REG, (u16)(0x64 << 8)); + + /* 5. Write MixSOC(0Dh)=100%; */ + fg_write_register(client, SOCMIX_REG, (u16)(0x64 << 8)); + + /* 6. Write AVSOC(0Eh)=100%; */ + fg_write_register(client, SOCAV_REG, (u16)(0x64 << 8)); + + /* if pre_update case, skip updating PrevFullCAP value. */ + if (!pre_update) + fuelgauge->info.previous_fullcap = + fg_read_register(client, FULLCAP_REG); + + dev_info(&client->dev, + "%s: (A) FullCap = 0x%04x, RemCap = 0x%04x\n", __func__, + fg_read_register(client, FULLCAP_REG), + fg_read_register(client, REMCAP_REP_REG)); + + fg_periodic_read(client); +} + +void fg_check_vf_fullcap_range(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + static int new_vffullcap; + bool is_vffullcap_changed = true; + + if (fuelgauge->pdata->check_jig_status()) + fg_reset_capacity_by_jig_connection(client); + + new_vffullcap = fg_read_register(client, FULLCAP_NOM_REG); + if (new_vffullcap < 0) + new_vffullcap = get_battery_data(fuelgauge).Capacity; + + /* compare with initial capacity */ + if (new_vffullcap > + (get_battery_data(fuelgauge).Capacity * 110 / 100)) { + dev_info(&client->dev, + "%s: [Case 1] capacity = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, get_battery_data(fuelgauge).Capacity, + new_vffullcap); + + new_vffullcap = + (get_battery_data(fuelgauge).Capacity * 110) / 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else if (new_vffullcap < + (get_battery_data(fuelgauge).Capacity * 50 / 100)) { + dev_info(&client->dev, + "%s: [Case 5] capacity = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, get_battery_data(fuelgauge).Capacity, + new_vffullcap); + + new_vffullcap = + (get_battery_data(fuelgauge).Capacity * 50) / 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else { + /* compare with previous capacity */ + if (new_vffullcap > + (fuelgauge->info.previous_vffullcap * 110 / 100)) { + dev_info(&client->dev, + "%s: [Case 2] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fuelgauge->info.previous_vffullcap * 110) / + 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else if (new_vffullcap < + (fuelgauge->info.previous_vffullcap * 90 / 100)) { + dev_info(&client->dev, + "%s: [Case 3] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fuelgauge->info.previous_vffullcap * 90) / 100; + + fg_write_register(client, DQACC_REG, + (u16)(new_vffullcap / 4)); + fg_write_register(client, DPACC_REG, (u16)0x3200); + } else { + dev_info(&client->dev, + "%s: [Case 4] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fuelgauge->info.previous_vffullcap, + new_vffullcap); + is_vffullcap_changed = false; + } + } + + /* delay for register setting (dQacc, dPacc) */ + if (is_vffullcap_changed) + msleep(300); + + fuelgauge->info.previous_vffullcap = + fg_read_register(client, FULLCAP_NOM_REG); + + if (is_vffullcap_changed) + dev_info(&client->dev, + "%s : VfFullCap(0x%04x), dQacc(0x%04x), dPacc(0x%04x)\n", + __func__, + fg_read_register(client, FULLCAP_NOM_REG), + fg_read_register(client, DQACC_REG), + fg_read_register(client, DPACC_REG)); + +} + +void fg_set_full_charged(struct i2c_client *client) +{ + dev_info(&client->dev, "[FG_Set_Full] (B) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(client, FULLCAP_REG)/2), + (fg_read_register(client, REMCAP_REP_REG)/2)); + + fg_write_register(client, FULLCAP_REG, + (u16)fg_read_register(client, REMCAP_REP_REG)); + + dev_info(&client->dev, "[FG_Set_Full] (A) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(client, FULLCAP_REG)/2), + (fg_read_register(client, REMCAP_REP_REG)/2)); +} + +static void display_low_batt_comp_cnt(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + pr_info("Check Array(%s): [%d, %d], [%d, %d], ", + get_battery_data(fuelgauge).type_str, + fuelgauge->info.low_batt_comp_cnt[0][0], + fuelgauge->info.low_batt_comp_cnt[0][1], + fuelgauge->info.low_batt_comp_cnt[1][0], + fuelgauge->info.low_batt_comp_cnt[1][1]); + pr_info("[%d, %d], [%d, %d], [%d, %d]\n", + fuelgauge->info.low_batt_comp_cnt[2][0], + fuelgauge->info.low_batt_comp_cnt[2][1], + fuelgauge->info.low_batt_comp_cnt[3][0], + fuelgauge->info.low_batt_comp_cnt[3][1], + fuelgauge->info.low_batt_comp_cnt[4][0], + fuelgauge->info.low_batt_comp_cnt[4][1]); +} + +static void add_low_batt_comp_cnt(struct i2c_client *client, + int range, int level) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int i; + int j; + + /* Increase the requested count value, and reset others. */ + fuelgauge->info.low_batt_comp_cnt[range-1][level/2]++; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (i == range-1 && j == level/2) + continue; + else + fuelgauge->info.low_batt_comp_cnt[i][j] = 0; + } + } +} + +void prevent_early_poweroff(struct i2c_client *client, + int vcell, int *fg_soc) +{ + int soc = 0; + int read_val; + + soc = get_fuelgauge_value(client, FG_LEVEL); + + if (soc > POWER_OFF_SOC_HIGH_MARGIN) + return; + + dev_info(&client->dev, "%s: soc=%d%%, vcell=%d\n", __func__, + soc, vcell); + + if (vcell > POWER_OFF_VOLTAGE_HIGH_MARGIN) { + read_val = fg_read_register(client, FULLCAP_REG); + /* FullCAP * 0.013 */ + fg_write_register(client, REMCAP_REP_REG, + (u16)(read_val * 13 / 1000)); + msleep(200); + *fg_soc = fg_read_soc(client); + dev_info(&client->dev, "%s : new soc=%d, vcell=%d\n", + __func__, *fg_soc, vcell); + } +} + +void reset_low_batt_comp_cnt(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + memset(fuelgauge->info.low_batt_comp_cnt, 0, + sizeof(fuelgauge->info.low_batt_comp_cnt)); +} + +static int check_low_batt_comp_condition( + struct i2c_client *client, int *nLevel) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int i; + int j; + int ret = 0; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (fuelgauge->info.low_batt_comp_cnt[i][j] >= + MAX_LOW_BATT_CHECK_CNT) { + display_low_batt_comp_cnt(client); + ret = 1; + *nLevel = j*2 + 1; + break; + } + } + } + + return ret; +} + +static int get_low_batt_threshold(struct i2c_client *client, + int range, int nCurrent, int level) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int ret = 0; + + ret = get_battery_data(fuelgauge).low_battery_table[range][OFFSET] + + ((nCurrent * + get_battery_data(fuelgauge).low_battery_table[range][SLOPE]) / + 1000); + + return ret; +} + +int low_batt_compensation(struct i2c_client *client, + int fg_soc, int fg_vcell, int fg_current) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int fg_avg_current = 0; + int fg_min_current = 0; + int new_level = 0; + int i, table_size; + + /* Not charging, Under low battery comp voltage */ + if (fg_vcell <= get_battery_data(fuelgauge).low_battery_comp_voltage) { + fg_avg_current = fg_read_avg_current(client); + fg_min_current = min(fg_avg_current, fg_current); + + table_size = + sizeof(get_battery_data(fuelgauge).low_battery_table) / + (sizeof(s16)*TABLE_MAX); + + for (i = 1; i < CURRENT_RANGE_MAX_NUM; i++) { + if ((fg_min_current >= get_battery_data(fuelgauge). + low_battery_table[i-1][RANGE]) && + (fg_min_current < get_battery_data(fuelgauge). + low_battery_table[i][RANGE])) { + if (fg_soc >= 2 && fg_vcell < + get_low_batt_threshold(client, + i, fg_min_current, 1)) { + add_low_batt_comp_cnt( + client, i, 1); + } else { + reset_low_batt_comp_cnt(client); + } + } + } + + if (check_low_batt_comp_condition(client, &new_level)) { + fg_low_batt_compensation(client, new_level); + reset_low_batt_comp_cnt(client); + } + + /* if compensation finished, then read SOC again!!*/ + dev_info(&client->dev, + "%s: MIN_CURRENT(%d), AVG_CURRENT(%d), CURRENT(%d), SOC(%d), VCELL(%d)\n", + __func__, fg_min_current, fg_avg_current, + fg_current, fg_soc, fg_vcell); + /* Do not update soc right after low battery compensation */ + /* to prevent from powering-off suddenly */ + dev_info(&client->dev, + "%s: SOC is set to %d\n", + __func__, fg_read_soc(client)); + } + + /* Prevent power off over 3500mV */ + prevent_early_poweroff(client, fg_vcell, &fg_soc); + + return fg_soc; +} + +static bool is_booted_in_low_battery(struct i2c_client *client) +{ + int fg_vcell = get_fuelgauge_value(client, FG_VOLTAGE); + int fg_current = get_fuelgauge_value(client, FG_CURRENT); + int threshold = 0; + + threshold = 3300 + ((fg_current * 17) / 100); + + if (fg_vcell <= threshold) + return true; + else + return false; +} + +static bool fuelgauge_recovery_handler(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + int current_soc; + int avsoc; + int temperature; + + if (fuelgauge->info.soc > LOW_BATTERY_SOC_REDUCE_UNIT) { + dev_err(&client->dev, + "%s: Reduce the Reported SOC by 1%%\n", + __func__); + current_soc = + get_fuelgauge_value(client, FG_LEVEL); + + if (current_soc) { + dev_info(&client->dev, + "%s: Returning to Normal discharge path\n", + __func__); + dev_info(&client->dev, + "%s: Actual SOC(%d) non-zero\n", + __func__, current_soc); + fuelgauge->info.is_low_batt_alarm = false; + } else { + temperature = + get_fuelgauge_value(client, FG_TEMPERATURE); + avsoc = + get_fuelgauge_value(client, FG_AV_SOC); + + if ((fuelgauge->info.soc > avsoc) || + (temperature < 0)) { + fuelgauge->info.soc -= + LOW_BATTERY_SOC_REDUCE_UNIT; + dev_err(&client->dev, + "%s: New Reduced RepSOC (%d)\n", + __func__, fuelgauge->info.soc); + } else + dev_info(&client->dev, + "%s: Waiting for recovery (AvSOC:%d)\n", + __func__, avsoc); + } + } + + return fuelgauge->info.is_low_batt_alarm; +} + +static int get_fuelgauge_soc(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + union power_supply_propval value; + int fg_soc; + int fg_vfsoc; + int fg_vcell; + int fg_current; + int avg_current; + ktime_t current_time; + struct timespec ts; + int fullcap_check_interval; + + if (fuelgauge->info.is_low_batt_alarm) + if (fuelgauge_recovery_handler(client)) + goto return_soc; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + /* check fullcap range */ + fullcap_check_interval = + (ts.tv_sec - fuelgauge->info.fullcap_check_interval); + if (fullcap_check_interval > + VFFULLCAP_CHECK_INTERVAL) { + dev_info(&client->dev, + "%s: check fullcap range (interval:%d)\n", + __func__, fullcap_check_interval); + fg_check_vf_fullcap_range(client); + fuelgauge->info.fullcap_check_interval = ts.tv_sec; + } + + fg_soc = get_fuelgauge_value(client, FG_LEVEL); + if (fg_soc < 0) { + dev_info(&client->dev, "Can't read soc!!!"); + fg_soc = fuelgauge->info.soc; + } + + if (fuelgauge->info.low_batt_boot_flag) { + fg_soc = 0; + + if (fuelgauge->pdata->check_cable_callback() != + POWER_SUPPLY_TYPE_BATTERY && + !is_booted_in_low_battery(client)) { + fg_adjust_capacity(client); + fuelgauge->info.low_batt_boot_flag = 0; + } + + if (fuelgauge->pdata->check_cable_callback() == + POWER_SUPPLY_TYPE_BATTERY) + fuelgauge->info.low_batt_boot_flag = 0; + } + + fg_vcell = get_fuelgauge_value(client, FG_VOLTAGE); + fg_current = get_fuelgauge_value(client, FG_CURRENT); + avg_current = get_fuelgauge_value(client, FG_CURRENT_AVG); + fg_vfsoc = get_fuelgauge_value(client, FG_VF_SOC); + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + /* Algorithm for reducing time to fully charged (from MAXIM) */ + if (value.intval != SEC_BATTERY_CHARGING_NONE && + value.intval != SEC_BATTERY_CHARGING_RECHARGING && + fuelgauge->cable_type != POWER_SUPPLY_TYPE_USB && + /* Skip when first check after boot up */ + !fuelgauge->info.is_first_check && + (fg_vfsoc > VFSOC_FOR_FULLCAP_LEARNING && + (fg_current > LOW_CURRENT_FOR_FULLCAP_LEARNING && + fg_current < HIGH_CURRENT_FOR_FULLCAP_LEARNING) && + (avg_current > LOW_AVGCURRENT_FOR_FULLCAP_LEARNING && + avg_current < HIGH_AVGCURRENT_FOR_FULLCAP_LEARNING))) { + + if (fuelgauge->info.full_check_flag == 2) { + dev_info(&client->dev, + "%s: force fully charged SOC !! (%d)", + __func__, fuelgauge->info.full_check_flag); + fg_set_full_charged(client); + fg_soc = get_fuelgauge_value(client, FG_LEVEL); + } else if (fuelgauge->info.full_check_flag < 2) + dev_info(&client->dev, + "%s: full_check_flag (%d)", + __func__, fuelgauge->info.full_check_flag); + + /* prevent overflow */ + if (fuelgauge->info.full_check_flag++ > 10000) + fuelgauge->info.full_check_flag = 3; + } else + fuelgauge->info.full_check_flag = 0; + + /* Checks vcell level and tries to compensate SOC if needed.*/ + /* If jig cable is connected, then skip low batt compensation check. */ + if (!fuelgauge->pdata->check_jig_status() && + value.intval == SEC_BATTERY_CHARGING_NONE) + fg_soc = low_batt_compensation( + client, fg_soc, fg_vcell, fg_current); + + if (fuelgauge->info.is_first_check) + fuelgauge->info.is_first_check = false; + + fuelgauge->info.soc = fg_soc; + +return_soc: + dev_dbg(&client->dev, "%s: soc(%d), low_batt_alarm(%d)\n", + __func__, fuelgauge->info.soc, + fuelgauge->info.is_low_batt_alarm); + + return fg_soc; +} + +static void full_comp_work_handler(struct work_struct *work) +{ + struct sec_fg_info *fg_info = + container_of(work, struct sec_fg_info, full_comp_work.work); + struct sec_fuelgauge_info *fuelgauge = + container_of(fg_info, struct sec_fuelgauge_info, info); + int avg_current; + union power_supply_propval value; + + avg_current = get_fuelgauge_value(fuelgauge->client, FG_CURRENT_AVG); + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + if (avg_current >= 25) { + cancel_delayed_work(&fuelgauge->info.full_comp_work); + schedule_delayed_work(&fuelgauge->info.full_comp_work, 100); + } else { + dev_info(&fuelgauge->client->dev, + "%s: full charge compensation start (avg_current %d)\n", + __func__, avg_current); + fg_fullcharged_compensation(fuelgauge->client, + (int)(value.intval == + SEC_BATTERY_CHARGING_RECHARGING), false); + } +} + +bool sec_hal_fg_init(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = + i2c_get_clientdata(client); + ktime_t current_time; + struct timespec ts; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + fuelgauge->info.fullcap_check_interval = ts.tv_sec; + + fuelgauge->info.is_low_batt_alarm = false; + fuelgauge->info.is_first_check = true; + + /* Init parameters to prevent wrong compensation. */ + fuelgauge->info.previous_fullcap = + fg_read_register(client, FULLCAP_REG); + fuelgauge->info.previous_vffullcap = + fg_read_register(client, FULLCAP_NOM_REG); + + fg_read_model_data(client); + fg_periodic_read(client); + + if (fuelgauge->pdata->check_cable_callback() != + POWER_SUPPLY_TYPE_BATTERY && + is_booted_in_low_battery(client)) + fuelgauge->info.low_batt_boot_flag = 1; + + if (fuelgauge->pdata->check_jig_status()) + fg_reset_capacity_by_jig_connection(client); + + INIT_DELAYED_WORK(&fuelgauge->info.full_comp_work, + full_comp_work_handler); + + return true; +} + +bool sec_hal_fg_suspend(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_resume(struct i2c_client *client) +{ + return true; +} + +bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc) +{ + if (fg_alert_init(client, soc) > 0) + return true; + else + return false; +} + +bool sec_hal_fg_is_fuelalerted(struct i2c_client *client) +{ + if (get_fuelgauge_value(client, FG_CHECK_STATUS) > 0) + return true; + else + return false; +} + +bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) +{ + struct sec_fuelgauge_info *fuelgauge = + (struct sec_fuelgauge_info *)irq_data; + union power_supply_propval value; + int overcurrent_limit_in_soc; + int current_soc = + get_fuelgauge_value(fuelgauge->client, FG_LEVEL); + + if (fuelgauge->info.soc <= STABLE_LOW_BATTERY_DIFF) + overcurrent_limit_in_soc = STABLE_LOW_BATTERY_DIFF_LOWBATT; + else + overcurrent_limit_in_soc = STABLE_LOW_BATTERY_DIFF; + + if ((fuelgauge->info.soc - current_soc) > + overcurrent_limit_in_soc) { + dev_info(&fuelgauge->client->dev, + "%s: Abnormal Current Consumption jump by %d units\n", + __func__, ((fuelgauge->info.soc - current_soc))); + dev_info(&fuelgauge->client->dev, + "%s: Last Reported SOC (%d).\n", + __func__, fuelgauge->info.soc); + + fuelgauge->info.is_low_batt_alarm = true; + + if (fuelgauge->info.soc >= + LOW_BATTERY_SOC_REDUCE_UNIT) + return true; + } + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + if (value.intval == + SEC_BATTERY_CHARGING_NONE) { + dev_err(&fuelgauge->client->dev, + "Set battery level as 0, power off.\n"); + fuelgauge->info.soc = 0; + value.intval = 0; + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CAPACITY, value); + } + + return true; +} + +bool sec_hal_fg_full_charged(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = + i2c_get_clientdata(client); + union power_supply_propval value; + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + + /* full charge compensation algorithm by MAXIM */ + fg_fullcharged_compensation(client, + (int)(value.intval == SEC_BATTERY_CHARGING_RECHARGING), true); + + cancel_delayed_work(&fuelgauge->info.full_comp_work); + schedule_delayed_work(&fuelgauge->info.full_comp_work, 100); + + return false; +} + +bool sec_hal_fg_reset(struct i2c_client *client) +{ + if (!fg_reset_soc(client)) + return true; + else + return false; +} + +bool sec_hal_fg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_fuelgauge_value(client, FG_VOLTAGE); + break; + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (val->intval) { + case SEC_BATTEY_VOLTAGE_AVERAGE: + val->intval = 0; + break; + case SEC_BATTEY_VOLTAGE_OCV: + val->intval = fg_read_vfocv(client); + break; + } + break; + /* Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_fuelgauge_value(client, FG_CURRENT); + break; + /* Average Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = get_fuelgauge_value(client, FG_CURRENT_AVG); + break; + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_fuelgauge_soc(client); + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = get_fuelgauge_value(client, FG_TEMPERATURE); + break; + default: + return false; + } + return true; +} + +bool sec_hal_fg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_fuelgauge_info *fuelgauge = + i2c_get_clientdata(client); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval != POWER_SUPPLY_TYPE_BATTERY) { + if (fuelgauge->info.is_low_batt_alarm) { + dev_info(&client->dev, + "%s: Reset low_batt_alarm\n", + __func__); + fuelgauge->info.is_low_batt_alarm = false; + } + + reset_low_batt_comp_cnt(client); + } + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + break; + default: + return false; + } + return true; +} + +ssize_t sec_hal_fg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int i = 0; + char *str = NULL; + + switch (offset) { +/* case FG_REG: */ +/* break; */ + case FG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%02x%02x\n", + fg->reg_data[1], fg->reg_data[0]); + break; + case FG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + fg_read_regs(fg->client, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_hal_fg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_fuelgauge_info *fg = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int ret = 0; + int x = 0; + + switch (offset) { + case FG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + fg->reg_addr = x; + fg_i2c_read(fg->client, + fg->reg_addr, fg->reg_data, 2); + dev_dbg(&fg->client->dev, + "%s: (read) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, + fg->reg_data[1], fg->reg_data[0]); + ret = count; + } + break; + case FG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + dev_dbg(&fg->client->dev, + "%s: (write) addr = 0x%x, data = 0x%02x%02x\n", + __func__, fg->reg_addr, x); + fg_write_and_verify_register(fg->client, + fg->reg_addr, (u16)x); + ret = count; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#endif + diff --git a/drivers/battery/max77693_charger.c b/drivers/battery/max77693_charger.c new file mode 100644 index 0000000..9ed3122 --- /dev/null +++ b/drivers/battery/max77693_charger.c @@ -0,0 +1,1644 @@ +/* + * max77693_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/battery/samsung_battery.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> +#include <linux/power/charger-manager.h> +#ifdef CONFIG_USB_HOST_NOTIFY +#include <linux/host_notify.h> +#include <plat/devs.h> +#endif +#include <plat/gpio-cfg.h> +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#endif +#endif + +/* MAX77693 Registers(defined @max77693-private.h) */ + +/* MAX77693_CHG_REG_CHG_INT */ +#define MAX77693_BYP_I (1 << 0) +#define MAX77693_THM_I (1 << 2) +#define MAX77693_BAT_I (1 << 3) +#define MAX77693_CHG_I (1 << 4) +#define MAX77693_CHGIN_I (1 << 6) + +/* MAX77693_CHG_REG_CHG_INT_MASK */ +#define MAX77693_BYP_IM (1 << 0) +#define MAX77693_THM_IM (1 << 2) +#define MAX77693_BAT_IM (1 << 3) +#define MAX77693_CHG_IM (1 << 4) +#define MAX77693_CHGIN_IM (1 << 6) + +/* MAX77693_CHG_REG_CHG_INT_OK */ +#define MAX77693_BYP_OK 0x01 +#define MAX77693_BYP_OK_SHIFT 0 +#define MAX77693_THM_OK 0x04 +#define MAX77693_THM_OK_SHIFT 2 +#define MAX77693_BAT_OK 0x08 +#define MAX77693_BAT_OK_SHIFT 3 +#define MAX77693_CHG_OK 0x10 +#define MAX77693_CHG_OK_SHIFT 4 +#define MAX77693_CHGIN_OK 0x40 +#define MAX77693_CHGIN_OK_SHIFT 6 +#define MAX77693_DETBAT 0x80 +#define MAX77693_DETBAT_SHIFT 7 + +/* MAX77693_CHG_REG_CHG_DTLS_00 */ +#define MAX77693_THM_DTLS 0x07 +#define MAX77693_THM_DTLS_SHIFT 0 +#define MAX77693_CHGIN_DTLS 0x60 +#define MAX77693_CHGIN_DTLS_SHIFT 5 + +/* MAX77693_CHG_REG_CHG_DTLS_01 */ +#define MAX77693_CHG_DTLS 0x0F +#define MAX77693_CHG_DTLS_SHIFT 0 +#define MAX77693_BAT_DTLS 0x70 +#define MAX77693_BAT_DTLS_SHIFT 4 + +/* MAX77693_CHG_REG_CHG_DTLS_02 */ +#define MAX77693_BYP_DTLS 0x0F +#define MAX77693_BYP_DTLS_SHIFT 0 +#define MAX77693_BYP_DTLS0 0x1 +#define MAX77693_BYP_DTLS1 0x2 +#define MAX77693_BYP_DTLS2 0x4 +#define MAX77693_BYP_DTLS3 0x8 + +/* MAX77693_CHG_REG_CHG_CNFG_00 */ +#define MAX77693_MODE_DEFAULT 0x04 +#define MAX77693_MODE_CHGR 0x01 +#define MAX77693_MODE_OTG 0x02 +#define MAX77693_MODE_BUCK 0x04 + +/* MAX77693_CHG_REG_CHG_CNFG_02 */ +#define MAX77693_CHG_CC 0x3F + +/* MAX77693_CHG_REG_CHG_CNFG_04 */ +#define MAX77693_CHG_MINVSYS_MASK 0x1F +#define MAX77693_CHG_MINVSYS_SHIFT 5 + +/* MAX77693_CHG_REG_CHG_CNFG_09 */ +#define MAX77693_CHG_CHGIN_LIM 0x7F + +/* MAX77693_MUIC_REG_CDETCTRL1 */ +#define MAX77693_CHGTYPMAN 0x02 +#define MAX77693_CHGTYPMAN_SHIFT 1 + +/* MAX77693_MUIC_REG_STATUS2 */ +#define MAX77693_VBVOLT 0x40 +#define MAX77693_VBVOLT_SHIFT 6 +#define MAX77693_CHGDETRUN 0x08 +#define MAX77693_CHGDETRUN_SHIFT 3 +#define MAX77693_CHGTYPE 0x07 +#define MAX77693_CHGTYPE_SHIFT 0 + +/* irq */ +#define IRQ_DEBOUNCE_TIME 20 /* msec */ + +/* charger type detection */ +#define DET_ERR_RETRY 5 +#define DET_ERR_DELAY 200 + +/* soft charging */ +#define SOFT_CHG_START_CURR 100 /* mA */ +#define SOFT_CHG_START_DUR 100 /* ms */ +#define SOFT_CHG_CURR_STEP 100 /* mA */ +#define SOFT_CHG_STEP_DUR 20 /* ms */ + +struct max77693_charger_data { + struct max77693_dev *max77693; + + struct power_supply charger; + + struct delayed_work update_work; + + /* mutex */ + struct mutex irq_lock; + struct mutex ops_lock; + + /* wakelock */ + struct wake_lock update_wake_lock; + + unsigned int charging_state; + unsigned int charging_type; + unsigned int battery_state; + unsigned int battery_present; + unsigned int cable_type; + unsigned int charging_current; + unsigned int vbus_state; + + int irq_bypass; + int irq_therm; + int irq_battery; + int irq_charge; + int irq_chargin; + + /* software regulation */ + bool soft_reg_state; + int soft_reg_current; + + /* unsufficient power */ + bool reg_loop_deted; + +#ifdef CONFIG_BATTERY_WPC_CHARGER + /* wireless charge, w(wpc), v(vbus) */ + int wc_w_gpio; + int wc_w_irq; + int wc_w_state; + int wc_v_gpio; + int wc_v_irq; + int wc_v_state; + bool wc_pwr_det; +#endif + + struct max77693_charger_platform_data *charger_pdata; + + int irq; + u8 irq_reg; + int irq_cnt; + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS + struct dentry *charger_debugfs_dir; +#endif +#endif +}; + +static void max77693_dump_reg(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + u32 reg_addr; + pr_info("%s\n", __func__); + + for (reg_addr = 0xB0; reg_addr <= 0xC5; reg_addr++) { + max77693_read_reg(i2c, reg_addr, ®_data); + pr_info("max77693: c: 0x%02x(0x%02x)\n", reg_addr, reg_data); + } +} + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +static int max77693_is_topoff_state(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + int state; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_DTLS_01, ®_data); + reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT); + pr_debug("%s: CHG_DTLS(0x%02x)\n", __func__, reg_data); + + if (reg_data == 0x3 || reg_data == 0x4) + return 1; + else + return 0; +} + +static bool check_charger_unlock_state(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, ®_data); + pr_debug("%s: chgprot = %d\n", __func__, reg_data); + + if ((reg_data&0x0C) != 0x0C) { + pr_info("%s: NOT unlock!(%d)\n", __func__, reg_data); + return false; + } else + return true; +} +#endif + +static int max77693_get_battery_present(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_INT_OK, ®_data); + pr_debug("%s: CHG_INT_OK(0x%02x)\n", __func__, reg_data); + + reg_data = ((reg_data & MAX77693_DETBAT) >> MAX77693_DETBAT_SHIFT); + + return !reg_data; +} + +static int max77693_get_vbus_state(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + int state; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_DTLS_00, ®_data); + reg_data = ((reg_data & MAX77693_CHGIN_DTLS) >> + MAX77693_CHGIN_DTLS_SHIFT); + pr_debug("%s: CHGIN_DTLS(0x%02x)\n", __func__, reg_data); + + switch (reg_data) { + case 0x00: + state = POWER_SUPPLY_VBUS_UVLO; + break; + case 0x01: + state = POWER_SUPPLY_VBUS_WEAK; + break; + case 0x02: + state = POWER_SUPPLY_VBUS_OVLO; + break; + case 0x03: + state = POWER_SUPPLY_VBUS_GOOD; + break; + default: + state = POWER_SUPPLY_VBUS_UNKNOWN; + break; + } + + chg_data->vbus_state = state; + return state; +} + +static int max77693_get_charger_type(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + int state; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_DTLS_01, ®_data); + reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT); + pr_debug("%s: CHG_DTLS(0x%02x)\n", __func__, reg_data); + + switch (reg_data) { + case 0x0: + state = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 0x1: + case 0x2: + case 0x3: + state = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case 0x4: + case 0x8: + case 0xA: + case 0xB: + state = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + state = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + break; + } + + chg_data->charging_type = state; + return state; +} + +static int max77693_get_charger_state(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + int state; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_DTLS_01, ®_data); + reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT); +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + pr_info("%s: CHG_DTLS(0x%02x)\n", __func__, reg_data); +#else + pr_debug("%s: CHG_DTLS(0x%02x)\n", __func__, reg_data); +#endif + + switch (reg_data) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + state = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x4: + state = POWER_SUPPLY_STATUS_FULL; + break; + case 0x5: + case 0x6: + case 0x7: + state = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 0x8: + case 0xA: + case 0xB: + state = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + state = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + chg_data->charging_state = state; + return state; +} + +static void max77693_set_charger_state(struct max77693_charger_data *chg_data, + int enable) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_debug("%s: enable=%d\n", __func__, enable); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_00, ®_data); + + if (enable) + reg_data |= MAX77693_MODE_CHGR; + else + reg_data &= ~MAX77693_MODE_CHGR; + + pr_debug("%s: CHG_CNFG_00(0x%02x)\n", __func__, reg_data); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); +} + +static void max77693_set_buck(struct max77693_charger_data *chg_data, + int enable) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_debug("%s: enable=%d\n", __func__, enable); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_00, ®_data); + + if (enable) + reg_data |= MAX77693_MODE_BUCK; + else + reg_data &= ~MAX77693_MODE_BUCK; + + pr_debug("%s: CHG_CNFG_00(0x%02x)\n", __func__, reg_data); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); +} + +int max77693_get_input_current(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + int get_current = 0; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, ®_data); + pr_debug("%s: CHG_CNFG_09(0x%02x)\n", __func__, reg_data); + + get_current = reg_data * 20; + + pr_debug("%s: get input current: %dmA\n", __func__, get_current); + return get_current; +} + +void max77693_set_input_current(struct max77693_charger_data *chg_data, + unsigned int set_current) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + int in_curr; + u8 set_curr_reg, now_curr_reg; + int step; + pr_debug("%s: set input current as %dmA\n", __func__, set_current); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (!check_charger_unlock_state(chg_data)) + pr_err("%s: charger NOT unlock state!!!\n", __func__); +#endif + + if (set_current == OFF_CURR) { + pr_debug("%s: buck off current(%d)\n", __func__, set_current); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, 0); + + max77693_set_buck(chg_data, DISABLE); + + if (chg_data->soft_reg_state == true) { + pr_info("%s: exit soft regulation loop\n", __func__); + chg_data->soft_reg_state = false; + } + + return; + } else + max77693_set_buck(chg_data, ENABLE); + + /* Set input current limit */ + if (chg_data->soft_reg_state) { + pr_info("%s: now in soft regulation loop: %d\n", __func__, + chg_data->soft_reg_current); + in_curr = max77693_get_input_current(chg_data); + if (in_curr == chg_data->soft_reg_current) { + pr_info("%s: same input current: %dmA\n", + __func__, in_curr); + return; + } + set_curr_reg = (chg_data->soft_reg_current / 20); + } else + set_curr_reg = (set_current / 20); + + /* soft charge, 1st step, under 100mA, over 50ms */ + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, + (SOFT_CHG_START_CURR / 20)); + pr_debug("%s: soft charge, %dmA for %dms\n", __func__, + SOFT_CHG_START_CURR, SOFT_CHG_START_DUR); + msleep(SOFT_CHG_START_DUR); + + step = 0; + do { + now_curr_reg = ((SOFT_CHG_START_CURR + + (SOFT_CHG_CURR_STEP * (++step))) / 20); + now_curr_reg = MIN(now_curr_reg, set_curr_reg); + pr_debug("%s: step%d: now curr(%dmA, 0x%x)\n", __func__, step, + (SOFT_CHG_START_CURR + (SOFT_CHG_CURR_STEP * (step))), + now_curr_reg); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, + now_curr_reg); + msleep(SOFT_CHG_STEP_DUR); + } while (now_curr_reg < set_curr_reg); + + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, set_curr_reg); +} + +int max77693_get_charge_current(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + int get_current = 0; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_02, ®_data); + pr_debug("%s: CHG_CNFG_02(0x%02x)\n", __func__, reg_data); + + reg_data &= MAX77693_CHG_CC; + get_current = chg_data->charging_current = reg_data * 333 / 10; + + pr_debug("%s: get charge current: %dmA\n", __func__, get_current); + return get_current; +} + +void max77693_set_charge_current(struct max77693_charger_data *chg_data, + unsigned int set_current) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_debug("%s: set charge current as %dmA\n", __func__, set_current); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (!check_charger_unlock_state(chg_data)) + pr_err("%s: charger NOT unlock state!!!\n", __func__); +#endif + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_02, ®_data); + + reg_data &= ~MAX77693_CHG_CC; + reg_data |= ((set_current * 3 / 100) << 0); + + pr_debug("%s: reg_data(0x%02x)\n", __func__, reg_data); + + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_02, reg_data); +} + +void max77693_reset_chgtyp(struct max77693_charger_data *chg_data) +{ + u8 reg_data; + pr_info("%s\n", __func__); + + /* reset charger detection mode */ + max77693_read_reg(chg_data->max77693->muic, + MAX77693_MUIC_REG_CDETCTRL1, + ®_data); + reg_data |= MAX77693_CHGTYPMAN; + max77693_write_reg(chg_data->max77693->muic, + MAX77693_MUIC_REG_CDETCTRL1, + reg_data); +} + +#ifdef CONFIG_BATTERY_WPC_CHARGER +static bool max77693_get_wc_state(struct max77693_charger_data *chg_data) +{ + bool state; + int wc_w_state, wc_v_state; + pr_debug("%s\n", __func__); + + wc_w_state = !gpio_get_value(chg_data->wc_w_gpio); + + if (chg_data->wc_pwr_det == true) { + wc_v_state = !gpio_get_value(chg_data->wc_v_gpio); + if ((wc_w_state == CHARGE_ENABLE) && + (wc_v_state == CHARGE_DISABLE)) { + pr_debug("%s: wpc(%d), vbus(%d), wc ok\n", + __func__, wc_w_state, wc_v_state); + state = true; + } else { + pr_debug("%s: wpc(%d), vbus(%d), wc not ok\n", + __func__, wc_w_state, wc_v_state); + state = false; + } + + chg_data->wc_w_state = wc_w_state; + chg_data->wc_v_state = wc_v_state; + } else { + if (wc_w_state == CHARGE_ENABLE) { + pr_debug("%s: wpc(%d), wc ok\n", + __func__, wc_w_state); + state = true; + } else { + pr_debug("%s: wpc(%d), wc not ok\n", + __func__, wc_w_state); + state = false; + } + + chg_data->wc_w_state = wc_w_state; + } + + return state; +} +#endif + +/* check chargable dock */ +static int max77693_get_dock_type(struct max77693_charger_data *chg_data) +{ + int state; + u8 reg_data; + int muic_cb_typ; + u8 mu_st2, vbvolt; + pr_debug("%s\n", __func__); + + muic_cb_typ = max77693_muic_get_charging_type(); + pr_debug("%s: muic cable type(%d)\n", __func__, muic_cb_typ); + + /* dock detect from muic */ + if ((muic_cb_typ == CABLE_TYPE_CARDOCK_MUIC) || + (muic_cb_typ == CABLE_TYPE_DESKDOCK_MUIC)) { + + /* check vbvolt */ + max77693_read_reg(chg_data->max77693->muic, + MAX77693_MUIC_REG_STATUS2, &mu_st2); + vbvolt = ((mu_st2 & MAX77693_VBVOLT) >> + MAX77693_VBVOLT_SHIFT); + + pr_info("%s: dock detected(%d), vbvolt(%d)\n", __func__, + muic_cb_typ, vbvolt); + + if (vbvolt == ENABLE) { + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_CNFG_00, ®_data); + reg_data |= CHG_CNFG_00_DIS_MUIC_CTRL_MASK; + max77693_write_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_CNFG_00, reg_data); + state = POWER_SUPPLY_TYPE_DOCK; + } else { + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_CNFG_00, ®_data); + reg_data &= ~CHG_CNFG_00_DIS_MUIC_CTRL_MASK; + max77693_write_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_CNFG_00, reg_data); + state = POWER_SUPPLY_TYPE_BATTERY; + } + } else + pr_debug("%s: dock not detected(%d), vbvolt(%d)\n", __func__, + muic_cb_typ, vbvolt); + + return state; +} + +static int max77693_get_cable_type(struct max77693_charger_data *chg_data) +{ + int state; + u8 reg_data, mu_adc, mu_adc1k, otg; + u8 dtls_00, chgin_dtls; + u8 mu_st2, chgdetrun, vbvolt, chgtyp; + bool wc_state; + bool retry_det, chg_det_erred; + int retry_cnt = 0; + pr_debug("%s\n", __func__); + + mutex_lock(&chg_data->ops_lock); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (!check_charger_unlock_state(chg_data)) + pr_err("%s: charger NOT unlock state!!!\n", __func__); +#endif + + /* If OTG enabled, skip detecting charger cable */ + /* So mhl cable does not have adc ID that below condition */ + /* can`t include adc1k mask */ + max77693_read_reg(chg_data->max77693->muic, + MAX77693_MUIC_REG_STATUS1, ®_data); + pr_debug("%s: MUIC_REG_STATUS1(0x%02x)\n", __func__, reg_data); + mu_adc1k = reg_data & (0x1 << 7); /* STATUS1_ADC1K_MASK */ + mu_adc = reg_data & 0x1F; + + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_CNFG_00, ®_data); + pr_debug("%s: CHG_REG_CHG_CNFG_00(0x%02x)\n", __func__, reg_data); + otg = reg_data & MAX77693_MODE_OTG; + + if (otg || (mu_adc == 0x00 && !mu_adc1k)) { + pr_info("%s: otg enabled(otg(0x%x), adc(0x%x))\n", + __func__, otg, mu_adc); + state = POWER_SUPPLY_TYPE_BATTERY; + goto chg_det_finish; + } + + /* dock charger */ + state = max77693_get_dock_type(chg_data); + if (state == POWER_SUPPLY_TYPE_DOCK) { + pr_info("%s: dock charger detected\n", __func__); + goto chg_det_finish; + } + + chg_det_erred = true; /* TEMP: set as true for logging */ + do { + retry_det = false; + + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_DTLS_00, &dtls_00); + max77693_read_reg(chg_data->max77693->muic, + MAX77693_MUIC_REG_STATUS2, &mu_st2); + chgin_dtls = ((dtls_00 & MAX77693_CHGIN_DTLS) >> + MAX77693_CHGIN_DTLS_SHIFT); + chgdetrun = ((mu_st2 & MAX77693_CHGDETRUN) >> + MAX77693_CHGDETRUN_SHIFT); + vbvolt = ((mu_st2 & MAX77693_VBVOLT) >> + MAX77693_VBVOLT_SHIFT); + chgtyp = ((mu_st2 & MAX77693_CHGTYPE) >> + MAX77693_CHGTYPE_SHIFT); + if (chg_det_erred) + pr_info("%s: CHGIN(0x%x). MU_ST2(0x%x), " + "CDR(0x%x), VB(0x%x), CHGTYP(0x%x)\n", __func__, + chgin_dtls, mu_st2, + chgdetrun, vbvolt, chgtyp); + + /* input power state */ + if (((chgin_dtls != 0x0) && (vbvolt == 0x1)) || + ((chgin_dtls == 0x0) && (vbvolt == 0x0)) || + (chg_data->reg_loop_deted == true)) { + pr_info("%s: sync power: CHGIN(0x%x), VB(0x%x), REG(%d)\n", + __func__, chgin_dtls, vbvolt, + chg_data->reg_loop_deted); + } else { + pr_err("%s: async power: CHGIN(0x%x), VB(0x%x), REG(%d)\n", + __func__, chgin_dtls, vbvolt, + chg_data->reg_loop_deted); + + /* check chargable input power */ + if ((chgin_dtls == 0x0) && + (chg_data->cable_type == + POWER_SUPPLY_TYPE_BATTERY)) { + pr_err("%s: unchargable power\n", __func__); + state = POWER_SUPPLY_TYPE_BATTERY; + goto chg_det_finish; + } + } + + /* charger detect running */ + if (chgdetrun == 0x1) { + pr_info("%s: CDR(0x%x)\n", __func__, chgdetrun); + goto chg_det_err; + } + + /* muic power and charger type */ + if (((vbvolt == 0x1) && (chgtyp == 0x00)) || + ((vbvolt == 0x0) && (chgtyp != 0x00))) { + pr_info("%s: VB(0x%x), CHGTYP(0x%x)\n", + __func__, vbvolt, chgtyp); + goto chg_det_err; + } + + /* charger type ok */ + if (chg_det_erred) + pr_info("%s: chgtyp detect ok, " + "CHGIN(0x%x). MU_ST2(0x%x), " + "CDR(0x%x), VB(0x%x), CHGTYP(0x%x)\n", + __func__, chgin_dtls, mu_st2, + chgdetrun, vbvolt, chgtyp); + + break; +chg_det_err: + retry_det = true; + chg_det_erred = true; + + pr_err("%s: chgtyp detect err, retry %d, " + "CHGIN(0x%x). MU_ST2(0x%x), CDR(0x%x), VB(0x%x), CHGTYP(0x%x)\n", + __func__, ++retry_cnt, chgin_dtls, + mu_st2, chgdetrun, vbvolt, chgtyp); + + /* after 200ms * 5 */ + if (retry_cnt == DET_ERR_RETRY) { + pr_info("%s: reset charger detection mode\n", + __func__); + + /* reset charger detection mode */ + max77693_reset_chgtyp(chg_data); + } + msleep(DET_ERR_DELAY); + } while ((retry_det == true) && (retry_cnt < DET_ERR_RETRY)); + + switch (chgtyp) { + case 0x0: /* Noting attached */ + /* clear regulation loop flag */ + chg_data->reg_loop_deted = false; + state = POWER_SUPPLY_TYPE_BATTERY; + break; + case 0x1: /* USB cabled */ + case 0x4: /* Apple 500mA charger */ + state = POWER_SUPPLY_TYPE_USB; +#ifdef CONFIG_BATTERY_WPC_CHARGER + wc_state = max77693_get_wc_state(chg_data); + if (wc_state == true) + state = POWER_SUPPLY_TYPE_WIRELESS; +#endif + break; + case 0x2: /* Charging downstream port */ + state = POWER_SUPPLY_TYPE_USB_CDP; + break; + case 0x3: /* Dedicated charger(up to 1.5A) */ + case 0x5: /* Apple 1A or 2A charger */ + case 0x6: /* Special charger */ + state = POWER_SUPPLY_TYPE_MAINS; + break; + default: + state = POWER_SUPPLY_TYPE_BATTERY; + break; + } + +chg_det_finish: + pr_info("%s: cable type(%d)\n", __func__, state); + chg_data->cable_type = state; + + mutex_unlock(&chg_data->ops_lock); + + return state; +} + +static int max77693_get_battery_state(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + int state; + int vbus_state; + int chg_state; + u8 reg_data; + pr_debug("%s\n", __func__); + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_DTLS_01, ®_data); + reg_data = ((reg_data & MAX77693_BAT_DTLS) >> MAX77693_BAT_DTLS_SHIFT); +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + pr_info("%s: BAT_DTLS(0x%02x)\n", __func__, reg_data); +#else + pr_debug("%s: BAT_DTLS(0x%02x)\n", __func__, reg_data); +#endif + + switch (reg_data) { + case 0x01: + state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case 0x02: + pr_info("%s: battery dead\n", __func__); + state = POWER_SUPPLY_HEALTH_DEAD; + break; + case 0x03: + state = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x04: + pr_info("%s: battery is okay " + "but its voltage is low\n", __func__); + state = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x05: + pr_info("%s: battery ovp\n", __func__); + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + default: + state = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + if (state == POWER_SUPPLY_HEALTH_GOOD) { + /* VBUS OVP state return battery OVP state */ + vbus_state = max77693_get_vbus_state(chg_data); + + /* read CHG_DTLS and detecting battery terminal error */ + chg_state = max77693_get_charger_state(chg_data); + + /* OVP is higher priority */ + if (vbus_state == POWER_SUPPLY_VBUS_OVLO) { + pr_info("%s: vbus ovp\n", __func__); + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if ((reg_data == 0x4) && + (chg_state == POWER_SUPPLY_STATUS_FULL)) { + pr_info("%s: battery terminal error\n", __func__); + state = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + } + } + + chg_data->battery_state = state; + return state; +} + +/* get cable type from muic */ +void max77693_set_muic_cb_type(struct max77693_charger_data *chg_data, int data) +{ + pr_info("%s: muic cable type(%d)\n", __func__, data); + + cancel_delayed_work(&chg_data->update_work); + wake_lock(&chg_data->update_wake_lock); + schedule_delayed_work(&chg_data->update_work, msecs_to_jiffies(500)); +} + +static void max77693_charger_reg_init(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_debug("%s\n", __func__); + + /* unlock charger setting protect */ + reg_data = (0x03 << 2); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, reg_data); + + /* + * fast charge timer 10hrs + * restart threshold disable + * pre-qual charge enable(default) + */ + reg_data = (0x04 << 0) | (0x03 << 4); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_01, reg_data); + + /* + * charge current 466mA(default) + * otg current limit 900mA + */ + reg_data = (1 << 7); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_02, reg_data); + +#if defined(CONFIG_MACH_S2PLUS) + /* top off current 200mA */ + reg_data = 0x04; +#else + /* + * top off current 100mA + * top off timer 0min + */ + if (chg_data->max77693->pmic_rev == MAX77693_REV_PASS1) + reg_data = (0x03 << 0); /* 125mA */ + else + reg_data = (0x00 << 0); /* 100mA */ +#endif + reg_data |= (0x00 << 3); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_03, reg_data); + + /* + * cv voltage 4.2V or 4.35V + * MINVSYS 3.6V(default) + */ + if ((system_rev != 3) && (system_rev >= 1)) + reg_data = (0xDD << 0); + else + reg_data = (0xD6 << 0); + + /* For GC1 Model, MINVSYS is 3.4V*/ +#if defined(CONFIG_MACH_GC1) + reg_data &= MAX77693_CHG_MINVSYS_MASK; + reg_data |= (0x4 << MAX77693_CHG_MINVSYS_SHIFT); +#endif + + pr_info("%s: battery cv voltage %s, (sysrev %d)\n", __func__, + (((reg_data & MAX77693_CHG_MINVSYS_MASK) == \ + (0x1D << 0)) ? "4.35V" : "4.2V"), system_rev); + + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_04, reg_data); + + /* VBYPSET 5V */ + reg_data = 0x50; + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_11, reg_data); + +#if defined(CONFIG_MACH_S2PLUS) + /* charging disable */ + reg_data = 0x00; + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); +#endif + + max77693_dump_reg(chg_data); +} + +#define SW_REG_CURR_STEP_MA 300 +static void max77693_soft_regulation(struct max77693_charger_data *chg_data) +{ + struct i2c_client *i2c = chg_data->max77693->i2c; + u8 reg_data; + pr_info("%s\n", __func__); + + /* enable soft regulation loop */ + chg_data->soft_reg_state = true; + + max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, ®_data); + reg_data &= MAX77693_CHG_CHGIN_LIM; + chg_data->soft_reg_current = reg_data * 20; + chg_data->soft_reg_current -= SW_REG_CURR_STEP_MA; + chg_data->soft_reg_current = max(chg_data->soft_reg_current, 0); + pr_info("%s: %dmA to %dmA\n", __func__, + reg_data * 20, chg_data->soft_reg_current); + + reg_data = (chg_data->soft_reg_current / 20); + pr_debug("%s: reg_data(0x%02x)\n", __func__, reg_data); + max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_09, reg_data); +} + +static void max77693_update_work(struct work_struct *work) +{ + struct max77693_charger_data *chg_data = container_of(work, + struct max77693_charger_data, + update_work.work); + struct power_supply *battery_psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + int vbus_state; + pr_debug("%s\n", __func__); + +#if defined(CONFIG_CHARGER_MANAGER) + /* Notify charger-manager */ + enum cm_event_types type; + + /* only consider battery in/out and external power in/out */ + /* It seems that charger interrupt does not work at all */ + switch (chg_data->irq - chg_data->max77693->irq_base) { + case MAX77693_CHG_IRQ_BAT_I: + type = max77693_get_battery_present(chg_data) ? + CM_EVENT_BATT_IN : CM_EVENT_BATT_OUT; + cm_notify_event(&chg_data->charger, type, NULL); + break; + case MAX77693_CHG_IRQ_CHGIN_I: + cm_notify_event(&chg_data->charger, + CM_EVENT_EXT_PWR_IN_OUT, NULL); + break; + default: + break; + } +#else + if (!battery_psy) { + pr_err("%s: fail to get battery power supply\n", __func__); + wake_unlock(&chg_data->update_wake_lock); + return; + } + + switch (chg_data->irq - chg_data->max77693->irq_base) { + case MAX77693_CHG_IRQ_CHGIN_I: + /* guarantee detection time */ + mdelay(100); + vbus_state = max77693_get_vbus_state(chg_data); + if (vbus_state == POWER_SUPPLY_VBUS_WEAK) { + pr_info("%s: vbus weak\n", __func__); + max77693_soft_regulation(chg_data); + } else + pr_debug("%s: vbus not weak\n", __func__); + break; + default: + break; + + } + + battery_psy->set_property(battery_psy, + POWER_SUPPLY_PROP_STATUS, + &value); +#endif + + wake_unlock(&chg_data->update_wake_lock); +} + +/* Support property from charger */ +static enum power_supply_property max77693_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW +}; + +static int max77693_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77693_charger_data *chg_data = container_of(psy, + struct max77693_charger_data, + charger); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max77693_get_charger_state(chg_data); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = max77693_get_charger_type(chg_data); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max77693_get_battery_state(chg_data); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max77693_get_battery_present(chg_data); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max77693_get_cable_type(chg_data); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = max77693_get_input_current(chg_data); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = max77693_get_charge_current(chg_data); + break; +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = max77693_is_topoff_state(chg_data); + break; +#endif + default: + return -EINVAL; + } + + return 0; +} + +static int max77693_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77693_charger_data *chg_data = container_of(psy, + struct max77693_charger_data, + charger); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + max77693_set_charger_state(chg_data, val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + max77693_set_muic_cb_type(chg_data, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + max77693_set_input_current(chg_data, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + max77693_set_charge_current(chg_data, val->intval); + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t max77693_bypass_irq(int irq, void *data) +{ + struct max77693_charger_data *chg_data = data; + u8 int_ok, dtls_02; + u8 byp_dtls; +#ifdef CONFIG_USB_HOST_NOTIFY + struct host_notifier_platform_data *host_noti_pdata = + host_notifier_device.dev.platform_data; +#endif + pr_info("%s: irq(%d)\n", __func__, irq); + + mutex_lock(&chg_data->irq_lock); + + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_INT_OK, + &int_ok); + + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_DTLS_02, + &dtls_02); + + byp_dtls = ((dtls_02 & MAX77693_BYP_DTLS) >> + MAX77693_BYP_DTLS_SHIFT); + pr_info("%s: INT_OK(0x%02x), BYP_DTLS(0x%02x)\n", + __func__, int_ok, byp_dtls); + + switch (byp_dtls) { + case 0x0: + pr_info("%s: bypass node is okay\n", __func__); + break; + case 0x1: + pr_err("%s: bypass overcurrent limit\n", __func__); +#ifdef CONFIG_USB_HOST_NOTIFY + host_state_notify(&host_noti_pdata->ndev, + NOTIFY_HOST_OVERCURRENT); +#endif + max77693_write_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_CNFG_00, + MAX77693_MODE_DEFAULT); + break; + case 0x8: + pr_err("%s: chgin regulation loop is active\n", __func__); + if (chg_data->cable_type != POWER_SUPPLY_TYPE_WIRELESS) { + /* software regulation */ + max77693_soft_regulation(chg_data); + } else + pr_err("%s: now in wireless charging, " + " do not sw regulation\n", __func__); + + break; + default: + pr_info("%s: bypass reserved\n", __func__); + break; + } + + cancel_delayed_work(&chg_data->update_work); + wake_lock(&chg_data->update_wake_lock); + schedule_delayed_work(&chg_data->update_work, msecs_to_jiffies(100)); + + mutex_unlock(&chg_data->irq_lock); + + return IRQ_HANDLED; +} + +/* TEMP: count same state irq occured */ +static irqreturn_t max77693_charger_irq(int irq, void *data) +{ + struct max77693_charger_data *chg_data = data; + u8 prev_int_ok, int_ok, dtls_00, dtls_01; + u8 thm_dtls, chgin_dtls, chg_dtls, bat_dtls; + u8 mu_st2; + pr_info("%s: irq(%d)\n", __func__, irq); + + mutex_lock(&chg_data->irq_lock); + + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_INT_OK, + &prev_int_ok); + + msleep(IRQ_DEBOUNCE_TIME); + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_INT_OK, + &int_ok); + if ((chg_data->irq_reg == int_ok) && (prev_int_ok != int_ok)) { + pr_info("%s: irq debounced(0x%x, 0x%x, 0x%x), return\n", + __func__, chg_data->irq_reg, prev_int_ok, int_ok); + mutex_unlock(&chg_data->irq_lock); + return IRQ_HANDLED; + } + + chg_data->irq_reg = int_ok; + chg_data->irq = irq; + + /* charger */ + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_DTLS_00, &dtls_00); + + max77693_read_reg(chg_data->max77693->i2c, + MAX77693_CHG_REG_CHG_DTLS_01, &dtls_01); + + thm_dtls = ((dtls_00 & MAX77693_THM_DTLS) >> + MAX77693_THM_DTLS_SHIFT); + chgin_dtls = ((dtls_00 & MAX77693_CHGIN_DTLS) >> + MAX77693_CHGIN_DTLS_SHIFT); + chg_dtls = ((dtls_01 & MAX77693_CHG_DTLS) >> + MAX77693_CHG_DTLS_SHIFT); + bat_dtls = ((dtls_01 & MAX77693_BAT_DTLS) >> + MAX77693_BAT_DTLS_SHIFT); + + /* muic */ + max77693_read_reg(chg_data->max77693->muic, + MAX77693_MUIC_REG_STATUS2, &mu_st2); + pr_info("%s: INT_OK(0x%x), THM(0x%x), CHGIN(0x%x), CHG(0x%x), BAT(0x%x), " + "ST2(0x%x)\n", __func__, + int_ok, thm_dtls, chgin_dtls, + chg_dtls, bat_dtls, mu_st2); + + cancel_delayed_work(&chg_data->update_work); + wake_lock(&chg_data->update_wake_lock); + schedule_delayed_work(&chg_data->update_work, msecs_to_jiffies(500)); + + mutex_unlock(&chg_data->irq_lock); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_BATTERY_WPC_CHARGER +static irqreturn_t wpc_charger_irq(int irq, void *data) +{ + struct max77693_charger_data *chg_data = data; + int wc_w_state, wc_v_state, wc_v_pud_state; + pr_info("%s: irq(%d)\n", __func__, irq); + + mutex_lock(&chg_data->irq_lock); + + wc_w_state = wc_v_state = 0; + + wc_w_state = !gpio_get_value(chg_data->wc_w_gpio); + if (chg_data->wc_pwr_det == true) { + if ((chg_data->wc_w_state == 0) && (wc_w_state == 1)) { + pr_info("%s: wpc activated, set V_INT as PN\n", + __func__); + s3c_gpio_setpull(chg_data->wc_v_gpio, + S3C_GPIO_PULL_NONE); + enable_irq(chg_data->wc_v_irq); + } else if ((chg_data->wc_w_state == 1) && (wc_w_state == 0)) { + disable_irq(chg_data->wc_v_irq); + pr_info("%s: wpc deactivated, set V_INT as PD\n", + __func__); + s3c_gpio_setpull(chg_data->wc_v_gpio, + S3C_GPIO_PULL_DOWN); + } else { + wc_v_pud_state = s3c_gpio_getpull(chg_data->wc_v_gpio); + pr_info("%s: wpc not changed, V_INT(0x%x)\n", __func__, + wc_v_pud_state); + } + + wc_v_state = !gpio_get_value(chg_data->wc_v_gpio); + pr_info("%s: w(%d to %d), v(%d to %d)\n", __func__, + chg_data->wc_w_state, wc_w_state, + chg_data->wc_v_state, wc_v_state); + + if ((wc_w_state == 1) && (chg_data->wc_v_state != wc_v_state)) { + pr_info("%s: wc power path is changed\n", __func__); + + /* limit input current as 100mA */ + max77693_set_input_current(chg_data, 100); + + /* reset charger detection mode */ + max77693_reset_chgtyp(chg_data); + + cancel_delayed_work(&chg_data->update_work); + wake_lock(&chg_data->update_wake_lock); + schedule_delayed_work(&chg_data->update_work, + msecs_to_jiffies(500)); + } + + chg_data->wc_w_state = wc_w_state; + chg_data->wc_v_state = wc_v_state; + } else { + pr_info("%s: w(%d to %d)\n", __func__, + chg_data->wc_w_state, wc_w_state); + + chg_data->wc_w_state = wc_w_state; + } + + mutex_unlock(&chg_data->irq_lock); + + return IRQ_HANDLED; +} +#endif + +static void max77693_charger_initialize(struct max77693_charger_data *chg_data) +{ + struct max77693_charger_platform_data *charger_pdata = + chg_data->charger_pdata; + struct i2c_client *i2c = chg_data->max77693->i2c; + int i; + + for (i = 0; i < charger_pdata->num_init_data; i++) + max77693_write_reg(i2c, charger_pdata->init_data[i].addr, + charger_pdata->init_data[i].data); +} + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS +static int max77693_debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t max77693_debugfs_read_registers(struct file *filp, + char __user *buffer, size_t count, loff_t *ppos) +{ + struct max77693_charger_data *chg_data = filp->private_data; + int i = 0; + char *buf; + size_t len = 0; + ssize_t ret; + u8 val; + + if (!chg_data) { + pr_err("%s : chg_data is null\n", __func__); + return 0; + } + + if (*ppos != 0) + return 0; + + if (count < sizeof(buf)) + return -ENOSPC; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0xB2; i <= 0xC6; i++) { + max77693_read_reg(chg_data->max77693->i2c, i, &val); + len += snprintf(buf + len, PAGE_SIZE - len, + "%x=%02x", i, val); + if (i == 0xC6) + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + else + len += snprintf(buf + len, PAGE_SIZE - len, " "); + } + + ret = simple_read_from_buffer(buffer, len, ppos, buf, PAGE_SIZE); + kfree(buf); + + return ret; +} + +static const struct file_operations max77693_debugfs_fops = { + .owner = THIS_MODULE, + .open = max77693_debugfs_open, + .read = max77693_debugfs_read_registers, +}; +#endif +#endif + +static __devinit int max77693_charger_probe(struct platform_device *pdev) +{ + struct max77693_charger_data *chg_data; + struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); + struct max77693_platform_data *pdata = dev_get_platdata(max77693->dev); + int ret; + + pr_info("%s: charger init start\n", __func__); + + chg_data = kzalloc(sizeof(struct max77693_charger_data), GFP_KERNEL); + if (!chg_data) + return -ENOMEM; + + platform_set_drvdata(pdev, chg_data); + chg_data->max77693 = max77693; + + mutex_init(&chg_data->irq_lock); + mutex_init(&chg_data->ops_lock); + + wake_lock_init(&chg_data->update_wake_lock, WAKE_LOCK_SUSPEND, + "charger-update"); + + chg_data->charger_pdata = pdata->charger_data; + if (!pdata->charger_data->init_data) + max77693_charger_reg_init(chg_data); + else + max77693_charger_initialize(chg_data); + + chg_data->irq_bypass = max77693->irq_base + MAX77693_CHG_IRQ_BYP_I; + chg_data->irq_therm = max77693->irq_base + MAX77693_CHG_IRQ_THM_I; + chg_data->irq_battery = max77693->irq_base + MAX77693_CHG_IRQ_BAT_I; + chg_data->irq_charge = max77693->irq_base + MAX77693_CHG_IRQ_CHG_I; + chg_data->irq_chargin = max77693->irq_base + MAX77693_CHG_IRQ_CHGIN_I; + + chg_data->charger.name = "max77693-charger", + chg_data->charger.type = POWER_SUPPLY_TYPE_BATTERY, + chg_data->charger.properties = max77693_charger_props, + chg_data->charger.num_properties = ARRAY_SIZE(max77693_charger_props), + chg_data->charger.get_property = max77693_charger_get_property, + chg_data->charger.set_property = max77693_charger_set_property, + + ret = power_supply_register(&pdev->dev, &chg_data->charger); + if (ret) { + pr_err("%s: failed: power supply register\n", __func__); + goto err_kfree; + } + + INIT_DELAYED_WORK_DEFERRABLE(&chg_data->update_work, + max77693_update_work); + + ret = request_threaded_irq(chg_data->irq_bypass, NULL, + max77693_bypass_irq, 0, "bypass-irq", chg_data); + if (ret < 0) + pr_err("%s: fail to request bypass IRQ: %d: %d\n", + __func__, chg_data->irq_bypass, ret); + + ret = request_threaded_irq(chg_data->irq_battery, NULL, + max77693_charger_irq, 0, "battery-irq", chg_data); + if (ret < 0) + pr_err("%s: fail to request battery IRQ: %d: %d\n", + __func__, chg_data->irq_battery, ret); + + ret = request_threaded_irq(chg_data->irq_charge, NULL, + max77693_charger_irq, 0, "charge-irq", chg_data); + if (ret < 0) + pr_err("%s: fail to request charge IRQ: %d: %d\n", + __func__, chg_data->irq_charge, ret); + +#ifdef CONFIG_BATTERY_WPC_CHARGER + chg_data->wc_pwr_det = chg_data->charger_pdata->wc_pwr_det; +#if defined(CONFIG_MACH_M0) || defined(CONFIG_MACH_C1VZW) + if (system_rev >= 0xA) + chg_data->wc_pwr_det = true; +#endif +#if defined(CONFIG_MACH_C1) + if (system_rev >= 0x6) + chg_data->wc_pwr_det = true; +#endif + + if (chg_data->wc_pwr_det) + pr_info("%s: support wc power detection\n", __func__); + else + pr_info("%s: not support wc power detection\n", __func__); + + /* wpc 5V interrupt */ + if (!chg_data->charger_pdata->wpc_irq_gpio) { + pr_err("%s: no irq gpio, do not support wpc\n", __func__); + goto wpc_init_finish; + } + + chg_data->wc_w_gpio = chg_data->charger_pdata->wpc_irq_gpio; + chg_data->wc_w_irq = gpio_to_irq(chg_data->wc_w_gpio); + ret = gpio_request(chg_data->wc_w_gpio, "wpc_charger-irq"); + if (ret < 0) { + pr_err("%s: failed requesting gpio %d\n", __func__, + chg_data->wc_w_gpio); + goto wpc_init_finish; + } + gpio_direction_input(chg_data->wc_w_gpio); + gpio_free(chg_data->wc_w_gpio); + + ret = request_threaded_irq(chg_data->wc_w_irq, NULL, + wpc_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "wpc-int", chg_data); + if (ret) + pr_err("can NOT request irq 'WPC_INT' %d", + chg_data->wc_w_irq); + + chg_data->wc_w_state = !gpio_get_value(chg_data->wc_w_gpio); + + /* vbus 5V interrupt */ + if (chg_data->wc_pwr_det == false) { + pr_info("%s: wpc(%d)\n", __func__, chg_data->wc_w_state); + goto wpc_init_finish; + } + + if (!chg_data->charger_pdata->vbus_irq_gpio) { + pr_err("%s: no irq gpio, not support wc power detection\n", + __func__); + goto wpc_init_finish; + } + + chg_data->wc_v_gpio = chg_data->charger_pdata->vbus_irq_gpio; + chg_data->wc_v_irq = gpio_to_irq(chg_data->wc_v_gpio); + ret = gpio_request(chg_data->wc_v_gpio, "vbus_charger-irq"); + if (ret < 0) { + pr_err("%s: failed requesting gpio %d\n", __func__, + chg_data->wc_v_gpio); + goto wpc_init_finish; + } + gpio_direction_input(chg_data->wc_v_gpio); + gpio_free(chg_data->wc_v_gpio); + + ret = request_threaded_irq(chg_data->wc_v_irq, NULL, + wpc_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "vbus-int", chg_data); + if (ret) + pr_err("can NOT request irq 'V_BUS_INT' %d", + chg_data->wc_v_irq); + + if (chg_data->wc_w_state == 1) { + pr_info("%s: wpc active, set V_BUS_INT as PN\n", __func__); + s3c_gpio_setpull(chg_data->wc_v_gpio, S3C_GPIO_PULL_NONE); + } else { + disable_irq(chg_data->wc_v_irq); + pr_info("%s: wpc deactive, set V_BUS_INT as PD\n", __func__); + s3c_gpio_setpull(chg_data->wc_v_gpio, S3C_GPIO_PULL_DOWN); + } + + chg_data->wc_v_state = !gpio_get_value(chg_data->wc_v_gpio); + pr_info("%s: wpc(%d), vbus(%d)\n", __func__, + chg_data->wc_w_state, chg_data->wc_v_state); + +wpc_init_finish: +#endif + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS + chg_data->charger_debugfs_dir = + debugfs_create_dir("charger_debug", NULL); + if (chg_data->charger_debugfs_dir) { + if (!debugfs_create_file("max77693_regs", 0644, + chg_data->charger_debugfs_dir, + chg_data, &max77693_debugfs_fops)) + pr_err("%s : debugfs_create_file, error\n", __func__); + } else + pr_err("%s : debugfs_create_dir, error\n", __func__); +#endif +#endif + + pr_info("%s: charger init complete\n", __func__); + + return 0; + +err_kfree: + wake_lock_destroy(&chg_data->update_wake_lock); + + mutex_destroy(&chg_data->ops_lock); + mutex_destroy(&chg_data->irq_lock); + kfree(chg_data); + return ret; +} + +static int __devexit max77693_charger_remove(struct platform_device *pdev) +{ + struct max77693_charger_data *chg_data = platform_get_drvdata(pdev); + + wake_lock_destroy(&chg_data->update_wake_lock); + + mutex_destroy(&chg_data->ops_lock); + mutex_destroy(&chg_data->irq_lock); + + power_supply_unregister(&chg_data->charger); + + kfree(chg_data); + + return 0; +} + +/* + * WORKAROUND: + * Several interrupts occur while charging through TA. + * Suspended state cannot be maintained by the interrupts. + */ +#ifdef CONFIG_SLP +static u8 saved_int_mask; +static int max77693_charger_suspend(struct device *dev) +{ + struct max77693_dev *max77693 = dev_get_drvdata(dev->parent); + u8 int_mask; + + /* Save the masking value */ + max77693_read_reg(max77693->i2c, + MAX77693_CHG_REG_CHG_INT_MASK, + &saved_int_mask); + + /* Mask all the interrupts related to charger */ + int_mask = 0xff; + max77693_write_reg(max77693->i2c, + MAX77693_CHG_REG_CHG_INT_MASK, + int_mask); + return 0; +} + +static int max77693_charger_resume(struct device *dev) +{ + struct max77693_dev *max77693 = dev_get_drvdata(dev->parent); + + /* Restore the saved masking value */ + max77693_write_reg(max77693->i2c, + MAX77693_CHG_REG_CHG_INT_MASK, + saved_int_mask); + return 0; +} + +static SIMPLE_DEV_PM_OPS(max77693_charger_pm_ops, max77693_charger_suspend, + max77693_charger_resume); +#endif + +static struct platform_driver max77693_charger_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max77693-charger", +#ifdef CONFIG_SLP + .pm = &max77693_charger_pm_ops, +#endif + }, + .probe = max77693_charger_probe, + .remove = __devexit_p(max77693_charger_remove), +}; + +static int __init max77693_charger_init(void) +{ + return platform_driver_register(&max77693_charger_driver); +} + +static void __exit max77693_charger_exit(void) +{ + platform_driver_unregister(&max77693_charger_driver); +} + +module_init(max77693_charger_init); +module_exit(max77693_charger_exit); + +MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>"); +MODULE_DESCRIPTION("max77693 Charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/samsung_battery.c b/drivers/battery/samsung_battery.c new file mode 100644 index 0000000..dfd5d4a --- /dev/null +++ b/drivers/battery/samsung_battery.c @@ -0,0 +1,1991 @@ +/* + * samsung_battery.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@samsung.com> + * + * based on sec_battery.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/reboot.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/android_alarm.h> +#include <linux/battery/samsung_battery.h> +#include <mach/regs-pmu.h> +#include "battery-factory.h" +#if defined(CONFIG_S3C_ADC) +#include <plat/adc.h> +#endif +#if defined(CONFIG_STMPE811_ADC) +#include <linux/stmpe811-adc.h> +#endif + +static char *supply_list[] = { + "battery", +}; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +static void battery_notify_full_state(struct battery_info *info); +static bool battery_terminal_check_support(struct battery_info *info); +static void battery_error_control(struct battery_info *info); +#endif + +/* Get LP charging mode state */ +unsigned int lpcharge; +static int battery_get_lpm_state(char *str) +{ + get_option(&str, &lpcharge); + pr_info("%s: Low power charging mode: %d\n", __func__, lpcharge); + + return lpcharge; +} +__setup("lpcharge=", battery_get_lpm_state); +#if defined(CONFIG_RTC_ALARM_BOOT) +EXPORT_SYMBOL(lpcharge); +#endif + +/* Cable type from charger or adc */ +static int battery_get_cable(struct battery_info *info) +{ + union power_supply_propval value; + int cable_type = 0; + + pr_debug("%s\n", __func__); + + mutex_lock(&info->ops_lock); + + switch (info->pdata->cb_det_src) { + case CABLE_DET_CHARGER: + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_ONLINE, &value); + cable_type = value.intval; + break; + default: + pr_err("%s: not support src(%d)\n", __func__, + info->pdata->cb_det_src); + cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + } + + mutex_unlock(&info->ops_lock); + + return cable_type; +} + +/* Temperature from fuelgauge or adc */ +static int battery_get_temper(struct battery_info *info) +{ + union power_supply_propval value; + int cnt, adc, adc_max, adc_min, adc_total; + int temper = 300; + int retry_cnt; + pr_debug("%s\n", __func__); + + mutex_lock(&info->ops_lock); + + switch (info->pdata->temper_src) { + case TEMPER_FUELGAUGE: + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_TEMP, &value); + temper = value.intval; + break; + case TEMPER_AP_ADC: +#if defined(CONFIG_MACH_S2PLUS) + if (system_rev < 2) { + pr_info("%s: adc fixed as 30.0\n", __func__); + temper = 300; + mutex_unlock(&info->ops_lock); + return temper; + } +#endif +#if defined(CONFIG_S3C_ADC) + adc = adc_max = adc_min = adc_total = 0; + for (cnt = 0; cnt < CNT_ADC_SAMPLE; cnt++) { + retry_cnt = 0; + do { + adc = s3c_adc_read(info->adc_client, + info->pdata->temper_ch); + if (adc < 0) { + pr_info("%s: adc read(%d), retry(%d)", + __func__, adc, retry_cnt++); + msleep(ADC_ERR_DELAY); + } + } while (((adc < 0) && (retry_cnt <= ADC_ERR_CNT))); + + if (cnt != 0) { + adc_max = MAX(adc, adc_max); + adc_min = MIN(adc, adc_min); + } else { + adc_max = adc_min = adc; + } + + adc_total += adc; + pr_debug("%s: adc(%d), total(%d), max(%d), min(%d), " + "avg(%d), cnt(%d)\n", __func__, + adc, adc_total, adc_max, adc_min, + adc_total / (cnt + 1), cnt + 1); + } + + info->battery_temper_adc = + (adc_total - adc_max - adc_min) / (CNT_ADC_SAMPLE - 2); + + if (info->battery_temper_adc < 0) { + pr_info("%s: adc read error(%d), temper set as 30.0", + __func__, info->battery_temper_adc); + temper = 300; + } else { + temper = info->pdata->covert_adc( + info->battery_temper_adc, + info->pdata->temper_ch); + } +#endif + break; + case TEMPER_EXT_ADC: +#if defined(CONFIG_STMPE811_ADC) + temper = stmpe811_get_adc_value(info->pdata->temper_ch); +#endif + break; + case TEMPER_UNKNOWN: + default: + pr_info("%s: invalid temper src(%d)\n", __func__, + info->pdata->temper_src); + temper = 300; + break; + } + + pr_debug("%s: temper(%d), source(%d)\n", __func__, + temper, info->pdata->temper_src); + + mutex_unlock(&info->ops_lock); + return temper; +} + +/* Get info from power supply at realtime */ +int battery_get_info(struct battery_info *info, + enum power_supply_property property) +{ + union power_supply_propval value; + value.intval = 0; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + /* do nothing */ +#else + if (info->battery_error_test) { + pr_info("%s: in test mode(%d), do not update\n", __func__, + info->battery_error_test); + return -EPERM; + } +#endif + + switch (property) { + /* Update from charger */ + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_NOW: +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + case POWER_SUPPLY_PROP_CHARGE_FULL: +#endif + info->psy_charger->get_property(info->psy_charger, + property, &value); + break; + case POWER_SUPPLY_PROP_ONLINE: + value.intval = battery_get_cable(info); + break; + /* Update from fuelgauge */ + case POWER_SUPPLY_PROP_CAPACITY: /* Only Adjusted SOC */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* Only VCELL */ + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + property, &value); + break; + /* Update from fuelgauge or adc */ + case POWER_SUPPLY_PROP_TEMP: + value.intval = battery_get_temper(info); + break; + default: + break; + } + + return value.intval; +} + +/* Update all values for battery */ +void battery_update_info(struct battery_info *info) +{ + union power_supply_propval value; + int temper; + + /* Update from Charger */ + info->cable_type = battery_get_cable(info); + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_STATUS, &value); + info->charge_real_state = info->charge_virt_state = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_CHARGE_TYPE, &value); + info->charge_type = value.intval; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC)\ + || defined(CONFIG_MACH_M0_CMCC) + /* temperature error is higher priority */ + if (!info->temper_state) { + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_HEALTH, &value); + info->battery_health = value.intval; + } +#else + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_HEALTH, &value); + info->battery_health = value.intval; +#endif + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_PRESENT, &value); + info->battery_present = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_CURRENT_NOW, &value); + info->charge_current = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_CURRENT_MAX, &value); + info->input_current = value.intval; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (info->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + info->battery_health = POWER_SUPPLY_HEALTH_GOOD; + info->battery_present = 1; + } +#endif + + /* Fuelgauge power off state */ + if ((info->cable_type != POWER_SUPPLY_TYPE_BATTERY) && + (info->battery_present == 0)) { + pr_info("%s: abnormal fuelgauge power state\n", __func__); + goto update_finish; + } + + /* Update from Fuelgauge */ + value.intval = SOC_TYPE_ADJUSTED; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_CAPACITY, &value); + info->battery_soc = value.intval; + + value.intval = SOC_TYPE_RAW; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_CAPACITY, &value); + info->battery_r_s_delta = value.intval - info->battery_raw_soc; + info->battery_raw_soc = value.intval; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + value.intval = SOC_TYPE_FULL; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_CAPACITY, &value); + info->battery_full_soc = value.intval; +#endif + + value.intval = VOLTAGE_TYPE_VCELL; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + &value); + info->battery_vcell = value.intval; + + value.intval = VOLTAGE_TYPE_VFOCV; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + &value); + info->battery_vfocv = value.intval; + info->battery_v_diff = info->battery_vcell - info->battery_vfocv; + + temper = battery_get_temper(info); + info->battery_t_delta = temper - info->battery_temper; + info->battery_temper = temper; + +update_finish: + switch (info->battery_error_test) { + case 0: + pr_debug("%s: error test: not test modde\n", __func__); + break; + case 1: + pr_info("%s: error test: full charged\n", __func__); + info->charge_real_state = POWER_SUPPLY_STATUS_FULL; + info->battery_vcell = info->pdata->voltage_max; + break; + case 2: + pr_info("%s: error test: freezed\n", __func__); + info->battery_temper = info->pdata->freeze_stop_temp - 10; + break; + case 3: + pr_info("%s: error test: overheated\n", __func__); + info->battery_temper = info->pdata->overheat_stop_temp + 10; + break; + case 4: + pr_info("%s: error test: ovp\n", __func__); + break; + case 5: + pr_info("%s: error test: vf error\n", __func__); + info->battery_present = 0; + break; + default: + pr_info("%s: error test: unknown state\n", __func__); + break; + } + + pr_debug("%s: state(%d), type(%d), " + "health(%d), present(%d), " + "cable(%d), curr(%d), " + "soc(%d), raw(%d), " + "vol(%d), ocv(%d), tmp(%d)\n", __func__, + info->charge_real_state, info->charge_type, + info->battery_health, info->battery_present, + info->cable_type, info->charge_current, + info->battery_soc, info->battery_raw_soc, + info->battery_vcell, info->battery_vfocv, + info->battery_temper); +} + +/* Control charger and fuelgauge */ +void battery_control_info(struct battery_info *info, + enum power_supply_property property, int intval) +{ + union power_supply_propval value; + + value.intval = intval; + + switch (property) { + /* Control to charger */ + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_NOW: +#if defined(CONFIG_CHARGER_MAX8922_U1) +#if defined(CONFIG_MACH_S2PLUS) + if (system_rev >= 2) { + info->psy_sub_charger->set_property( + info->psy_sub_charger, + property, &value); + } else { + info->psy_charger->set_property(info->psy_charger, + property, &value); + } +#else + info->psy_sub_charger->set_property(info->psy_sub_charger, + property, &value); +#endif +#else + info->psy_charger->set_property(info->psy_charger, + property, &value); +#endif + break; + + /* Control to fuelgauge */ + case POWER_SUPPLY_PROP_CAPACITY: + info->psy_fuelgauge->set_property(info->psy_fuelgauge, + property, &value); + break; + default: + break; + } +} + +static void samsung_battery_alarm_start(struct alarm *alarm) +{ + struct battery_info *info = container_of(alarm, struct battery_info, + alarm); + pr_debug("%s\n", __func__); + + wake_lock(&info->monitor_wake_lock); + schedule_work(&info->monitor_work); +} + +static void battery_monitor_interval(struct battery_info *info) +{ + ktime_t interval, next, slack; + unsigned long flags; + pr_debug("%s\n", __func__); + + local_irq_save(flags); + + info->last_poll = alarm_get_elapsed_realtime(); + + switch (info->monitor_mode) { + case MONITOR_CHNG: + info->monitor_interval = info->pdata->chng_interval; + break; + case MONITOR_CHNG_SUSP: + info->monitor_interval = info->pdata->chng_susp_interval; + break; + case MONITOR_NORM: + info->monitor_interval = info->pdata->norm_interval; + break; + case MONITOR_NORM_SUSP: + info->monitor_interval = info->pdata->norm_susp_interval; + break; + case MONITOR_EMER_LV1: + info->monitor_interval = info->pdata->emer_lv1_interval; + break; + case MONITOR_EMER_LV2: + info->monitor_interval = info->pdata->emer_lv2_interval; + break; + default: + info->monitor_interval = info->pdata->norm_interval; + break; + } + + /* apply monitor interval weight */ + if (info->monitor_weight != 100) { + pr_info("%s: apply weight(%d), %d -> %d\n", __func__, + info->monitor_weight, info->monitor_interval, + (info->monitor_interval * info->monitor_weight / 100)); + info->monitor_interval *= info->monitor_weight; + info->monitor_interval /= 100; + } + + pr_debug("%s: monitor mode(%d), interval(%d)\n", __func__, + info->monitor_mode, info->monitor_interval); + + interval = ktime_set(info->monitor_interval, 0); + next = ktime_add(info->last_poll, interval); + slack = ktime_set(20, 0); + + alarm_start_range(&info->alarm, next, ktime_add(next, slack)); + + local_irq_restore(flags); +} + +static bool battery_recharge_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + if (info->charge_real_state == POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("%s: r_state chargng, cs(%d)\n", __func__, + info->charge_real_state); + return false; + } + + if (info->battery_vcell < info->pdata->recharge_voltage) { + pr_info("%s: recharge state, vcell(%d ? %d)\n", __func__, + info->battery_vcell, info->pdata->recharge_voltage); + return true; + } else + pr_debug("%s: not recharge state, vcell(%d ? %d)\n", __func__, + info->battery_vcell, info->pdata->recharge_voltage); + + return false; +} + +static bool battery_abstimer_cond(struct battery_info *info) +{ + unsigned int abstimer_duration; + ktime_t ktime; + struct timespec current_time; + pr_debug("%s\n", __func__); + + if ((info->cable_type == POWER_SUPPLY_TYPE_USB) || + (info->full_charged_state == true) || + (info->charge_start_time == 0)) { + pr_debug("%s: not abstimer state, cb(%d), f(%d), t(%d)\n", + __func__, info->cable_type, + info->full_charged_state, + info->charge_start_time); + info->abstimer_state = false; + return false; + } + + ktime = alarm_get_elapsed_realtime(); + current_time = ktime_to_timespec(ktime); + + if (info->recharge_phase) + abstimer_duration = info->pdata->abstimer_recharge_duration; + else + abstimer_duration = info->pdata->abstimer_charge_duration; + + if ((current_time.tv_sec - info->charge_start_time) > + abstimer_duration) { + pr_info("%s: abstimer state, t(%d - %d ?? %d)\n", __func__, + (int)current_time.tv_sec, info->charge_start_time, + abstimer_duration); + info->abstimer_state = true; + } else { + pr_debug("%s: not abstimer state, t(%d - %d ?? %d)\n", __func__, + (int)current_time.tv_sec, info->charge_start_time, + abstimer_duration); + info->abstimer_state = false; + } + + return info->abstimer_state; +} + +static bool battery_fullcharged_cond(struct battery_info *info) +{ + int f_cond_soc; + int f_cond_vcell; + pr_debug("%s\n", __func__); + + /* max voltage - 50mV */ + f_cond_vcell = info->pdata->voltage_max - 50000; + /* max soc - 5% */ + f_cond_soc = 95; + + pr_debug("%s: cs(%d ? %d), v(%d ? %d), s(%d ? %d)\n", __func__, + info->charge_real_state, POWER_SUPPLY_STATUS_FULL, + info->battery_vcell, f_cond_vcell, + info->battery_soc, f_cond_soc); + + if (info->charge_real_state == POWER_SUPPLY_STATUS_FULL) { + if ((info->battery_vcell > f_cond_vcell) && + (info->battery_soc > f_cond_soc)) { + pr_info("%s: real full charged, v(%d), s(%d)\n", + __func__, info->battery_vcell, + info->battery_soc); + info->full_charged_state = true; + return true; + } else { + pr_info("%s: charger full charged, v(%d), s(%d)\n", + __func__, info->battery_vcell, + info->battery_soc); + + /* restart charger */ + battery_control_info(info, + POWER_SUPPLY_PROP_STATUS, + DISABLE); + msleep(100); + battery_control_info(info, + POWER_SUPPLY_PROP_STATUS, + ENABLE); + + info->charge_real_state = info->charge_virt_state = + battery_get_info(info, + POWER_SUPPLY_PROP_STATUS); + return false; + } + } else if (info->full_charged_state == true) { + pr_debug("%s: already full charged, v(%d), s(%d)\n", __func__, + info->battery_vcell, info->battery_soc); + } else { + pr_debug("%s: not full charged, v(%d), s(%d)\n", __func__, + info->battery_vcell, info->battery_soc); + info->full_charged_state = false; + } + + return false; +} + +static bool battery_vf_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + +#if defined(CONFIG_MACH_P11) || defined(CONFIG_MACH_P10) + /* FIXME: fix P11 build error temporarily */ +#else + /* jig detect by MUIC */ + if (is_jig_attached == JIG_ON) { + pr_info("%s: JIG ON, do not check\n", __func__); + info->vf_state = false; + return false; + } +#endif + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (info->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + info->vf_state = false; + return false; + } +#endif + + /* real time state */ + info->battery_present = + battery_get_info(info, POWER_SUPPLY_PROP_PRESENT); + + if (info->battery_present == 0) { + pr_info("%s: battery(%d) is not detected\n", __func__, + info->battery_present); + info->vf_state = true; + } else { + pr_debug("%s: battery(%d) is detected\n", __func__, + info->battery_present); + info->vf_state = false; + } + + return info->vf_state; +} + +static bool battery_health_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (info->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + info->health_state = false; + info->is_unspec_phase = false; + return false; + } +#endif + + /* temperature error is higher priority */ + if (info->temper_state == true) { + pr_info("%s: temper stop state, do not check\n", __func__); + return false; + } + + /* real time state */ + info->battery_health = + battery_get_info(info, POWER_SUPPLY_PROP_HEALTH); + + if (info->battery_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("%s: battery unspec(%d)\n", __func__, + info->battery_health); + info->health_state = true; + } else if (info->battery_health == POWER_SUPPLY_HEALTH_DEAD) { + pr_info("%s: battery dead(%d)\n", __func__, + info->battery_health); + info->health_state = true; + } else if (info->battery_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) { + pr_info("%s: battery overvoltage(%d)\n", __func__, + info->battery_health); + info->health_state = true; + } else if (info->battery_health == POWER_SUPPLY_HEALTH_UNDERVOLTAGE) { + pr_info("%s: battery undervoltage(%d)\n", __func__, + info->battery_health); + info->health_state = true; + } else { + pr_debug("%s: battery good(%d)\n", __func__, + info->battery_health); + info->health_state = false; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if (battery_terminal_check_support(info)) + pr_err("%s: health support, error state!\n", __func__); +#endif + } + + return info->health_state; +} + +static bool battery_temper_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + if (info->temper_state == false) { + if (info->charge_real_state != POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("%s: r_state !charging, cs(%d)\n", + __func__, info->charge_real_state); + return false; + } + + pr_debug("%s: check charging stop temper " + "cond: %d ?? %d ~ %d\n", __func__, + info->battery_temper, + info->pdata->freeze_stop_temp, + info->pdata->overheat_stop_temp); + + if (info->battery_temper >= + info->pdata->overheat_stop_temp) { + pr_info("%s: stop by overheated, t(%d ? %d)\n", + __func__, info->battery_temper, + info->pdata->overheat_stop_temp); + info->overheated_state = true; + } else if (info->battery_temper <= + info->pdata->freeze_stop_temp) { + pr_info("%s: stop by overheated, t(%d ? %d)\n", + __func__, info->battery_temper, + info->pdata->freeze_stop_temp); + info->freezed_state = true; + } else + pr_debug("%s: normal charging, t(%d)\n", __func__, + info->battery_temper); + } else { + pr_debug("%s: check charging recovery temper " + "cond: %d ?? %d ~ %d\n", __func__, + info->battery_temper, + info->pdata->freeze_recovery_temp, + info->pdata->overheat_recovery_temp); + + if ((info->overheated_state == true) && + (info->battery_temper <= + info->pdata->overheat_recovery_temp)) { + pr_info("%s: recovery from overheated, t(%d ? %d)\n", + __func__, info->battery_temper, + info->pdata->overheat_recovery_temp); + info->overheated_state = false; + } else if ((info->freezed_state == true) && + (info->battery_temper >= + info->pdata->freeze_recovery_temp)) { + pr_info("%s: recovery from freezed, t(%d ? %d)\n", + __func__, info->battery_temper, + info->pdata->freeze_recovery_temp); + info->freezed_state = false; + } else + pr_info("%s: charge stopped, t(%d)\n", __func__, + info->battery_temper); + } + + if (info->overheated_state == true) { + info->battery_health = POWER_SUPPLY_HEALTH_OVERHEAT; + info->freezed_state = false; + info->temper_state = true; + } else if (info->freezed_state == true) { + info->battery_health = POWER_SUPPLY_HEALTH_COLD; + info->overheated_state = false; + info->temper_state = true; + } else { +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + info->battery_health = POWER_SUPPLY_HEALTH_GOOD; +#endif + info->overheated_state = false; + info->freezed_state = false; + info->temper_state = false; + } + + return info->temper_state; +} + +static void battery_charge_control(struct battery_info *info, + unsigned int chg_curr, unsigned int in_curr) +{ + int charge_state; + ktime_t ktime; + struct timespec current_time; + pr_debug("%s, chg(%d), in(%d)\n", __func__, chg_curr, in_curr); + + mutex_lock(&info->ops_lock); + + ktime = alarm_get_elapsed_realtime(); + current_time = ktime_to_timespec(ktime); + + if ((chg_curr != 0) && (info->siop_state == true)) { + pr_info("%s: siop state, charge current is %dmA\n", __func__, + info->siop_charge_current); + chg_curr = info->siop_charge_current; + } + + if (in_curr == KEEP_CURR) + goto charge_current_con; + + /* input current limit */ + in_curr = min(in_curr, info->pdata->in_curr_limit); + + /* check charge input before and after */ + if (info->input_current == ((in_curr / 20) * 20)) { + /* + * (current / 20) is converted value + * for register setting. + * (register current * 20) is actual value + * for input current + */ + pr_debug("%s: same input current: %dmA\n", __func__, in_curr); + } else { + battery_control_info(info, + POWER_SUPPLY_PROP_CURRENT_MAX, + in_curr); + info->input_current = + battery_get_info(info, POWER_SUPPLY_PROP_CURRENT_MAX); + + pr_debug("%s: update input current: %dmA\n", __func__, in_curr); + } + +charge_current_con: + if (chg_curr == KEEP_CURR) + goto charge_state_con; + + /* check charge current before and after */ + if (info->charge_current == ((chg_curr * 3 / 100) * 333 / 10)) { + /* + * (current * 3 / 100) is converted value + * for register setting. + * (register current * 333 / 10) is actual value + * for charge current + */ + pr_debug("%s: same charge current: %dmA\n", + __func__, chg_curr); + } else { + battery_control_info(info, + POWER_SUPPLY_PROP_CURRENT_NOW, + chg_curr); + info->charge_current = + battery_get_info(info, POWER_SUPPLY_PROP_CURRENT_NOW); + + pr_debug("%s: update charge current: %dmA\n", + __func__, chg_curr); + } + +charge_state_con: + /* control charger control only, buck is on by default */ + if ((chg_curr != 0) && (info->charge_start_time == 0)) { + battery_control_info(info, POWER_SUPPLY_PROP_STATUS, ENABLE); + + info->charge_start_time = current_time.tv_sec; + pr_err("%s: charge enabled, current as %d/%dmA @%d\n", + __func__, info->charge_current, info->input_current, + info->charge_start_time); + + charge_state = battery_get_info(info, POWER_SUPPLY_PROP_STATUS); + + if (charge_state != POWER_SUPPLY_STATUS_CHARGING) { + pr_info("%s: force set v_state as charging\n", + __func__); + info->charge_real_state = charge_state; + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + info->ambiguous_state = true; + } else { + info->charge_real_state = info->charge_virt_state = + charge_state; + info->ambiguous_state = false; + } + } else if ((chg_curr == 0) && (info->charge_start_time != 0)) { + battery_control_info(info, POWER_SUPPLY_PROP_STATUS, DISABLE); + + pr_err("%s: charge disabled, current as %d/%dmA @%d\n", + __func__, info->charge_current, info->input_current, + (int)current_time.tv_sec); + + info->charge_start_time = 0; + + charge_state = battery_get_info(info, POWER_SUPPLY_PROP_STATUS); + + if (charge_state != POWER_SUPPLY_STATUS_DISCHARGING) { + pr_info("%s: force set v_state as discharging\n", + __func__); + info->charge_real_state = charge_state; + info->charge_virt_state = + POWER_SUPPLY_STATUS_DISCHARGING; + info->ambiguous_state = true; + } else { + info->charge_real_state = info->charge_virt_state = + charge_state; + info->ambiguous_state = false; + } + } else { + pr_debug("%s: same charge state(%s), current as %d/%dmA @%d\n", + __func__, ((chg_curr != 0) ? "enabled" : "disabled"), + info->charge_current, info->input_current, + info->charge_start_time); + + /* release ambiguous state */ + if ((info->ambiguous_state == true) && + (info->charge_real_state == info->charge_virt_state)) { + pr_debug("%s: release ambiguous state, s(%d)\n", + __func__, info->charge_real_state); + info->ambiguous_state = false; + } + } + + mutex_unlock(&info->ops_lock); +} + +/* charge state for UI(icon) */ +static void battery_indicator_icon(struct battery_info *info) +{ + if (info->cable_type != POWER_SUPPLY_TYPE_BATTERY) { + if (info->full_charged_state == true) { + info->charge_virt_state = + POWER_SUPPLY_STATUS_FULL; + info->battery_soc = 100; + } else if (info->abstimer_state == true) { + info->charge_virt_state = + POWER_SUPPLY_STATUS_CHARGING; + } else if (info->recharge_phase == true) { + info->charge_virt_state = + POWER_SUPPLY_STATUS_CHARGING; + } + + if (info->temper_state == true) { + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + if (info->vf_state == true) { + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + info->battery_health = + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } + + if (info->health_state == true) { + /* ovp is not 'NOT_CHARGING' */ + if (info->battery_health == + POWER_SUPPLY_HEALTH_OVERVOLTAGE) + info->charge_virt_state = + POWER_SUPPLY_STATUS_DISCHARGING; + else + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } +} + +/* charge state for LED */ +static void battery_indicator_led(struct battery_info *info) +{ + + if (info->charge_virt_state == + POWER_SUPPLY_STATUS_CHARGING) { + if (info->led_state != BATT_LED_CHARGING) { + /* TODO: for kernel LED control: CHARGING */ + info->led_state = BATT_LED_CHARGING; + } + } else if (info->charge_virt_state == + POWER_SUPPLY_STATUS_NOT_CHARGING) { + if (info->led_state != BATT_LED_NOT_CHARGING) { + /* TODO: for kernel LED control: NOT CHARGING */ + info->led_state = BATT_LED_NOT_CHARGING; + } + } else if (info->charge_virt_state == + POWER_SUPPLY_STATUS_FULL) { + if (info->led_state != BATT_LED_FULL) { + /* TODO: for kernel LED control: FULL */ + info->led_state = BATT_LED_FULL; + } + } else { + if (info->led_state != BATT_LED_DISCHARGING) { + /* TODO: for kernel LED control: DISCHARGING */ + info->led_state = BATT_LED_DISCHARGING; + } + } +} + +/* dynamic battery polling interval */ +static void battery_interval_calulation(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + /* init monitor interval weight */ + info->monitor_weight = 100; + + /* ambiguous state */ + if (info->ambiguous_state == true) { + pr_info("%s: ambiguous state\n", __func__); + info->monitor_mode = MONITOR_EMER_LV2; + wake_lock(&info->emer_wake_lock); + return; + } else { + pr_debug("%s: not ambiguous state\n", __func__); + wake_unlock(&info->emer_wake_lock); + } + + /* prevent critical low raw soc factor */ + if (info->battery_raw_soc < 100) { + pr_info("%s: soc(%d) too low state\n", __func__, + info->battery_raw_soc); + info->monitor_mode = MONITOR_EMER_LV2; + wake_lock(&info->emer_wake_lock); + return; + } else { + pr_debug("%s: soc(%d) not too low state\n", __func__, + info->battery_raw_soc); + wake_unlock(&info->emer_wake_lock); + } + + /* prevent critical low voltage factor */ + if ((info->battery_vcell < (info->pdata->voltage_min - 100000)) || + (info->battery_vfocv < info->pdata->voltage_min)) { + pr_info("%s: voltage(%d) too low state\n", __func__, + info->battery_vcell); + info->monitor_mode = MONITOR_EMER_LV2; + wake_lock(&info->emer_wake_lock); + return; + } else { + pr_debug("%s: voltage(%d) not too low state\n", __func__, + info->battery_vcell); + wake_unlock(&info->emer_wake_lock); + } + + /* charge state factor */ + if (info->charge_virt_state == + POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("%s: v_state charging\n", __func__); + info->monitor_mode = MONITOR_CHNG; + wake_unlock(&info->emer_wake_lock); +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + if ((info->prev_cable_type == POWER_SUPPLY_TYPE_BATTERY && + info->cable_type != POWER_SUPPLY_TYPE_BATTERY) && + (info->battery_temper >= + info->pdata->overheat_stop_temp || + info->battery_temper <= + info->pdata->freeze_stop_temp)) { + pr_info("%s : re-check temper condition\n", __func__); + info->monitor_mode = MONITOR_EMER_LV2; + } +#endif + } else if (info->charge_virt_state == + POWER_SUPPLY_STATUS_NOT_CHARGING) { + pr_debug("%s: emergency(not charging) state\n", __func__); + info->monitor_mode = MONITOR_EMER_LV2; + wake_lock(&info->emer_wake_lock); + return; + } else { + pr_debug("%s: normal state\n", __func__); + info->monitor_mode = MONITOR_NORM; + wake_unlock(&info->emer_wake_lock); + } + + /* + * in LPM state, set default weight as 200% + */ + if (info->lpm_state == true) + info->monitor_weight *= 2; + + /* 2 times after boot(about 1min), apply charging interval(30sec) */ + if (info->monitor_count < 2) { + pr_info("%s: now in booting, set 30s\n", __func__); + info->monitor_mode = MONITOR_EMER_LV1; + return; + } + + /* + * prevent low voltage phase + * default, vcell is lower than min_voltage + 50mV, -20% + */ + if (info->battery_vcell < (info->pdata->voltage_min + 50000)) { + info->monitor_mode = MONITOR_EMER_LV1; + info->monitor_weight -= 30; + pr_info("%s: low v(%d), weight(%d)\n", __func__, + info->battery_vcell, info->monitor_weight); + } + + /* + * prevent high current state(both charging and discharging + * default, (v diff = vcell - vfocv) + * charging, v_diff is higher than 250mV, (%dmV / 1000)% + * discharging, v_diff is higher than 100mV, (%dmV / 1000)% + * both, v_diff is lower than 10mV, +20% + */ + if ((info->battery_v_diff > 250000) || + (info->battery_v_diff < -100000)) { + info->monitor_weight += (info->battery_v_diff / 10000); + pr_info("%s: v diff(%d), weight(%d)\n", __func__, + ABS(info->battery_v_diff), info->monitor_weight); + } else if ((ABS(info->battery_v_diff)) < 50000) { + info->monitor_weight += 20; + pr_info("%s: v diff(%d), weight(%d)\n", __func__, + ABS(info->battery_v_diff), info->monitor_weight); + } + + /* + * prevent raw soc changable phase + * default, raw soc X.YY%, YY% is in under 0.1% or upper 0.9%, -10% + */ + if ((((info->battery_raw_soc % 100) < 10) && + (info->battery_v_diff < 0)) || + (((info->battery_raw_soc % 100) > 90) && + (info->battery_v_diff > 0))) { + info->monitor_weight -= 10; + pr_info("%s: raw soc(%d), weight(%d)\n", __func__, + info->battery_raw_soc, info->monitor_weight); + } + + /* + * prevent high slope raw soc change + * default, raw soc delta is higher than 0.5%, -(raw soc delta / 5)% + */ + if (ABS(info->battery_r_s_delta) > 50) { + info->monitor_weight -= (ABS(info->battery_r_s_delta)) / 5; + pr_info("%s: raw soc delta(%d), weight(%d)\n", __func__, + ABS(info->battery_r_s_delta), info->monitor_weight); + } + + /* + * prevent high/low temper phase + * default, temper is in (overheat temp - 5'C) or (freeze temp + 5'C) + */ + if ((info->battery_temper > (info->pdata->overheat_stop_temp - 50)) || + (info->battery_temper < (info->pdata->freeze_stop_temp + 50))) { + info->monitor_weight -= 20; + pr_info("%s: temper(%d ? %d - %d), weight(%d)\n", __func__, + info->battery_temper, + (info->pdata->overheat_stop_temp - 50), + (info->pdata->freeze_stop_temp + 50), + info->monitor_weight); + } + + /* + * prevent high slope temper change + * default, temper delta is higher than 2.00'C + */ + if (ABS(info->battery_t_delta) > 20) { + info->monitor_weight -= 20; + pr_info("%s: temper delta(%d), weight(%d)\n", __func__, + ABS(info->battery_t_delta), info->monitor_weight); + } + + /* prevent too low or too high weight, 10 ~ 150% */ + info->monitor_weight = MIN(MAX(info->monitor_weight, 10), 150); + + if (info->monitor_weight != 100) + pr_info("%s: weight(%d)\n", __func__, info->monitor_weight); +} + +static void battery_monitor_work(struct work_struct *work) +{ + struct battery_info *info = container_of(work, struct battery_info, + monitor_work); + pr_debug("%s\n", __func__); + + mutex_lock(&info->mon_lock); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + /* first, check cable-type */ + info->cable_type = battery_get_cable(info); +#endif + + /* If battery is not connected, clear flag for charge scenario */ + if ((battery_vf_cond(info) == true) || + (battery_health_cond(info) == true)) { + pr_info("%s: battery error\n", __func__); + info->overheated_state = false; + info->freezed_state = false; + info->temper_state = false; + info->full_charged_state = false; + info->abstimer_state = false; + info->recharge_phase = false; + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + pr_info("%s: not support standever...\n", __func__); + battery_error_control(info); +#else + if (info->pdata->battery_standever == true) { + pr_info("%s: support standever\n", __func__); + schedule_work(&info->error_work); + } else { + pr_info("%s: not support standever\n", __func__); + battery_charge_control(info, OFF_CURR, OFF_CURR); + } +#endif + } + + /* Check battery state from charger and fuelgauge */ + battery_update_info(info); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + /* check unspec recovery */ + if (info->is_unspec_phase) { + if ((info->battery_health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) && + (battery_terminal_check_support(info) == false)) { + pr_info("%s: recover from unspec phase!\n", __func__); + info->is_unspec_recovery = true; + } + } + + /* If it's recovery phase from unspec state, go to charge_ok */ + if (info->is_unspec_recovery) { + pr_info("%s: recovered from unspec phase" + ": re-setting charge current!\n", __func__); + battery_charge_control(info, OFF_CURR, OFF_CURR); + info->is_unspec_recovery = false; + info->is_unspec_phase = false; + goto charge_ok; + } +#endif + + /* if battery is missed state, do not check charge scenario */ + if (info->battery_present == 0) + goto monitor_finish; + + /* If charger is not connected, do not check charge scenario */ + if (info->cable_type == POWER_SUPPLY_TYPE_BATTERY) + goto charge_ok; + + /* Below is charger is connected state */ + if (battery_temper_cond(info) == true) { + pr_info("%s: charge stopped by temperature\n", __func__); + battery_charge_control(info, OFF_CURR, OFF_CURR); + goto monitor_finish; + } + + if (battery_fullcharged_cond(info) == true) { + pr_info("%s: full charged state\n", __func__); + battery_charge_control(info, OFF_CURR, KEEP_CURR); + info->recharge_phase = true; + goto monitor_finish; + } + + if (battery_abstimer_cond(info) == true) { + pr_info("%s: abstimer state\n", __func__); + battery_charge_control(info, OFF_CURR, OFF_CURR); + info->recharge_phase = true; + goto monitor_finish; + } + + if (info->recharge_phase == true) { + if (battery_recharge_cond(info) == true) { + pr_info("%s: recharge condition\n", __func__); + goto charge_ok; + } else { + pr_debug("%s: not recharge\n", __func__); + goto monitor_finish; + } + } + +charge_ok: + pr_err("%s: Updated Cable State(%d)\n", __func__, info->cable_type); + switch (info->cable_type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (!info->pdata->suspend_chging) + wake_unlock(&info->charge_wake_lock); + battery_charge_control(info, OFF_CURR, OFF_CURR); + + /* clear charge scenario state */ + info->overheated_state = false; + info->freezed_state = false; + info->temper_state = false; + info->full_charged_state = false; + info->abstimer_state = false; + info->recharge_phase = false; + break; + case POWER_SUPPLY_TYPE_MAINS: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, info->pdata->chg_curr_ta, + info->pdata->chg_curr_ta); + break; + case POWER_SUPPLY_TYPE_USB: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, info->pdata->chg_curr_usb, + info->pdata->chg_curr_usb); + break; + case POWER_SUPPLY_TYPE_USB_CDP: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, info->pdata->chg_curr_cdp, + info->pdata->chg_curr_cdp); + break; + case POWER_SUPPLY_TYPE_DOCK: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, info->pdata->chg_curr_dock, + info->pdata->chg_curr_dock); + break; + case POWER_SUPPLY_TYPE_WIRELESS: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, info->pdata->chg_curr_wpc, + info->pdata->chg_curr_wpc); + break; + default: + break; + } + +monitor_finish: + /* icon indicator */ + battery_indicator_icon(info); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + battery_notify_full_state(info); +#endif + + /* dynamic battery polling interval */ + battery_interval_calulation(info); + + /* prevent suspend before starting the alarm */ + battery_monitor_interval(info); + + /* led indictor */ + if (info->pdata->led_indicator == true) + battery_indicator_led(info); + + pr_info("[%d] bat: s(%d, %d), v(%d, %d), b(%d), " + "t(%d.%d), h(%d), " + "cs(%d, %d), cb(%d), cr(%d, %d), " + "a(%d), f(%d), r(%d), t(%d)\n", + ++info->monitor_count, + info->battery_soc, + info->battery_r_s_delta, + info->battery_vcell / 1000, + info->battery_v_diff / 1000, + info->battery_present, + info->battery_temper / 10, info->battery_temper % 10, + info->battery_health, + info->charge_real_state, + info->charge_virt_state, + info->cable_type, + info->charge_current, + info->input_current, + info->abstimer_state, + info->full_charged_state, + info->recharge_phase, + info->charge_start_time); + + power_supply_changed(&info->psy_bat); + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + /* prevent suspend for ui-update */ + if (info->prev_cable_type != info->cable_type || + info->prev_battery_health != info->battery_health || + info->prev_charge_virt_state != info->charge_virt_state || + info->prev_battery_soc != info->battery_soc) { + /* TBD : timeout value */ + pr_info("%s : update wakelock (%d)\n", __func__, 3 * HZ); + wake_lock_timeout(&info->update_wake_lock, 3 * HZ); + } + + info->prev_cable_type = info->cable_type; + info->prev_battery_health = info->battery_health; + info->prev_charge_virt_state = info->charge_virt_state; + info->prev_battery_soc = info->battery_soc; +#endif + + /* if cable is detached in lpm, guarantee some secs for playlpm */ + if ((info->lpm_state == true) && + (info->cable_type == POWER_SUPPLY_TYPE_BATTERY)) { + pr_info("%s: lpm with battery, maybe power off\n", __func__); + wake_lock_timeout(&info->monitor_wake_lock, 10 * HZ); + } else + wake_lock_timeout(&info->monitor_wake_lock, HZ); + + mutex_unlock(&info->mon_lock); + + return; +} + +static void battery_error_work(struct work_struct *work) +{ + struct battery_info *info = container_of(work, struct battery_info, + error_work); + int err_cnt; + int old_vcell, new_vcell, vcell_diff; + pr_info("%s\n", __func__); + + mutex_lock(&info->err_lock); + + if ((info->vf_state == true) || (info->health_state == true)) { + pr_info("%s: battery error state\n", __func__); + old_vcell = info->battery_vcell; + new_vcell = 0; + for (err_cnt = 1; err_cnt <= VF_CHECK_COUNT; err_cnt++) { +#if defined(CONFIG_MACH_P11) || defined(CONFIG_MACH_P10) + /* FIXME: fix P11 build error temporarily */ +#else + if (is_jig_attached == JIG_ON) { + pr_info("%s: JIG detected, return\n", __func__); + mutex_unlock(&info->err_lock); + return; + } +#endif + info->battery_present = + battery_get_info(info, + POWER_SUPPLY_PROP_PRESENT); + if (info->battery_present == 0) { + pr_info("%s: battery still error(%d)\n", + __func__, err_cnt); + msleep(VF_CHECK_DELAY); + } else { + pr_info("%s: battery detect ok, " + "check soc\n", __func__); + new_vcell = battery_get_info(info, + POWER_SUPPLY_PROP_VOLTAGE_NOW); + vcell_diff = abs(old_vcell - new_vcell); + pr_info("%s: check vcell: %d -> %d, diff: %d\n", + __func__, info->battery_vcell, + new_vcell, vcell_diff); + if (vcell_diff > RESET_SOC_DIFF_TH) { + pr_info("%s: reset soc\n", __func__); + battery_control_info(info, + POWER_SUPPLY_PROP_CAPACITY, 1); + } else + pr_info("%s: keep soc\n", __func__); + break; + } + + if (err_cnt == VF_CHECK_COUNT) { + pr_info("%s: battery error, power off\n", + __func__); + battery_charge_control(info, OFF_CURR, + OFF_CURR); + } + } + } else + pr_info("%s: unexpected error\n", __func__); + + mutex_unlock(&info->err_lock); + return; +} + +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) +static void battery_notify_full_state(struct battery_info *info) +{ + union power_supply_propval value; + + if ((info->recharge_phase && info->full_charged_state) || + ((info->battery_raw_soc > info->battery_full_soc) && + (info->battery_soc == 100))) { + /* notify full state to fuel guage */ + value.intval = POWER_SUPPLY_STATUS_FULL; + info->psy_fuelgauge->set_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_STATUS, &value); + } +} + +static bool battery_terminal_check_support(struct battery_info *info) +{ + int full_mode; + int vcell; + bool ret = false; + pr_debug("%s\n", __func__); + + full_mode = battery_get_info(info, POWER_SUPPLY_PROP_CHARGE_FULL); + vcell = battery_get_info(info, POWER_SUPPLY_PROP_VOLTAGE_NOW); + pr_debug("%s: chg_status = %d, vcell = %d\n", + __func__, full_mode, vcell); + + if (full_mode && (vcell <= 3800000)) { + pr_info("%s: top-off or done mode, but low voltage(%d)\n", + __func__, vcell / 1000); + /* check again */ + vcell = battery_get_info(info, POWER_SUPPLY_PROP_VOLTAGE_NOW); + if (vcell <= 3800000) { + pr_info("%s: top-off or done mode, but low voltage(%d), " + "set health error!\n", + __func__, vcell / 1000); + info->health_state = true; + ret = true; + } + } + + return ret; +} + +static void battery_error_control(struct battery_info *info) +{ + pr_info("%s\n", __func__); + + if (info->vf_state == true) { + pr_info("%s: battery vf error state\n", __func__); + + /* check again */ + info->battery_present = battery_get_info(info, + POWER_SUPPLY_PROP_PRESENT); + + if (info->battery_present == 0) { + pr_info("%s: battery vf error, " + "disable charging and off the system path!\n", + __func__); + battery_charge_control(info, OFF_CURR, OFF_CURR); + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } else if (info->health_state == true) { + if ((info->battery_health == + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || + battery_terminal_check_support(info)) { + pr_info("%s: battery unspec, " + "disable charging and off the " + "system path!\n", __func__); + + /* invalid top-off state, + assume terminals(+/-) open */ + battery_charge_control(info, OFF_CURR, OFF_CURR); + pr_info("%s: set unspec phase!\n", __func__); + info->is_unspec_phase = true; + } else if (info->battery_health == + POWER_SUPPLY_HEALTH_OVERVOLTAGE) + pr_info("%s: vbus ovp state!", __func__); + } + + return; +} +#endif + +/* Support property from battery */ +static enum power_supply_property samsung_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +/* Support property from usb, ac */ +static enum power_supply_property samsung_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int samsung_battery_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_info, + psy_bat); + + /* re-update indicator icon */ + battery_indicator_icon(info); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = info->charge_virt_state; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = info->charge_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = info->battery_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = info->battery_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->cable_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = info->battery_vcell; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = info->input_current; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = info->charge_current; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = info->battery_soc; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = info->battery_temper; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = info->pdata->voltage_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = info->pdata->voltage_min; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int samsung_battery_set_property(struct power_supply *ps, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_info, + psy_bat); + + if (info->is_suspended) { + pr_info("%s: now in suspend\n", __func__); + return 0; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_TECHNOLOGY: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + break; + default: + return -EINVAL; + } + + cancel_work_sync(&info->monitor_work); + wake_lock(&info->monitor_wake_lock); + schedule_work(&info->monitor_work); + + return 0; +} + +static int samsung_usb_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_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 == POWER_SUPPLY_TYPE_USB) || + (info->cable_type == POWER_SUPPLY_TYPE_USB_CDP); + + return 0; +} + +static int samsung_ac_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_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 == POWER_SUPPLY_TYPE_MAINS) || + (info->cable_type == POWER_SUPPLY_TYPE_MISC) || + (info->cable_type == POWER_SUPPLY_TYPE_DOCK) || + (info->cable_type == POWER_SUPPLY_TYPE_WIRELESS); + + return 0; +} + +static __devinit int samsung_battery_probe(struct platform_device *pdev) +{ + struct battery_info *info; + int ret = 0; + char *temper_src_name[] = { "fuelgauge", "ap adc", + "ext adc", "unknown" + }; + pr_info("%s: SAMSUNG Battery Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + info->pdata = pdev->dev.platform_data; + + /* Check charger name and fuelgauge name. */ + if (!info->pdata->charger_name || !info->pdata->fuelgauge_name) { + pr_err("%s: no charger or fuel gauge name\n", __func__); + goto err_psy_get; + } + info->charger_name = info->pdata->charger_name; + info->fuelgauge_name = info->pdata->fuelgauge_name; +#if defined(CONFIG_CHARGER_MAX8922_U1) + if (system_rev >= 2) + info->sub_charger_name = info->pdata->sub_charger_name; +#endif + pr_info("%s: Charger name: %s\n", __func__, info->charger_name); + pr_info("%s: Fuelgauge name: %s\n", __func__, info->fuelgauge_name); +#if defined(CONFIG_CHARGER_MAX8922_U1) + if (system_rev >= 2) + pr_info("%s: SubCharger name: %s\n", __func__, + info->sub_charger_name); +#endif + + info->psy_charger = power_supply_get_by_name(info->charger_name); + info->psy_fuelgauge = power_supply_get_by_name(info->fuelgauge_name); +#if defined(CONFIG_CHARGER_MAX8922_U1) + if (system_rev >= 2) + info->psy_sub_charger = + power_supply_get_by_name(info->sub_charger_name); +#endif + if (!info->psy_charger || !info->psy_fuelgauge) { + pr_err("%s: fail to get power supply\n", __func__); + goto err_psy_get; + } + + /* WORKAROUND: set battery pdata in driver */ + if (system_rev == 3) { + info->pdata->temper_src = TEMPER_EXT_ADC; + info->pdata->temper_ch = 7; + } + pr_info("%s: Temperature source: %s\n", __func__, + temper_src_name[info->pdata->temper_src]); + + /* recalculate recharge voltage, it depends on max voltage value */ + info->pdata->recharge_voltage = info->pdata->voltage_max - + RECHG_DROP_VALUE; + pr_info("%s: Recharge voltage: %d\n", __func__, + info->pdata->recharge_voltage); +#if defined(CONFIG_S3C_ADC) +#if defined(CONFIG_MACH_S2PLUS) + if (system_rev >= 2) +#endif + /* adc register */ + info->adc_client = s3c_adc_register(pdev, NULL, NULL, 0); + + if (IS_ERR(info->adc_client)) { + pr_err("%s: fail to register adc\n", __func__); + goto err_adc_reg; + } +#endif + + /* init battery info */ + info->charge_real_state = battery_get_info(info, + POWER_SUPPLY_PROP_STATUS); + if ((info->charge_real_state == POWER_SUPPLY_STATUS_CHARGING) || + (info->charge_real_state == POWER_SUPPLY_STATUS_FULL)) { + pr_info("%s: boot with charging, s(%d)\n", __func__, + info->charge_real_state); + info->charge_start_time = 1; + } else { + pr_info("%s: boot without charging, s(%d)\n", __func__, + info->charge_real_state); + info->charge_start_time = 0; + } + info->full_charged_state = false; + info->abstimer_state = false; + info->recharge_phase = false; + info->siop_charge_current = info->pdata->chg_curr_usb; + info->monitor_mode = MONITOR_NORM; + info->led_state = BATT_LED_DISCHARGING; + info->monitor_count = 0; + + /* LPM charging state */ + info->lpm_state = lpcharge; + + mutex_init(&info->mon_lock); + mutex_init(&info->ops_lock); + mutex_init(&info->err_lock); + + wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "battery-monitor"); + wake_lock_init(&info->emer_wake_lock, WAKE_LOCK_SUSPEND, + "battery-emergency"); + if (!info->pdata->suspend_chging) + wake_lock_init(&info->charge_wake_lock, + WAKE_LOCK_SUSPEND, "battery-charging"); +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + wake_lock_init(&info->update_wake_lock, WAKE_LOCK_SUSPEND, + "battery-update"); +#endif + + /* Init wq for battery */ + INIT_WORK(&info->error_work, battery_error_work); + INIT_WORK(&info->monitor_work, battery_monitor_work); + + /* Init Power supply class */ + info->psy_bat.name = "battery"; + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY; + info->psy_bat.properties = samsung_battery_props; + info->psy_bat.num_properties = ARRAY_SIZE(samsung_battery_props); + info->psy_bat.get_property = samsung_battery_get_property; + info->psy_bat.set_property = samsung_battery_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 = samsung_power_props; + info->psy_usb.num_properties = ARRAY_SIZE(samsung_power_props); + info->psy_usb.get_property = samsung_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 = samsung_power_props; + info->psy_ac.num_properties = ARRAY_SIZE(samsung_power_props); + info->psy_ac.get_property = samsung_ac_get_property; + + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + pr_err("%s: failed to register psy_bat\n", __func__); + goto err_psy_reg_bat; + } + + ret = power_supply_register(&pdev->dev, &info->psy_usb); + if (ret) { + pr_err("%s: failed to register psy_usb\n", __func__); + goto err_psy_reg_usb; + } + + ret = power_supply_register(&pdev->dev, &info->psy_ac); + if (ret) { + pr_err("%s: failed to register psy_ac\n", __func__); + goto err_psy_reg_ac; + } + + /* Using android alarm for gauging instead of workqueue */ + info->last_poll = alarm_get_elapsed_realtime(); + alarm_init(&info->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + samsung_battery_alarm_start); + + /* update battery init status */ + schedule_work(&info->monitor_work); + + /* Create samsung detail attributes */ + battery_create_attrs(info->psy_bat.dev); + +#if defined(CONFIG_TARGET_LOCALE_KOR) + info->entry = create_proc_entry("batt_info_proc", S_IRUGO, NULL); + if (!info->entry) + pr_err("%s: failed to create proc_entry\n", __func__); + else { + info->entry->read_proc = battery_info_proc; + info->entry->data = (struct battery_info *)info; + } +#endif + + pr_info("%s: SAMSUNG Battery Driver Loaded\n", __func__); + return 0; + +err_psy_reg_ac: + power_supply_unregister(&info->psy_usb); +err_psy_reg_usb: + power_supply_unregister(&info->psy_bat); +err_psy_reg_bat: + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->emer_wake_lock); +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + wake_lock_destroy(&info->update_wake_lock); +#endif + mutex_destroy(&info->mon_lock); + mutex_destroy(&info->ops_lock); + mutex_destroy(&info->err_lock); + if (!info->pdata->suspend_chging) + wake_lock_destroy(&info->charge_wake_lock); + +err_adc_reg: +err_psy_get: + kfree(info); + + return ret; +} + +static int __devexit samsung_battery_remove(struct platform_device *pdev) +{ + struct battery_info *info = platform_get_drvdata(pdev); + + remove_proc_entry("battery_info_proc", NULL); + + cancel_work_sync(&info->error_work); + cancel_work_sync(&info->monitor_work); + + power_supply_unregister(&info->psy_bat); + power_supply_unregister(&info->psy_usb); + power_supply_unregister(&info->psy_ac); + + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->emer_wake_lock); +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC) + wake_lock_destroy(&info->update_wake_lock); +#endif + if (!info->pdata->suspend_chging) + wake_lock_destroy(&info->charge_wake_lock); + + mutex_destroy(&info->mon_lock); + mutex_destroy(&info->ops_lock); + mutex_destroy(&info->err_lock); + + kfree(info); + + return 0; +} + +#ifdef CONFIG_PM +static int samsung_battery_prepare(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + if ((info->monitor_mode != MONITOR_EMER_LV1) && + (info->monitor_mode != MONITOR_EMER_LV2)) { + if ((info->charge_real_state == + POWER_SUPPLY_STATUS_CHARGING) || + (info->charge_virt_state == + POWER_SUPPLY_STATUS_CHARGING)) + info->monitor_mode = MONITOR_CHNG_SUSP; + else + info->monitor_mode = MONITOR_NORM_SUSP; + } + + battery_monitor_interval(info); + + return 0; +} + +static void samsung_battery_complete(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + info->monitor_mode = MONITOR_NORM; +} + +static int samsung_battery_suspend(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + info->is_suspended = true; + + cancel_work_sync(&info->monitor_work); + + return 0; +} + +static int samsung_battery_resume(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + schedule_work(&info->monitor_work); + + info->is_suspended = false; + + return 0; +} + +static const struct dev_pm_ops samsung_battery_pm_ops = { + .prepare = samsung_battery_prepare, + .complete = samsung_battery_complete, + .suspend = samsung_battery_suspend, + .resume = samsung_battery_resume, +}; +#endif + +static struct platform_driver samsung_battery_driver = { + .driver = { + .name = "samsung-battery", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &samsung_battery_pm_ops, +#endif + }, + .probe = samsung_battery_probe, + .remove = __devexit_p(samsung_battery_remove), +}; + +static int __init samsung_battery_init(void) +{ + return platform_driver_register(&samsung_battery_driver); +} + +static void __exit samsung_battery_exit(void) +{ + platform_driver_unregister(&samsung_battery_driver); +} + +late_initcall(samsung_battery_init); +module_exit(samsung_battery_exit); + +MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>"); +MODULE_DESCRIPTION("SAMSUNG battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/samsung_battery_s2plus.c b/drivers/battery/samsung_battery_s2plus.c new file mode 100644 index 0000000..8f26e4f --- /dev/null +++ b/drivers/battery/samsung_battery_s2plus.c @@ -0,0 +1,1273 @@ +/* + * samsung_battery.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@samsung.com> + * + * based on sec_battery.c + * + * Copyright (C) 2012 Samsung Electronics + * Jaecheol Kim <jc22.kim@samsung.com> + * + * add sub-charger based on samsung_battery.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/reboot.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/android_alarm.h> +#include <linux/battery/samsung_battery.h> +#include <mach/regs-pmu.h> +#include "battery-factory.h" +#if defined(CONFIG_S3C_ADC) +#include <plat/adc.h> +#endif + +static char *supply_list[] = { + "battery", +}; + +/* Get LP charging mode state */ +unsigned int lpcharge; +static int battery_get_lpm_state(char *str) +{ + get_option(&str, &lpcharge); + pr_info("%s: Low power charging mode: %d\n", __func__, lpcharge); + + return lpcharge; +} +__setup("lpcharge=", battery_get_lpm_state); + +/* Temperature from fuelgauge or adc */ +static int battery_get_temper(struct battery_info *info) +{ + union power_supply_propval value; + int temper = 0; + int retry_cnt = 0; + pr_debug("%s\n", __func__); + + switch (info->pdata->temper_src) { + case TEMPER_FUELGAUGE: + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_TEMP, &value); + temper = value.intval; + break; + case TEMPER_AP_ADC: +#if defined(CONFIG_S3C_ADC) + do { + info->battery_temper_adc = + s3c_adc_read(info->adc_client, + info->pdata->temper_ch); + + if (info->battery_temper_adc < 0) { + pr_info("%s: adc read(%d), retry(%d)", __func__, + info->battery_temper_adc, retry_cnt); + retry_cnt++; + msleep(100); + } + } while ((info->battery_temper_adc < 0) && (retry_cnt <= 5)); + + if (info->battery_temper_adc < 0) { + pr_info("%s: adc read error(%d), temper set as 30.0", + __func__, info->battery_temper_adc); + temper = 300; + } else { + temper = info->pdata->covert_adc( + info->battery_temper_adc, + info->pdata->temper_ch); + } +#endif + break; + case TEMPER_EXT_ADC: +#if defined(CONFIG_STMPE811_ADC) + temper = stmpe811_get_adc_value(info->pdata->temper_ch); +#endif + break; + case TEMPER_UNKNOWN: + default: + pr_info("%s: invalid temper src(%d)\n", __func__, + info->pdata->temper_src); + temper = 300; + break; + } + + pr_debug("%s: temper(%d), source(%d)\n", __func__, + temper, info->pdata->temper_src); + return temper; +} + +/* Get info from power supply at realtime */ +int battery_get_info(struct battery_info *info, + enum power_supply_property property) +{ + union power_supply_propval value; + value.intval = 0; + + if (info->battery_error_test) { + pr_info("%s: in test mode(%d), do not update\n", __func__, + info->battery_error_test); + return -EPERM; + } + + switch (property) { + /* Update from charger */ + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + info->psy_charger->get_property(info->psy_charger, + property, &value); + break; + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (info->use_sub_charger) { + info->psy_sub_charger->get_property(info->psy_sub_charger, + property, &value); + } else { + info->psy_charger->get_property(info->psy_charger, + property, &value); + } + break; + /* Update from fuelgauge */ + case POWER_SUPPLY_PROP_CAPACITY: /* Only Adjusted SOC */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* Only VCELL */ + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + property, &value); + break; + /* Update from fuelgauge or adc */ + case POWER_SUPPLY_PROP_TEMP: + value.intval = battery_get_temper(info); + break; + default: + break; + } + + return value.intval; +} + +/* Update all values for battery */ +void battery_update_info(struct battery_info *info) +{ + union power_supply_propval value; + + if (info->use_sub_charger) { + /* Update from Charger */ + info->psy_sub_charger->get_property(info->psy_sub_charger, + POWER_SUPPLY_PROP_STATUS, &value); + info->charge_real_state = info->charge_virt_state = value.intval; + + info->psy_sub_charger->get_property(info->psy_sub_charger, + POWER_SUPPLY_PROP_CURRENT_NOW, &value); + info->charge_current = value.intval; + + } else { + /* Update from Charger */ + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_STATUS, &value); + info->charge_real_state = info->charge_virt_state = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_CURRENT_NOW, &value); + info->charge_current = value.intval; + } + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_HEALTH, &value); + info->battery_health = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_PRESENT, &value); + info->battery_present = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_ONLINE, &value); + info->cable_type = value.intval; + + info->psy_charger->get_property(info->psy_charger, + POWER_SUPPLY_PROP_CHARGE_TYPE, &value); + info->charge_type = value.intval; + + /* Fuelgauge power off state */ + if ((info->cable_type != POWER_SUPPLY_TYPE_BATTERY) && + (info->battery_present == 0)) { + pr_info("%s: Abnormal fuelgauge power state\n", __func__); + goto update_finish; + } + + /* Update from Fuelgauge */ + value.intval = SOC_TYPE_ADJUSTED; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_CAPACITY, &value); + info->battery_soc = value.intval; + + value.intval = SOC_TYPE_RAW; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_CAPACITY, &value); + info->battery_raw_soc = value.intval; + + value.intval = VOLTAGE_TYPE_VCELL; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + &value); + info->battery_vcell = value.intval; + + value.intval = VOLTAGE_TYPE_VFOCV; + info->psy_fuelgauge->get_property(info->psy_fuelgauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + &value); + info->battery_vfocv = value.intval; + + info->battery_temper = battery_get_temper(info); + +update_finish: + switch (info->battery_error_test) { + case 0: + pr_debug("%s: error test: normal state\n", __func__); + break; + case 1: + pr_info("%s: error test: full charged\n", __func__); + info->charge_real_state = POWER_SUPPLY_STATUS_FULL; + info->battery_vcell = 4200000; + info->battery_soc = 100; + break; + case 2: + pr_info("%s: error test: freezed\n", __func__); + info->battery_temper = info->pdata->freeze_stop_temp - 10; + break; + case 3: + pr_info("%s: error test: overheated\n", __func__); + info->battery_temper = info->pdata->overheat_stop_temp + 10; + break; + case 4: + pr_info("%s: error test: ovp\n", __func__); + break; + case 5: + pr_info("%s: error test: vf error\n", __func__); + info->battery_present = 0; + break; + default: + pr_info("%s: error test: unknown state\n", __func__); + break; + } + + pr_debug("%s: state(%d), type(%d), " + "health(%d), present(%d), " + "cable(%d), curr(%d), " + "soc(%d), raw(%d), " + "vol(%d), ocv(%d), tmp(%d)\n", __func__, + info->charge_real_state, info->charge_type, + info->battery_health, info->battery_present, + info->cable_type, info->charge_current, + info->battery_soc, info->battery_raw_soc, + info->battery_vcell, info->battery_vfocv, + info->battery_temper); +} + +/* Control charger and fuelgauge */ +void battery_control_info(struct battery_info *info, + enum power_supply_property property, int intval) +{ + union power_supply_propval value; + + value.intval = intval; + + switch (property) { + /* Control to charger */ + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (info->use_sub_charger) { + info->psy_sub_charger->set_property(info->psy_sub_charger, + property, &value); + } else { + info->psy_charger->set_property(info->psy_charger, + property, &value); + } + break; + + /* Control to fuelgauge */ + case POWER_SUPPLY_PROP_CAPACITY: + info->psy_fuelgauge->set_property(info->psy_fuelgauge, + property, &value); + break; + default: + break; + } +} + +/* Support property from battery */ +static enum power_supply_property samsung_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +/* Support property from usb, ac */ +static enum power_supply_property samsung_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int samsung_battery_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_info, + psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = info->charge_virt_state; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = info->charge_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = info->battery_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = info->battery_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->cable_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = info->battery_vcell; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = info->charge_current; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = info->battery_soc; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = info->battery_temper; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int samsung_battery_set_property(struct power_supply *ps, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_info, + psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_TECHNOLOGY: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + break; + default: + return -EINVAL; + } + + wake_lock(&info->monitor_wake_lock); + schedule_work(&info->monitor_work); + + return 0; +} + +static int samsung_usb_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_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 == POWER_SUPPLY_TYPE_USB) || + (info->cable_type == POWER_SUPPLY_TYPE_USB_CDP); + + return 0; +} + +static int samsung_ac_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_info *info = container_of(ps, struct battery_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 == POWER_SUPPLY_TYPE_MAINS) || + (info->cable_type == POWER_SUPPLY_TYPE_MISC) || + (info->cable_type == POWER_SUPPLY_TYPE_WIRELESS); + + return 0; +} + +static void samsung_battery_alarm_start(struct alarm *alarm) +{ + struct battery_info *info = container_of(alarm, struct battery_info, + alarm); + pr_debug("%s\n", __func__); + + wake_lock(&info->monitor_wake_lock); + schedule_work(&info->monitor_work); +} + +static void samsung_battery_next_monitor(struct battery_info *info) +{ + ktime_t interval, next; + unsigned long flags; + pr_debug("%s\n", __func__); + + local_irq_save(flags); + + info->last_poll = alarm_get_elapsed_realtime(); + + switch (info->monitor_mode) { + case MONITOR_CHNG: + info->monitor_interval = info->pdata->chng_interval; + break; + case MONITOR_CHNG_SUSP: + info->monitor_interval = info->pdata->chng_susp_interval; + break; + case MONITOR_NORM: + info->monitor_interval = info->pdata->norm_interval; + break; + case MONITOR_NORM_SUSP: + info->monitor_interval = info->pdata->norm_susp_interval; + break; + case MONITOR_EMER: + info->monitor_interval = info->pdata->emer_interval; + break; + default: + info->monitor_interval = info->pdata->norm_interval; + break; + } + + pr_debug("%s: monitor mode(%d), interval(%d)\n", __func__, + info->monitor_mode, info->monitor_interval); + + interval = ktime_set(info->monitor_interval, 0); + next = ktime_add(info->last_poll, interval); + alarm_start_range(&info->alarm, next, next); + + local_irq_restore(flags); +} + +static bool battery_recharge_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + if (info->charge_real_state == POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("%s: not recharge cond., now charging\n", __func__); + return false; + } + + if (info->battery_vcell < info->pdata->recharge_voltage) { + pr_info("%s: recharge start(%d ?? %d)\n", __func__, + info->battery_vcell, info->pdata->recharge_voltage); + return true; + } else + pr_debug("%s: not recharge cond., vcell is enough\n", __func__); + + return false; +} + +static bool battery_abstimer_cond(struct battery_info *info) +{ + unsigned int abstimer_duration; + ktime_t ktime; + struct timespec current_time; + pr_debug("%s\n", __func__); + + if ((info->cable_type != POWER_SUPPLY_TYPE_MAINS) || + (info->full_charged_state == true) || + (info->charge_start_time == 0)) { + info->abstimer_state = false; + return false; + } + + ktime = alarm_get_elapsed_realtime(); + current_time = ktime_to_timespec(ktime); + + if (info->recharge_phase) + abstimer_duration = info->pdata->abstimer_recharge_duration; + else + abstimer_duration = info->pdata->abstimer_charge_duration; + + if ((current_time.tv_sec - info->charge_start_time) > + abstimer_duration) { + pr_info("%s: charge time out(%d - %d ?? %d)\n", __func__, + (int)current_time.tv_sec, + info->charge_start_time, + abstimer_duration); + info->abstimer_state = true; + } else { + pr_debug("%s: not abstimer condition\n", __func__); + info->abstimer_state = false; + } + + return info->abstimer_state; +} + +static bool battery_fullcharged_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + if ((info->charge_real_state == POWER_SUPPLY_STATUS_FULL) && + (info->battery_vcell > 4150000) && + (info->battery_soc > 95)) { + pr_info("%s: real full charged(%d, %d)\n", __func__, + info->battery_vcell, info->battery_soc); + info->full_charged_state = true; + return true; + } else if (info->full_charged_state == true) { + pr_debug("%s: already full charged\n", __func__); + } else { + pr_debug("%s: not full charged\n", __func__); + info->full_charged_state = false; + } + + /* Add some more full charged case(current sensing, etc...) */ + + return false; +} + +static bool battery_vf_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + +#if defined(CONFIG_MACH_P11) + /* FIXME: fix P11 build error temporarily */ +#else + /* jig detect by MUIC */ + if (is_jig_attached == JIG_ON) { + pr_info("%s: JIG ON, do not check\n", __func__); + info->vf_state = false; + return false; + } +#endif + + /* TODO: Check VF from ADC */ + + /* Now, battery present from charger */ + info->battery_present = + battery_get_info(info, POWER_SUPPLY_PROP_PRESENT); + if (info->battery_present == 0) { + pr_info("%s: battery is not detected.\n", __func__); + info->vf_state = true; + } else { + pr_debug("%s: battery is detected.\n", __func__); + info->vf_state = false; + } + + return info->vf_state; +} + +static bool battery_health_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + if (info->battery_health == POWER_SUPPLY_HEALTH_DEAD) { + pr_info("%s: battery dead(%d)\n", __func__, + info->battery_health); + info->health_state = true; + } else if (info->battery_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) { + pr_info("%s: battery overvoltage(%d)\n", __func__, + info->battery_health); + info->health_state = true; + } else { + pr_debug("%s: battery good(%d)\n", __func__, + info->battery_health); + info->health_state = false; + } + + return info->health_state; +} + +static bool battery_temp_cond(struct battery_info *info) +{ + pr_debug("%s\n", __func__); + + if (info->temperature_state == false) { + if (info->charge_real_state != POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("%s: not charging state\n", __func__); + return false; + } + + pr_debug("%s: check charging stop temp." + "cond: %d ?? %d ~ %d\n", __func__, + info->battery_temper, + info->pdata->freeze_stop_temp, + info->pdata->overheat_stop_temp); + + if (info->battery_temper >= + info->pdata->overheat_stop_temp) { + pr_info("%s: stop by overheated temp\n", __func__); + info->overheated_state = true; + } else if (info->battery_temper <= + info->pdata->freeze_stop_temp) { + pr_info("%s: stop by freezed temp\n", __func__); + info->freezed_state = true; + } else + pr_debug("%s: normal charging temp\n", __func__); + } else { + pr_debug("%s: check charging recovery temp." + "cond: %d ?? %d ~ %d\n", __func__, + info->battery_temper, + info->pdata->freeze_recovery_temp, + info->pdata->overheat_recovery_temp); + + if ((info->overheated_state == true) && + (info->battery_temper <= + info->pdata->overheat_recovery_temp)) { + pr_info("%s: recovery from overheated\n", + __func__); + info->overheated_state = false; + } else if ((info->freezed_state == true) && + (info->battery_temper >= + info->pdata->freeze_recovery_temp)) { + pr_info("%s: recovery from freezed\n", + __func__); + info->freezed_state = false; + } else + pr_info("%s: charge stopped temp\n", __func__); + } + + if (info->overheated_state == true) { + info->battery_health = POWER_SUPPLY_HEALTH_OVERHEAT; + info->freezed_state = false; + info->temperature_state = true; + } else if (info->freezed_state == true) { + info->battery_health = POWER_SUPPLY_HEALTH_COLD; + info->overheated_state = false; + info->temperature_state = true; + } else { + info->overheated_state = false; + info->freezed_state = false; + info->temperature_state = false; + } + + return info->temperature_state; +} + +static void battery_charge_control(struct battery_info *info, + int enable, + int set_current) +{ + ktime_t ktime; + struct timespec current_time; + pr_debug("%s\n", __func__); + + ktime = alarm_get_elapsed_realtime(); + current_time = ktime_to_timespec(ktime); + + if (set_current == CHARGER_KEEP_CURRENT) + goto charge_state_control; + + if (!info->use_sub_charger) { + if (info->siop_state == true) { + pr_debug("%s: siop state, charge current is %dmA\n", __func__, + info->siop_charge_current); + set_current = info->siop_charge_current; + } + + /* check charge current before and after */ + if (info->charge_current == ((set_current * 3 / 100) * 333 / 10)) { + /* + * (current * 3 / 100) is converted value + * for register setting. + * (register current * 333 / 10) is actual value + * for charging + */ + pr_debug("%s: same charge current: %dmA\n", __func__, + set_current); + } else { + battery_control_info(info, + POWER_SUPPLY_PROP_CURRENT_NOW, + set_current); + pr_info("%s: update charge current: %dmA\n", __func__, + set_current); + } + } + + info->charge_current = + battery_get_info(info, POWER_SUPPLY_PROP_CURRENT_NOW); + +charge_state_control: + /* check charge state before and after */ + if ((enable == CHARGE_ENABLE) && + (info->charge_start_time == 0)) { + battery_control_info(info, + POWER_SUPPLY_PROP_STATUS, + CHARGE_ENABLE); + + info->charge_start_time = current_time.tv_sec; + pr_info("%s: charge enabled, current as %dmA @%d\n", __func__, + info->charge_current, info->charge_start_time); + } else if ((enable == CHARGE_DISABLE) && + (info->charge_start_time != 0)) { + battery_control_info(info, + POWER_SUPPLY_PROP_STATUS, + CHARGE_DISABLE); + + pr_info("%s: charge disabled, current as %dmA @%d\n", __func__, + info->charge_current, (int)current_time.tv_sec); + + info->charge_start_time = 0; + } else { + pr_debug("%s: same charge state(%s), current as %dmA @%d\n", + __func__, (enable ? "enabled" : "disabled"), + info->charge_current, info->charge_start_time); + } + + info->charge_real_state = + battery_get_info(info, POWER_SUPPLY_PROP_STATUS); +} + +static void battery_monitor_work(struct work_struct *work) +{ + struct battery_info *info = container_of(work, struct battery_info, + monitor_work); + pr_debug("%s\n", __func__); + + /* If battery is not connected, clear flag for charge scenario */ + if (battery_vf_cond(info) == true) { + pr_info("%s: battery error\n", __func__); + info->overheated_state = false; + info->freezed_state = false; + info->temperature_state = false; + info->full_charged_state = false; + info->abstimer_state = false; + info->recharge_phase = false; + + schedule_work(&info->error_work); + } + + /* Check battery state from charger and fuelgauge */ + battery_update_info(info); + + /* If charger is not connected, do not check charge scenario */ + if (info->cable_type == POWER_SUPPLY_TYPE_BATTERY) + goto charge_ok; + + /* Below is charger is connected state */ + if (battery_temp_cond(info) == true) { + pr_info("%s: charge stopped by temperature\n", __func__); + battery_charge_control(info, + CHARGE_DISABLE, CHARGER_OFF_CURRENT); + goto monitor_finish; + } + + if (battery_health_cond(info) == true) { + pr_info("%s: bad health state\n", __func__); + goto monitor_finish; + } + + if (battery_fullcharged_cond(info) == true) { + pr_info("%s: full charged state\n", __func__); + battery_charge_control(info, + CHARGE_DISABLE, CHARGER_KEEP_CURRENT); + info->recharge_phase = true; + goto monitor_finish; + } + + if (battery_abstimer_cond(info) == true) { + pr_info("%s: abstimer state\n", __func__); + battery_charge_control(info, + CHARGE_DISABLE, CHARGER_OFF_CURRENT); + info->recharge_phase = true; + goto monitor_finish; + } + + if (info->recharge_phase == true) { + if (battery_recharge_cond(info) == true) { + pr_info("%s: recharge condition\n", __func__); + goto charge_ok; + } else { + pr_debug("%s: not recharge\n", __func__); + goto monitor_finish; + } + } + +charge_ok: + switch (info->cable_type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (!info->pdata->suspend_chging) + wake_unlock(&info->charge_wake_lock); + battery_charge_control(info, + CHARGE_DISABLE, CHARGER_OFF_CURRENT); + info->charge_virt_state = POWER_SUPPLY_STATUS_DISCHARGING; + + /* clear charge scenario state */ + info->overheated_state = false; + info->freezed_state = false; + info->temperature_state = false; + info->full_charged_state = false; + info->abstimer_state = false; + info->recharge_phase = false; + break; + case POWER_SUPPLY_TYPE_MAINS: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, CHARGE_ENABLE, CHARGER_AC_CURRENT_S2PLUS); + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_TYPE_USB: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, + CHARGE_ENABLE, CHARGER_USB_CURRENT); + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_TYPE_USB_CDP: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, + CHARGE_ENABLE, CHARGER_CDP_CURRENT); + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_TYPE_WIRELESS: + if (!info->pdata->suspend_chging) + wake_lock(&info->charge_wake_lock); + battery_charge_control(info, + CHARGE_ENABLE, CHARGER_WPC_CURRENT); + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + break; + default: + break; + } + +monitor_finish: + /* Overwrite charge state for UI(icon) */ + if (info->full_charged_state == true) { + info->charge_virt_state = POWER_SUPPLY_STATUS_FULL; + info->battery_soc = 100; + } else if (info->abstimer_state == true) { + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + } else if (info->recharge_phase == true) { + info->charge_virt_state = POWER_SUPPLY_STATUS_CHARGING; + } + + if (info->cable_type != POWER_SUPPLY_TYPE_BATTERY) { + if (info->temperature_state == true) + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (info->vf_state == true) { + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + /* to be considered */ + info->battery_health = + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } + + if (info->health_state == true) + info->charge_virt_state = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + /* monitoring interval */ + if (info->charge_virt_state == POWER_SUPPLY_STATUS_NOT_CHARGING) { + pr_debug("%s: emergency(not charging) state\n", __func__); + info->monitor_mode = MONITOR_EMER; + wake_lock(&info->emer_wake_lock); + } else { + pr_debug("%s: normal state\n", __func__); + info->monitor_mode = MONITOR_NORM; + wake_unlock(&info->emer_wake_lock); + } + + pr_info("bat: s(%d), v(%d, %d), b(%d), " + "t(%d.%d), h(%d), " + "ch(%d, %d), cb(%d), cr(%d), " + "abs(%d), f(%d), rch(%d), t(%d)\n", + info->battery_soc, + info->battery_vcell / 1000, + info->battery_vfocv / 1000, + info->battery_present, + info->battery_temper / 10, info->battery_temper % 10, + info->battery_health, + info->charge_real_state, + info->charge_virt_state, + info->cable_type, + info->charge_current, + info->abstimer_state, + info->full_charged_state, + info->recharge_phase, info->charge_start_time); + + /* + * WORKAROUND: Do not power off, if vell is over 3400mV + */ + if (info->battery_soc == 0) { + if (info->battery_vcell > 3400) { + pr_info("%s: soc 0%%, but vcell(%d) is over 3400mV, " + "do not power off\n", + __func__, info->battery_vcell); + info->battery_soc = 1; + } + } + + power_supply_changed(&info->psy_bat); + + /* prevent suspend before starting the alarm */ + samsung_battery_next_monitor(info); + + wake_unlock(&info->monitor_wake_lock); + + return; +} + +static void battery_error_work(struct work_struct *work) +{ + struct battery_info *info = container_of(work, struct battery_info, + error_work); + int err_cnt; + int old_vcell, new_vcell, vcell_diff; + pr_info("%s\n", __func__); + + if (info->vf_state == true) { + pr_info("%s: battery error state\n", __func__); + old_vcell = info->battery_vcell; + new_vcell = 0; + for (err_cnt = 1; err_cnt <= VF_CHECK_COUNT; err_cnt++) { + /* check jig first */ + if (is_jig_attached == JIG_ON) { + pr_info("%s: JIG detected, return\n", __func__); + return; + } + info->battery_present = + battery_get_info(info, + POWER_SUPPLY_PROP_PRESENT); + if (info->battery_present == 0) { + pr_info("%s: battery still error(%d)\n", + __func__, err_cnt); + msleep(VF_CHECK_DELAY); + } else { + pr_info("%s: battery detect ok, " + "check soc\n", __func__); + new_vcell = battery_get_info(info, + POWER_SUPPLY_PROP_VOLTAGE_NOW); + vcell_diff = abs(old_vcell - new_vcell); + pr_info("%s: check vcell: %d -> %d, diff: %d\n", + __func__, info->battery_vcell, + new_vcell, vcell_diff); + if (vcell_diff > RESET_SOC_DIFF_TH) { + pr_info("%s: reset soc\n", __func__); + battery_control_info(info, + POWER_SUPPLY_PROP_CAPACITY, 1); + } else + pr_info("%s: keep soc\n", __func__); + break; + } + + if (err_cnt == VF_CHECK_COUNT) { + pr_info("%s: battery error, power off\n", + __func__); + battery_charge_control(info, + CHARGE_DISABLE, CHARGER_OFF_CURRENT); + } + } + } + + return; +} + +static __devinit int samsung_battery_probe(struct platform_device *pdev) +{ + struct battery_info *info; + int ret = 0; + char *temper_src_name[] = { "fuelgauge", "ap adc", + "ext adc", "unknown" + }; + pr_info("%s: SAMSUNG Battery Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + info->pdata = pdev->dev.platform_data; + + /* Check charger name and fuelgauge name. */ + if (!info->pdata->charger_name || !info->pdata->fuelgauge_name) { + pr_err("%s: no charger or fuel gauge name\n", __func__); + goto err_kfree; + } + info->charger_name = info->pdata->charger_name; + info->fuelgauge_name = info->pdata->fuelgauge_name; + + pr_info("%s: Charger name: %s\n", __func__, info->charger_name); + pr_info("%s: Fuelgauge name: %s\n", __func__, info->fuelgauge_name); + + info->psy_charger = power_supply_get_by_name(info->charger_name); + info->psy_fuelgauge = power_supply_get_by_name(info->fuelgauge_name); + + if (!info->psy_charger || !info->psy_fuelgauge) { + pr_err("%s: fail to get power supply\n", __func__); + goto err_kfree; + } + + info->use_sub_charger = info->pdata->use_sub_charger; + if (info->use_sub_charger) { + info->sub_charger_name = info->pdata->sub_charger_name; + pr_info("%s: subcharger name: %s\n", __func__, + info->sub_charger_name); + info->psy_sub_charger = power_supply_get_by_name(info->sub_charger_name); + + if (!info->psy_sub_charger) { + pr_err("%s fail to get sub charger\n", __func__); + goto err_kfree; + } + } + /* force set S2PLUS recharge voltage */ + info->pdata->recharge_voltage = 4150000; + + pr_info("%s: Temperature source: %s\n", __func__, + temper_src_name[info->pdata->temper_src]); + pr_info("%s: Recharge voltage: %d\n", __func__, + info->pdata->recharge_voltage); + +#if defined(CONFIG_S3C_ADC) + info->adc_client = s3c_adc_register(pdev, NULL, NULL, 0); +#endif + + /* init battery info */ + info->full_charged_state = false; + info->abstimer_state = false; + info->recharge_phase = false; + info->siop_charge_current = CHARGER_USB_CURRENT; + info->monitor_mode = MONITOR_NORM; + + /* LPM charging state */ + info->lpm_state = lpcharge; + + wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "battery-monitor"); + wake_lock_init(&info->emer_wake_lock, WAKE_LOCK_SUSPEND, + "battery-emergency"); + if (!info->pdata->suspend_chging) + wake_lock_init(&info->charge_wake_lock, + WAKE_LOCK_SUSPEND, "battery-charging"); + + /* Init wq for battery */ + INIT_WORK(&info->error_work, battery_error_work); + INIT_WORK(&info->monitor_work, battery_monitor_work); + + /* Init Power supply class */ + info->psy_bat.name = "battery"; + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY; + info->psy_bat.properties = samsung_battery_props; + info->psy_bat.num_properties = ARRAY_SIZE(samsung_battery_props); + info->psy_bat.get_property = samsung_battery_get_property; + info->psy_bat.set_property = samsung_battery_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 = samsung_power_props; + info->psy_usb.num_properties = ARRAY_SIZE(samsung_power_props); + info->psy_usb.get_property = samsung_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 = samsung_power_props; + info->psy_ac.num_properties = ARRAY_SIZE(samsung_power_props); + info->psy_ac.get_property = samsung_ac_get_property; + + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + pr_err("%s: failed to register psy_bat\n", __func__); + goto err_psy_reg_bat; + } + + ret = power_supply_register(&pdev->dev, &info->psy_usb); + if (ret) { + pr_err("%s: failed to register psy_usb\n", __func__); + goto err_psy_reg_usb; + } + + ret = power_supply_register(&pdev->dev, &info->psy_ac); + if (ret) { + pr_err("%s: failed to register psy_ac\n", __func__); + goto err_psy_reg_ac; + } + + /* Using android alarm for gauging instead of workqueue */ + info->last_poll = alarm_get_elapsed_realtime(); + alarm_init(&info->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + samsung_battery_alarm_start); + + /* update battery init status */ + schedule_work(&info->monitor_work); + + /* Create samsung detail attributes */ + battery_create_attrs(info->psy_bat.dev); + + pr_info("%s: SAMSUNG Battery Driver Loaded\n", __func__); + return 0; + + err_psy_reg_ac: + power_supply_unregister(&info->psy_usb); + err_psy_reg_usb: + power_supply_unregister(&info->psy_bat); + err_psy_reg_bat: + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->emer_wake_lock); + if (!info->pdata->suspend_chging) + wake_lock_destroy(&info->charge_wake_lock); + err_kfree: + kfree(info); + + return ret; +} + +static int __devexit samsung_battery_remove(struct platform_device *pdev) +{ + struct battery_info *info = platform_get_drvdata(pdev); + + remove_proc_entry("battery_info_proc", NULL); + + cancel_work_sync(&info->error_work); + cancel_work_sync(&info->monitor_work); + + power_supply_unregister(&info->psy_bat); + power_supply_unregister(&info->psy_usb); + power_supply_unregister(&info->psy_ac); + + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->emer_wake_lock); + if (!info->pdata->suspend_chging) + wake_lock_destroy(&info->charge_wake_lock); + + kfree(info); + + return 0; +} + +#ifdef CONFIG_PM +static int samsung_battery_prepare(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + if (info->monitor_mode != MONITOR_EMER) { + if (info->charge_real_state == POWER_SUPPLY_STATUS_CHARGING) + info->monitor_mode = MONITOR_CHNG_SUSP; + else + info->monitor_mode = MONITOR_NORM_SUSP; + } + + samsung_battery_next_monitor(info); + + return 0; +} + +static void samsung_battery_complete(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + info->monitor_mode = MONITOR_NORM; +} + +static int samsung_battery_suspend(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + flush_work_sync(&info->monitor_work); + + return 0; +} + +static int samsung_battery_resume(struct device *dev) +{ + struct battery_info *info = dev_get_drvdata(dev); + pr_info("%s\n", __func__); + + schedule_work(&info->monitor_work); + + return 0; +} + +static const struct dev_pm_ops samsung_battery_pm_ops = { + .prepare = samsung_battery_prepare, + .complete = samsung_battery_complete, + .suspend = samsung_battery_suspend, + .resume = samsung_battery_resume, +}; +#endif + +static struct platform_driver samsung_battery_driver = { + .driver = { + .name = "samsung-battery", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &samsung_battery_pm_ops, +#endif + }, + .probe = samsung_battery_probe, + .remove = __devexit_p(samsung_battery_remove), +}; + +static int __init samsung_battery_init(void) +{ + return platform_driver_register(&samsung_battery_driver); +} + +static void __exit samsung_battery_exit(void) +{ + platform_driver_unregister(&samsung_battery_driver); +} + +late_initcall(samsung_battery_init); +module_exit(samsung_battery_exit); + +MODULE_AUTHOR("Jaecheol Kim <jc22.kim@samsung.com>"); +MODULE_DESCRIPTION("SAMSUNG battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/sec_battery.c b/drivers/battery/sec_battery.c new file mode 100644 index 0000000..3be97cd --- /dev/null +++ b/drivers/battery/sec_battery.c @@ -0,0 +1,2199 @@ +/* + * sec_battery.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG + +#include <linux/battery/sec_battery.h> + +char *sec_bat_charging_mode_str[] = { + "None", + "Normal", + "Additional", + "Re-Charging" +}; + +char *sec_bat_status_str[] = { + "Unknown", + "Charging", + "Discharging", + "Not-charging", + "Full" +}; + +char *sec_bat_health_str[] = { + "Unknown", + "Good", + "Overheat", + "Dead", + "OverVoltage", + "UnspecFailure", + "Cold", + "UnderVoltage" +}; + +static int sec_bat_set_charge( + struct sec_battery_info *battery, + bool enable) +{ + union power_supply_propval val; + ktime_t current_time; + struct timespec ts; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + if ((battery->cable_type != POWER_SUPPLY_TYPE_BATTERY) && + (battery->health != POWER_SUPPLY_HEALTH_GOOD)) { + dev_info(battery->dev, + "%s: Battery is NOT good!\n", __func__); + return -EPERM; + } + + if (enable) { + val.intval = battery->cable_type; + /*Reset charging start time only in initial charging start */ + if (battery->charging_start_time == 0) { + battery->charging_start_time = ts.tv_sec; + battery->charging_next_time = + battery->pdata->charging_reset_time; + } + } else { + val.intval = POWER_SUPPLY_TYPE_BATTERY; + battery->charging_start_time = 0; + battery->charging_passed_time = 0; + battery->charging_next_time = 0; + battery->full_check_cnt = 0; + } + + battery->temp_high_cnt = 0; + battery->temp_low_cnt = 0; + battery->temp_recover_cnt = 0; + + psy_do_property("sec-charger", set, + POWER_SUPPLY_PROP_ONLINE, val); + + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_ONLINE, val); + + return 0; +} + +static int sec_bat_get_adc_data(struct sec_battery_info *battery, + int adc_ch, int count) +{ + int adc_data; + int adc_max; + int adc_min; + int adc_total; + int i; + + adc_data = 0; + adc_max = 0; + adc_min = 0; + adc_total = 0; + + for (i = 0; i < count; i++) { + mutex_lock(&battery->adclock); + adc_data = adc_read(battery->pdata, adc_ch); + mutex_unlock(&battery->adclock); + + if (adc_data < 0) + 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) / (count - 2); +err: + return adc_data; +} + +static unsigned long calculate_average_adc( + struct sec_battery_info *battery, + int channel, int adc) +{ + unsigned int cnt = 0; + int total_adc = 0; + int average_adc = 0; + int index = 0; + + cnt = battery->adc_sample[channel].cnt; + total_adc = battery->adc_sample[channel].total_adc; + + if (adc < 0) { + dev_err(battery->dev, + "%s : Invalid ADC : %d\n", __func__, adc); + adc = battery->adc_sample[channel].average_adc; + } + + if (cnt < ADC_SAMPLE_COUNT) { + battery->adc_sample[channel].adc_arr[cnt] = adc; + battery->adc_sample[channel].index = cnt; + battery->adc_sample[channel].cnt = ++cnt; + + total_adc += adc; + average_adc = total_adc / cnt; + } else { + index = battery->adc_sample[channel].index; + if (++index >= ADC_SAMPLE_COUNT) + index = 0; + + total_adc = total_adc - + battery->adc_sample[channel].adc_arr[index] + adc; + average_adc = total_adc / ADC_SAMPLE_COUNT; + + battery->adc_sample[channel].adc_arr[index] = adc; + battery->adc_sample[channel].index = index; + } + + battery->adc_sample[channel].total_adc = total_adc; + battery->adc_sample[channel].average_adc = average_adc; + + return average_adc; +} + +static int sec_bat_get_adc_value( + struct sec_battery_info *battery, int channel) +{ + int adc; + + adc = sec_bat_get_adc_data(battery, channel, + battery->pdata->adc_check_count); + + if (adc < 0) { + dev_err(battery->dev, + "%s: Error in ADC\n", __func__); + return adc; + } + + return adc; +} + +static int sec_bat_get_charger_type_adc + (struct sec_battery_info *battery) +{ + /* It is true something valid is + connected to the device for charging. + By default this something is considered to be USB.*/ + int result = POWER_SUPPLY_TYPE_USB; + + int adc = 0; + int i; + + battery->pdata->cable_switch_check(); + + adc = sec_bat_get_adc_value(battery, + SEC_BAT_ADC_CHANNEL_CABLE_CHECK); + + battery->pdata->cable_switch_normal(); + + for (i = 0; i < SEC_SIZEOF_POWER_SUPPLY_TYPE; i++) + if ((adc > battery->pdata->cable_adc_value[i].min) && + (adc < battery->pdata->cable_adc_value[i].max)) + break; + if (i >= SEC_SIZEOF_POWER_SUPPLY_TYPE) + dev_err(battery->dev, + "%s : default USB\n", __func__); + else + result = i; + + dev_dbg(battery->dev, "%s : result(%d), adc(%d)\n", + __func__, result, adc); + + return result; +} + +static bool sec_bat_check_vf_adc(struct sec_battery_info *battery) +{ + int adc; + + adc = sec_bat_get_adc_data(battery, + SEC_BAT_ADC_CHANNEL_BAT_CHECK, + battery->pdata->adc_check_count); + + if (adc < 0) { + dev_err(battery->dev, "%s: VF ADC error\n", __func__); + adc = battery->check_adc_value; + } else + battery->check_adc_value = adc; + + if ((battery->check_adc_value < battery->pdata->check_adc_max) && + (battery->check_adc_value > battery->pdata->check_adc_min)) + return true; + else + return false; +} + +static bool sec_bat_check_by_psy(struct sec_battery_info *battery) +{ + char *psy_name; + union power_supply_propval value; + bool ret; + ret = true; + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_PMIC: + psy_name = battery->pdata->pmic_name; + break; + case SEC_BATTERY_CHECK_FUELGAUGE: + psy_name = "sec-fuelgauge"; + break; + case SEC_BATTERY_CHECK_CHARGER: + psy_name = "sec-charger"; + break; + default: + dev_err(battery->dev, + "%s: Invalid Battery Check Type\n", __func__); + ret = false; + goto battery_check_error; + break; + } + + psy_do_property(psy_name, get, + POWER_SUPPLY_PROP_PRESENT, value); + ret = (bool)value.intval; + +battery_check_error: + return ret; +} + +static bool sec_bat_check(struct sec_battery_info *battery) +{ + bool ret; + ret = true; + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_ADC: + ret = sec_bat_check_vf_adc(battery); + break; + case SEC_BATTERY_CHECK_CALLBACK: + ret = battery->pdata->check_battery_callback(); + break; + case SEC_BATTERY_CHECK_PMIC: + case SEC_BATTERY_CHECK_FUELGAUGE: + case SEC_BATTERY_CHECK_CHARGER: + ret = sec_bat_check_by_psy(battery); + break; + case SEC_BATTERY_CHECK_INT: + ret = battery->present; + break; + case SEC_BATTERY_CHECK_NONE: + dev_dbg(battery->dev, "%s: No Check\n", __func__); + default: + break; + } + + return ret; +} + +static bool sec_bat_battery_cable_check(struct sec_battery_info *battery) +{ + if (!sec_bat_check(battery)) { + if (battery->check_count < battery->pdata->check_count) + battery->check_count++; + else { + dev_err(battery->dev, + "%s: Battery Disconnected\n", __func__); + battery->present = false; + battery->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + battery->pdata->check_battery_result_callback(); + return false; + } + } else + battery->check_count = 0; + + battery->present = true; + + dev_dbg(battery->dev, "%s: Battery Connected\n", __func__); + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_POLLING) { + /* check cable status */ + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->cable_work); + } + return true; +}; + +static bool sec_bat_ovp_uvlo_by_psy(struct sec_battery_info *battery) +{ + char *psy_name; + union power_supply_propval value; + bool ret; + ret = true; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + psy_name = battery->pdata->pmic_name; + break; + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + psy_name = "sec-charger"; + break; + default: + dev_err(battery->dev, + "%s: Invalid OVP/UVLO Check Type\n", __func__); + ret = false; + goto ovp_uvlo_check_error; + break; + } + + psy_do_property(psy_name, get, + POWER_SUPPLY_PROP_HEALTH, value); + + if (value.intval != POWER_SUPPLY_HEALTH_GOOD) { + battery->health = value.intval; + ret = false; + } + +ovp_uvlo_check_error: + return ret; +} + +static bool sec_bat_ovp_uvlo(struct sec_battery_info *battery) +{ + bool ret; + ret = true; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_CALLBACK: + ret = battery->pdata->ovp_uvlo_callback(); + break; + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + ret = sec_bat_ovp_uvlo_by_psy(battery); + break; + case SEC_BATTERY_OVP_UVLO_PMICINT: + case SEC_BATTERY_OVP_UVLO_CHGINT: + /* nothing for interrupt check */ + default: + break; + } + + return ret; +} + +static bool sec_bat_check_recharge(struct sec_battery_info *battery) +{ + if ((battery->status == POWER_SUPPLY_STATUS_FULL || + (battery->status == POWER_SUPPLY_STATUS_CHARGING && + (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL))) && + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_SOC) && + (battery->capacity <= + battery->pdata->recharge_condition_soc)) { + dev_info(battery->dev, + "%s: Re-charging by SOC (%d)\n", + __func__, battery->capacity); + return true; + } + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_AVGVCELL) && + (battery->voltage_avg <= + battery->pdata->recharge_condition_avgvcell)) { + dev_info(battery->dev, + "%s: Re-charging by average VCELL (%d)\n", + __func__, battery->voltage_avg); + return true; + } + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_VCELL) && + (battery->voltage_now <= + battery->pdata->recharge_condition_vcell)) { + dev_info(battery->dev, + "%s: Re-charging by VCELL (%d)\n", + __func__, battery->voltage_now); + return true; + } + } + + return false; +} + +static bool sec_bat_ovp_uvlo_result(struct sec_battery_info *battery) +{ + bool ret; + + ret = true; + + dev_err(battery->dev, + "%s: Abnormal Charging Voltage\n", __func__); + battery->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + ret = battery->pdata->ovp_uvlo_result_callback(battery->health); + + return ret; +} + +static bool sec_bat_voltage_check(struct sec_battery_info *battery) +{ + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + /* OVP/UVLO check */ + if (sec_bat_ovp_uvlo(battery)) + dev_dbg(battery->dev, + "%s: Normal Charging Voltage\n", __func__); + else + sec_bat_ovp_uvlo_result(battery); + + /* Re-Charging check */ + if (sec_bat_check_recharge(battery)) { + battery->charging_mode = SEC_BATTERY_CHARGING_RECHARGING; + sec_bat_set_charge(battery, true); + } + + return true; +}; + +static bool sec_bat_get_temperature_by_adc( + struct sec_battery_info *battery, + enum power_supply_property psp, + union power_supply_propval *value) +{ + int temp = 0; + int temp_adc; + int low = 0; + int high = 0; + int mid = 0; + int channel; + sec_bat_adc_table_data_t *temp_adc_table; + unsigned int temp_adc_table_size; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + channel = SEC_BAT_ADC_CHANNEL_TEMP; + temp_adc_table = battery->pdata->temp_adc_table; + temp_adc_table_size = + battery->pdata->temp_adc_table_size; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + channel = SEC_BAT_ADC_CHANNEL_TEMP_AMBIENT; + temp_adc_table = battery->pdata->temp_amb_adc_table; + temp_adc_table_size = + battery->pdata->temp_amb_adc_table_size; + break; + default: + dev_err(battery->dev, + "%s: Invalid Property\n", __func__); + return false; + } + + temp_adc = sec_bat_get_adc_value(battery, channel); + if (temp_adc < 0) + return true; + battery->temp_adc = temp_adc; + + high = temp_adc_table_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (temp_adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (temp_adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = temp_adc_table[mid].temperature; + + temp += + ((temp_adc_table[mid+1].temperature - temp_adc_table[mid].temperature) * + (temp_adc - temp_adc_table[mid].adc)) / + (temp_adc_table[mid+1].adc - temp_adc_table[mid].adc); + + value->intval = temp; + + dev_dbg(battery->dev, + "%s: Temp(%d), Temp-ADC(%d)\n", + __func__, temp, temp_adc); + + return true; +} + +static bool sec_bat_temperature( + struct sec_battery_info *battery) +{ + bool ret; + ret = true; + + if (battery->pdata->event_check && battery->event) { + battery->temp_high_threshold = + battery->pdata->temp_high_threshold_event; + battery->temp_high_recovery = + battery->pdata->temp_high_recovery_event; + battery->temp_low_recovery = + battery->pdata->temp_low_recovery_event; + battery->temp_low_threshold = + battery->pdata->temp_low_threshold_event; + } else if (battery->pdata->is_lpm()) { + battery->temp_high_threshold = + battery->pdata->temp_high_threshold_lpm; + battery->temp_high_recovery = + battery->pdata->temp_high_recovery_lpm; + battery->temp_low_recovery = + battery->pdata->temp_low_recovery_lpm; + battery->temp_low_threshold = + battery->pdata->temp_low_threshold_lpm; + } else { + battery->temp_high_threshold = + battery->pdata->temp_high_threshold_normal; + battery->temp_high_recovery = + battery->pdata->temp_high_recovery_normal; + battery->temp_low_recovery = + battery->pdata->temp_low_recovery_normal; + battery->temp_low_threshold = + battery->pdata->temp_low_threshold_normal; + } + + dev_info(battery->dev, + "%s: HT(%d), HR(%d), LT(%d), LR(%d)\n", + __func__, battery->temp_high_threshold, + battery->temp_high_recovery, + battery->temp_low_threshold, + battery->temp_low_recovery); + return ret; +} + +static bool sec_bat_temperature_check( + struct sec_battery_info *battery) +{ + int temp_value; + + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + sec_bat_temperature(battery); + + switch (battery->pdata->temp_check_type) { + case SEC_BATTERY_TEMP_CHECK_ADC: + temp_value = battery->temp_adc; + break; + case SEC_BATTERY_TEMP_CHECK_TEMP: + temp_value = battery->temperature; + break; + default: + dev_err(battery->dev, + "%s: Invalid Temp Check Type\n", __func__); + return true; + } + + if (temp_value >= battery->temp_high_threshold) { + if (battery->health != POWER_SUPPLY_HEALTH_OVERHEAT) { + if (battery->temp_high_cnt < + battery->pdata->temp_check_count) + battery->temp_high_cnt++; + dev_dbg(battery->dev, + "%s: high count = %d\n", + __func__, battery->temp_high_cnt); + } + } else if ((temp_value <= battery->temp_high_recovery) && + (temp_value >= battery->temp_low_recovery)) { + if (battery->health == POWER_SUPPLY_HEALTH_OVERHEAT || + battery->health == POWER_SUPPLY_HEALTH_COLD) { + if (battery->temp_recover_cnt < + battery->pdata->temp_check_count) + battery->temp_recover_cnt++; + dev_dbg(battery->dev, + "%s: recovery count = %d\n", + __func__, battery->temp_recover_cnt); + } + } else if (temp_value <= battery->temp_low_threshold) { + if (battery->health != POWER_SUPPLY_HEALTH_COLD) { + if (battery->temp_low_cnt < + battery->pdata->temp_check_count) + battery->temp_low_cnt++; + dev_dbg(battery->dev, + "%s: low count = %d\n", + __func__, battery->temp_low_cnt); + } + } else { + battery->temp_high_cnt = 0; + battery->temp_low_cnt = 0; + battery->temp_recover_cnt = 0; + } + + if (battery->temp_high_cnt >= + battery->pdata->temp_check_count) + battery->health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (battery->temp_low_cnt >= + battery->pdata->temp_check_count) + battery->health = POWER_SUPPLY_HEALTH_COLD; + else if (battery->temp_recover_cnt >= + battery->pdata->temp_check_count) + battery->health = POWER_SUPPLY_HEALTH_GOOD; + + if ((battery->health == POWER_SUPPLY_HEALTH_OVERHEAT) || + (battery->health == POWER_SUPPLY_HEALTH_COLD)) { + dev_info(battery->dev, + "%s: Insafe Temperature\n", __func__); + battery->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + /* change charging current to battery (default 0mA) */ + sec_bat_set_charge(battery, false); + return false; + } else { + dev_dbg(battery->dev, + "%s: Safe Temperature\n", __func__); + /* if recovered from not charging */ + if ((battery->health == POWER_SUPPLY_HEALTH_GOOD) && + (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) { + if (battery->charging_mode == + SEC_BATTERY_CHARGING_RECHARGING) + battery->status = POWER_SUPPLY_STATUS_FULL; + else /* Normal Charging */ + battery->status = POWER_SUPPLY_STATUS_CHARGING; + /* turn on charger by cable type */ + sec_bat_set_charge(battery, true); + } + return true; + } +}; + +static void sec_bat_event_expired_timer_func(unsigned long param) +{ + struct sec_battery_info *battery = + (struct sec_battery_info *)param; + + battery->event &= (~battery->event_wait); + dev_info(battery->dev, + "%s: event expired (0x%x)\n", __func__, battery->event); +} + +static void sec_bat_event_set( + struct sec_battery_info *battery, int event, int enable) +{ + if (!battery->pdata->event_check) + return; + + /* ignore duplicated deactivation of same event + * only if the event is one last event + */ + if (!enable && (battery->event == battery->event_wait)) { + dev_info(battery->dev, + "%s: ignore duplicated deactivation of same event\n", + __func__); + return; + } + + del_timer_sync(&battery->event_expired_timer); + battery->event &= (~battery->event_wait); + + if (enable) { + battery->event_wait = 0; + battery->event |= event; + + dev_info(battery->dev, + "%s: event set (0x%x)\n", __func__, battery->event); + } else { + if (battery->event == 0) { + dev_dbg(battery->dev, + "%s: nothing to clear\n", __func__); + return; /* nothing to clear */ + } + battery->event_wait = event; + mod_timer(&battery->event_expired_timer, + jiffies + (battery->pdata->event_waiting_time * HZ)); + dev_info(battery->dev, + "%s: start timer (curr 0x%x, wait 0x%x)\n", + __func__, battery->event, battery->event_wait); + } +} + +static bool sec_bat_time_management( + struct sec_battery_info *battery) +{ + unsigned long charging_time; + ktime_t current_time; + struct timespec ts; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + if (battery->charging_start_time == 0) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + if (ts.tv_sec >= battery->charging_start_time) + charging_time = ts.tv_sec - battery->charging_start_time; + else + charging_time = 0xFFFFFFFF - battery->charging_start_time + + ts.tv_sec; + + battery->charging_passed_time = charging_time; + + dev_info(battery->dev, + "%s: Charging Time : %ld secs\n", __func__, + battery->charging_passed_time); + + switch (battery->status) { + case POWER_SUPPLY_STATUS_FULL: + if (battery->charging_mode == SEC_BATTERY_CHARGING_RECHARGING && + (charging_time > + battery->pdata->recharging_total_time)) { + dev_dbg(battery->dev, + "%s: Recharging Timer Expired\n", __func__); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + if (sec_bat_set_charge(battery, false)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", __func__); + return true; + } + + return false; + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (battery->charging_mode == SEC_BATTERY_CHARGING_NORMAL && + (charging_time > + battery->pdata->charging_total_time)) { + dev_dbg(battery->dev, + "%s: Charging Timer Expired\n", __func__); + if (!(battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL)) + battery->status = POWER_SUPPLY_STATUS_FULL; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + if (sec_bat_set_charge(battery, false)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", __func__); + return true; + } + + return false; + } + if (battery->pdata->charging_reset_time) { + if (charging_time > battery->charging_next_time) { + /*reset current in charging status */ + battery->charging_next_time = + battery->charging_passed_time + + (battery->pdata->charging_reset_time * + HZ); + + dev_dbg(battery->dev, + "%s: Reset charging current\n", + __func__); + if (sec_bat_set_charge(battery, true)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", + __func__); + return true; + } + } + } + break; + default: + dev_err(battery->dev, + "%s: Undefine Battery Status\n", __func__); + return true; + } + + return true; +} + +static bool sec_bat_check_fullcharged( + struct sec_battery_info *battery) +{ + int current_adc; + bool ret; + int err; + + ret = false; + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_SOC) { + if (battery->capacity < + battery->pdata->full_condition_soc) { + dev_dbg(battery->dev, + "%s: Not enough SOC (%d%%)\n", + __func__, battery->capacity); + goto not_full_charged; + } + } + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_AVGVCELL) { + if (battery->voltage_avg < + battery->pdata->full_condition_avgvcell) { + dev_dbg(battery->dev, + "%s: Not enough AVGVCELL (%dmV)\n", + __func__, battery->voltage_avg); + goto not_full_charged; + } + } + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_OCV) { + if (battery->voltage_ocv < + battery->pdata->full_condition_ocv) { + dev_dbg(battery->dev, + "%s: Not enough OCV (%dmV)\n", + __func__, battery->voltage_ocv); + goto not_full_charged; + } + } + + switch (battery->pdata->full_check_type) { + case SEC_BATTERY_FULLCHARGED_ADC_DUAL: + if (battery->charging_mode == + SEC_BATTERY_CHARGING_2ND) { + current_adc = + sec_bat_get_adc_value(battery, + SEC_BAT_ADC_CHANNEL_FULL_CHECK); + + dev_dbg(battery->dev, + "%s: Current ADC (%d)\n", + __func__, current_adc); + + if (current_adc < 0) + break; + battery->current_adc = current_adc; + + if (battery->current_adc < + battery->pdata->full_check_adc_2nd) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check 2nd (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + } + case SEC_BATTERY_FULLCHARGED_ADC: + current_adc = + sec_bat_get_adc_value(battery, + SEC_BAT_ADC_CHANNEL_FULL_CHECK); + + dev_dbg(battery->dev, + "%s: Current ADC (%d)\n", + __func__, current_adc); + + if (current_adc < 0) + break; + battery->current_adc = current_adc; + + if (battery->current_adc < + battery->pdata->full_check_adc_1st) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check ADC (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + + case SEC_BATTERY_FULLCHARGED_FG_CURRENT_DUAL: + if (battery->charging_mode == + SEC_BATTERY_CHARGING_2ND) { + if (battery->current_avg < + battery->pdata->charging_current[ + battery->cable_type].full_check_current_2nd) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check Current 2nd (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + } + case SEC_BATTERY_FULLCHARGED_FG_CURRENT: + if (battery->current_avg < + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check Current (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + err = gpio_request( + battery->pdata->chg_gpio_full_check, + "GPIO_CHG_FULL"); + if (err) { + dev_err(battery->dev, + "%s: Error in Request of GPIO\n", __func__); + break; + } + if (!(gpio_get_value_cansleep( + battery->pdata->chg_gpio_full_check) ^ + !battery->pdata->chg_polarity_full_check)) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check GPIO (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + gpio_free(battery->pdata->chg_gpio_full_check); + break; + + case SEC_BATTERY_FULLCHARGED_CHGINT: + break; + + default: + dev_err(battery->dev, + "%s: Invalid Full Check\n", __func__); + break; + } + + if (battery->full_check_cnt > + battery->pdata->full_check_count) { + battery->full_check_cnt = 0; + ret = true; + } + +not_full_charged: + return ret; +} + +static void sec_bat_do_fullcharged( + struct sec_battery_info *battery) +{ + union power_supply_propval value; + + if (((battery->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_ADC_DUAL) || + (battery->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_FG_CURRENT_DUAL)) && + (battery->charging_mode == + SEC_BATTERY_CHARGING_NORMAL)) { + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + } else { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_charge(battery, false); + + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_STATUS, value); + } + + /* platform can NOT get information of battery + * because wakeup time is too short to check uevent + * To make sure that target is wakeup if full-charged, + * activated wake lock in a few seconds + */ + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 10); + battery->status = POWER_SUPPLY_STATUS_FULL; +} + +static bool sec_bat_fullcharged_check( + struct sec_battery_info *battery) +{ + if (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) { + dev_dbg(battery->dev, + "%s: No Need to Check Full-Charged\n", __func__); + return true; + } + + if (sec_bat_check_fullcharged(battery)) + sec_bat_do_fullcharged(battery); + + dev_info(battery->dev, + "%s: Charging Mode : %s\n", __func__, + sec_bat_charging_mode_str[battery->charging_mode]); + + return true; +}; + +static void sec_bat_get_battery_info( + struct sec_battery_info *battery) +{ + union power_supply_propval value; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + + value.intval = SEC_BATTEY_VOLTAGE_AVERAGE; + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg = value.intval; + + value.intval = SEC_BATTEY_VOLTAGE_OCV; + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_ocv = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; + + switch (battery->pdata->thermal_source) { + case SEC_BATTERY_THERMAL_SOURCE_FG: + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + case SEC_BATTERY_THERMAL_SOURCE_CALLBACK: + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP_AMBIENT, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + case SEC_BATTERY_THERMAL_SOURCE_ADC: + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP_AMBIENT, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + default: + break; + } + + dev_info(battery->dev, + "%s: %s, SOC(%d%%)\n", __func__, + battery->present ? "Connected" : "Disconnected", + battery->capacity); + dev_info(battery->dev, + "%s: Vnow(%dmV), Vavg(%dmV), Vocv(%dmV)\n", + __func__, battery->voltage_now, + battery->voltage_avg, battery->voltage_ocv); + dev_info(battery->dev, + "%s: Inow(%dmA), Iavg(%dmA), Iadc(%d)\n", + __func__, battery->current_now, + battery->current_avg, battery->current_adc); + dev_info(battery->dev, + "%s: Tbat(%d.%d), Tamb(%d.%d)\n", + __func__, battery->temperature / 10, + battery->temperature % 10, + battery->temper_amb / 10, + battery->temper_amb % 10); +}; + +static void sec_bat_polling_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of( + work, struct sec_battery_info, polling_work.work); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + dev_dbg(battery->dev, "%s: Activated\n", __func__); +} + +static void sec_bat_program_alarm( + struct sec_battery_info *battery, int seconds) +{ + ktime_t low_interval = ktime_set(seconds, 0); + ktime_t slack = ktime_set(10, 0); + ktime_t next; + + next = ktime_add(battery->last_poll_time, low_interval); + alarm_start_range(&battery->polling_alarm, + next, ktime_add(next, slack)); +} + +static void sec_bat_alarm(struct alarm *alarm) +{ + struct sec_battery_info *battery = container_of(alarm, + struct sec_battery_info, polling_alarm); + + /* In wake up, monitor work will be queued in complete function + * To avoid duplicated queuing of monitor work, + * do NOT queue monitor work in wake up by polling alarm + */ + if (!battery->polling_in_sleep) { + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + dev_dbg(battery->dev, "%s: Activated\n", __func__); + } +} + + +static unsigned int sec_bat_get_polling_time( + struct sec_battery_info *battery) +{ + if (battery->status == + POWER_SUPPLY_STATUS_FULL) + battery->polling_time = + battery->pdata->polling_time[ + POWER_SUPPLY_STATUS_CHARGING]; + else + battery->polling_time = + battery->pdata->polling_time[ + battery->status]; + + battery->polling_short = true; + + switch (battery->status) { + case POWER_SUPPLY_STATUS_CHARGING: + if (battery->polling_in_sleep) + battery->polling_short = false; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + if (battery->polling_in_sleep) + battery->polling_time = + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_SLEEP]; + else + battery->polling_time = + battery->pdata->polling_time[ + battery->status]; + battery->polling_short = false; + break; + case POWER_SUPPLY_STATUS_FULL: + if (battery->polling_in_sleep) { + if (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) + battery->polling_time = + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_SLEEP]; + battery->polling_short = false; + } else { + if (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) + battery->polling_short = false; + } + break; + } + + if (battery->polling_short) + return battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_BASIC]; + else + return battery->polling_time; +} + +static bool sec_bat_is_short_polling( + struct sec_battery_info *battery) +{ + if (battery->polling_short && + ((battery->polling_time / + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_BASIC]) + > battery->polling_count)) + return true; + + return false; +} + +static void sec_bat_update_polling_count( + struct sec_battery_info *battery) +{ + if (sec_bat_is_short_polling(battery)) + battery->polling_count++; + else + battery->polling_count = 1; /* initial value = 1 */ +} + +static void sec_bat_set_polling( + struct sec_battery_info *battery) +{ + unsigned long flags; + unsigned int polling_time_temp; + + polling_time_temp = sec_bat_get_polling_time(battery); + + dev_dbg(battery->dev, + "%s: Status:%s, Sleep:%s, Charging:%s, Short Poll:%s\n", + __func__, sec_bat_status_str[battery->status], + battery->polling_in_sleep ? "Yes" : "No", + (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) ? "No" : "Yes", + battery->polling_short ? "Yes" : "No"); + dev_dbg(battery->dev, + "%s: Polling time %d/%d sec.\n", __func__, + battery->polling_short ? + (polling_time_temp * battery->polling_count) : + polling_time_temp, battery->polling_time); + + /* To sync with log above, + * change polling count after log is displayed + */ + sec_bat_update_polling_count(battery); + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + if (battery->pdata->monitor_initial_count) { + battery->pdata->monitor_initial_count--; + schedule_delayed_work(&battery->polling_work, HZ); + } else + schedule_delayed_work(&battery->polling_work, + polling_time_temp * HZ); + break; + case SEC_BATTERY_MONITOR_ALARM: + battery->last_poll_time = alarm_get_elapsed_realtime(); + local_irq_save(flags); + if (battery->pdata->monitor_initial_count) { + battery->pdata->monitor_initial_count--; + sec_bat_program_alarm(battery, 1); + } else + sec_bat_program_alarm(battery, polling_time_temp); + local_irq_restore(flags); + break; + case SEC_BATTERY_MONITOR_TIMER: + break; + default: + break; + } +} + +static void sec_bat_monitor_work( + struct work_struct *work) +{ + struct sec_battery_info *battery = + container_of(work, struct sec_battery_info, + monitor_work); + + sec_bat_get_battery_info(battery); + + /* 0. test mode */ + if (battery->test_activated) + goto continue_monitor; + + /* 1. battery check */ + if (!sec_bat_battery_cable_check(battery)) + goto continue_monitor; + + /* 2. voltage check */ + if (!sec_bat_voltage_check(battery)) + goto continue_monitor; + + if (sec_bat_is_short_polling(battery)) + goto continue_monitor; + + /* monitor every stage in first time after wakeup */ + if (battery->polling_in_sleep) + battery->polling_in_sleep = false; + + /* 3. time management */ + if (!sec_bat_time_management(battery)) + goto continue_monitor; + + /* 4. temperature check */ + if (!sec_bat_temperature_check(battery)) + goto continue_monitor; + + /* 5. full charging check */ + sec_bat_fullcharged_check(battery); + +continue_monitor: + dev_info(battery->dev, + "%s: Status(%s), Health(%s)\n", __func__, + sec_bat_status_str[battery->status], + sec_bat_health_str[battery->health]); + + battery->test_activated = false; + power_supply_changed(&battery->psy_bat); + + sec_bat_set_polling(battery); + + wake_unlock(&battery->monitor_wake_lock); + + return; +} + +static void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, cable_work); + int cable_type; + + switch (battery->pdata->cable_source_type) { + case SEC_BATTERY_CABLE_SOURCE_CALLBACK: + cable_type = + battery->pdata->check_cable_callback(); + break; + case SEC_BATTERY_CABLE_SOURCE_ADC: + if (gpio_get_value_cansleep( + battery->pdata->bat_gpio_ta_nconnected) ^ + battery->pdata->bat_polarity_ta_nconnected) + cable_type = POWER_SUPPLY_TYPE_BATTERY; + else + cable_type = + sec_bat_get_charger_type_adc(battery); + break; + case SEC_BATTERY_CABLE_SOURCE_EXTERNAL: + dev_dbg(battery->dev, + "%s: Cable Type Defined (%d)\n", + __func__, battery->cable_type); + default: + /* make cable status changed forcefully */ + cable_type = SEC_SIZEOF_POWER_SUPPLY_TYPE; + break; + } + + if (battery->cable_type == cable_type) { + dev_dbg(battery->dev, + "%s: No need to change cable status\n", __func__); + goto end_of_cable_work; + } else { + if (battery->pdata->cable_source_type != + SEC_BATTERY_CABLE_SOURCE_EXTERNAL) { + if (cable_type < POWER_SUPPLY_TYPE_BATTERY || + cable_type >= SEC_SIZEOF_POWER_SUPPLY_TYPE) { + dev_err(battery->dev, + "%s: Invalid cable type\n", __func__); + goto end_of_cable_work; + } else + battery->cable_type = cable_type; + } + dev_dbg(battery->dev, "%s: Cable Changed (%d)\n", + __func__, battery->cable_type); + } + + if (battery->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + battery->health = POWER_SUPPLY_HEALTH_GOOD; + + if (sec_bat_set_charge(battery, false)) + goto end_of_cable_work; + + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 5); + } else { + battery->charging_mode = SEC_BATTERY_CHARGING_NORMAL; + battery->status = POWER_SUPPLY_STATUS_CHARGING; + + if (sec_bat_set_charge(battery, true)) + goto end_of_cable_work; + + /* No need for wakelock in Alarm */ + if (battery->pdata->polling_type != SEC_BATTERY_MONITOR_ALARM) + wake_lock(&battery->vbus_wake_lock); + } + + battery->polling_count = 1; /* initial value = 1 */ + + battery->pdata->check_cable_result_callback(battery->cable_type); + + power_supply_changed(&battery->psy_ac); + power_supply_changed(&battery->psy_usb); + +end_of_cable_work: + wake_unlock(&battery->cable_wake_lock); +} + +ssize_t sec_bat_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + const ptrdiff_t offset = attr - sec_battery_attrs; + int i = 0; + + switch (offset) { + case BATT_RESET_SOC: + break; + case BATT_READ_RAW_SOC: + break; + case BATT_READ_ADJ_SOC: + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + battery->pdata->vendor); + break; + case BATT_VFOCV: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->voltage_ocv); + break; + case BATT_VOL_ADC: + break; + case BATT_VOL_ADC_CAL: + break; + case BATT_VOL_AVER: + break; + case BATT_VOL_ADC_AVER: + break; + case BATT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->temp_adc); + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case BATT_VF_ADC: + break; + + case BATT_LP_CHARGING: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->is_lpm() ? 1 : 0); + break; + case SIOP_ACTIVATED: + break; + case BATT_CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->cable_type); + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->current_adc); + break; + case WC_ADC: + break; + case WC_STATUS: + break; + + case BATT_EVENT_2G_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_2G_CALL) ? 1 : 0); + break; + case BATT_EVENT_3G_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_3G_CALL) ? 1 : 0); + break; + case BATT_EVENT_MUSIC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_MUSIC) ? 1 : 0); + break; + case BATT_EVENT_VIDEO: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_VIDEO) ? 1 : 0); + break; + case BATT_EVENT_BROWSER: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_BROWSER) ? 1 : 0); + break; + case BATT_EVENT_HOTSPOT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_HOTSPOT) ? 1 : 0); + break; + case BATT_EVENT_CAMERA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_CAMERA) ? 1 : 0); + break; + case BATT_EVENT_CAMCORDER: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_CAMCORDER) ? 1 : 0); + break; + case BATT_EVENT_DATA_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_DATA_CALL) ? 1 : 0); + break; + case BATT_EVENT_WIFI: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_WIFI) ? 1 : 0); + break; + case BATT_EVENT_WIBRO: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_WIBRO) ? 1 : 0); + break; + case BATT_EVENT_LTE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_LTE) ? 1 : 0); + break; + case BATT_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->event); + break; + default: + i = -EINVAL; + } + + return i; +} + +ssize_t sec_bat_store_attrs( + struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + const ptrdiff_t offset = attr - sec_battery_attrs; + int ret = 0; + int x = 0; + + switch (offset) { + case BATT_RESET_SOC: + /* Do NOT reset fuel gauge in charging mode */ + if (!battery->pdata->check_cable_callback()) { + union power_supply_propval value; + + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_CAPACITY, value); + + /* update battery info */ + sec_bat_get_battery_info(battery); + } + ret = count; + break; + case BATT_READ_RAW_SOC: + break; + case BATT_READ_ADJ_SOC: + break; + case BATT_TYPE: + break; + case BATT_VFOCV: + break; + case BATT_VOL_ADC: + break; + case BATT_VOL_ADC_CAL: + break; + case BATT_VOL_AVER: + break; + case BATT_VOL_ADC_AVER: + break; + case BATT_TEMP_ADC: + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case BATT_VF_ADC: + break; + + case BATT_LP_CHARGING: + break; + case SIOP_ACTIVATED: + break; + case BATT_CHARGING_SOURCE: + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + break; + case WC_ADC: + break; + case WC_STATUS: + break; + + case BATT_EVENT_2G_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_2G_CALL, x); + ret = count; + } + break; + case BATT_EVENT_3G_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_3G_CALL, x); + ret = count; + } + break; + case BATT_EVENT_MUSIC: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_MUSIC, x); + ret = count; + } + break; + case BATT_EVENT_VIDEO: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_VIDEO, x); + ret = count; + } + break; + case BATT_EVENT_BROWSER: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_BROWSER, x); + ret = count; + } + break; + case BATT_EVENT_HOTSPOT: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_HOTSPOT, x); + ret = count; + } + break; + case BATT_EVENT_CAMERA: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_CAMERA, x); + ret = count; + } + break; + case BATT_EVENT_CAMCORDER: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_CAMCORDER, x); + ret = count; + } + break; + case BATT_EVENT_DATA_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_DATA_CALL, x); + ret = count; + } + break; + case BATT_EVENT_WIFI: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_WIFI, x); + ret = count; + } + break; + case BATT_EVENT_WIBRO: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_WIBRO, x); + ret = count; + } + break; + case BATT_EVENT_LTE: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_LTE, x); + ret = count; + } + break; + 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 create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); +create_attrs_succeed: + return rc; +} + +static int sec_bat_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + + dev_dbg(battery->dev, + "%s: (%d,%d)\n", __func__, psp, val->intval); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if ((battery->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) && + (val->intval == POWER_SUPPLY_STATUS_FULL)) + sec_bat_do_fullcharged(battery); + battery->status = val->intval; + break; + case POWER_SUPPLY_PROP_HEALTH: + battery->health = val->intval; + switch (battery->health) { + /* OVP/UVLO from Interrupt */ + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + sec_bat_ovp_uvlo_result(battery); + break; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + /* cable is attached or detached */ + if (battery->pdata->cable_source_type == + SEC_BATTERY_CABLE_SOURCE_EXTERNAL) { + /* skip cable work if cable is NOT changed */ + if (battery->cable_type != val->intval) + battery->cable_type = val->intval; + else { + dev_dbg(battery->dev, + "%s: Cable is NOT Changed(%d)\n", + __func__, battery->cable_type); + /* Do NOT activate cable work for NOT changed */ + break; + } + } + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, &battery->cable_work); + break; + case POWER_SUPPLY_PROP_CAPACITY: + battery->capacity = val->intval; + power_supply_changed(&battery->psy_bat); + break; + default: + return -EINVAL; + } + + battery->test_activated = true; + return 0; +} + +static int sec_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE) { + switch (battery->cable_type) { + case POWER_SUPPLY_TYPE_USB: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_USB_ACA: + val->intval = + POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + } + val->intval = battery->status; + break; + /* charging mode (differ from power supply) */ + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = battery->charging_mode; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery->health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battery->cable_type; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battery->pdata->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* voltage value should be in uV */ + val->intval = battery->voltage_now * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* voltage value should be in uV */ + val->intval = battery->voltage_avg * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battery->current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = battery->current_avg; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = battery->capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery->temperature; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = battery->temper_amb; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_usb); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the USB charger is connected */ + val->intval = (battery->cable_type == POWER_SUPPLY_TYPE_USB); + + return 0; +} + +static int sec_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_ac); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the AC charger is connected */ + val->intval = (battery->cable_type == POWER_SUPPLY_TYPE_MAINS); + + return 0; +} + +static irqreturn_t sec_bat_irq_thread(int irq, void *irq_data) +{ + struct sec_battery_info *battery = irq_data; + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_INT) { + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, &battery->cable_work); + } + + if (battery->pdata->battery_check_type == + SEC_BATTERY_CHECK_INT) { + battery->present = battery->pdata->check_battery_callback(); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + } + + return IRQ_HANDLED; +} + +static int __devinit sec_battery_probe(struct platform_device *pdev) +{ + sec_battery_platform_data_t *pdata = dev_get_platdata(&pdev->dev); + struct sec_battery_info *battery; + int ret = 0; + int i; + + dev_dbg(&pdev->dev, + "%s: SEC Battery Driver Loading\n", __func__); + + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + platform_set_drvdata(pdev, battery); + + battery->dev = &pdev->dev; + battery->pdata = pdata; + + mutex_init(&battery->adclock); + dev_dbg(battery->dev, "%s: ADC init\n", __func__); + for (i = 0; i < SEC_BAT_ADC_CHANNEL_FULL_CHECK; i++) + adc_init(pdev, pdata, i); + + wake_lock_init(&battery->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-monitor"); + wake_lock_init(&battery->cable_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-cable"); + wake_lock_init(&battery->vbus_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-vbus"); + + /* initialization of battery info */ + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + battery->health = POWER_SUPPLY_HEALTH_GOOD; + battery->present = true; + + battery->polling_count = 1; /* initial value = 1 */ + battery->polling_time = pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_DISCHARGING]; + battery->polling_in_sleep = false; + battery->polling_short = false; + + battery->check_count = 0; + battery->check_adc_count = 0; + battery->check_adc_value = 0; + + battery->charging_start_time = 0; + battery->charging_passed_time = 0; + battery->charging_next_time = 0; + + setup_timer(&battery->event_expired_timer, + sec_bat_event_expired_timer_func, (unsigned long)battery); + + battery->temp_high_threshold = + pdata->temp_high_threshold_normal; + battery->temp_high_recovery = + pdata->temp_high_recovery_normal; + battery->temp_low_recovery = + pdata->temp_low_recovery_normal; + battery->temp_low_threshold = + pdata->temp_low_threshold_normal; + + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->cable_type = POWER_SUPPLY_TYPE_BATTERY; + battery->test_activated = false; + + battery->psy_bat.name = "battery", + battery->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + battery->psy_bat.properties = sec_battery_props, + battery->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props), + battery->psy_bat.get_property = sec_bat_get_property, + battery->psy_bat.set_property = sec_bat_set_property, + battery->psy_usb.name = "usb", + battery->psy_usb.type = POWER_SUPPLY_TYPE_USB, + battery->psy_usb.supplied_to = supply_list, + battery->psy_usb.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_usb.properties = sec_power_props, + battery->psy_usb.num_properties = ARRAY_SIZE(sec_power_props), + battery->psy_usb.get_property = sec_usb_get_property, + battery->psy_ac.name = "ac", + battery->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + battery->psy_ac.supplied_to = supply_list, + battery->psy_ac.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_ac.properties = sec_power_props, + battery->psy_ac.num_properties = ARRAY_SIZE(sec_power_props), + battery->psy_ac.get_property = sec_ac_get_property; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &battery->psy_bat); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_bat\n", __func__); + goto err_wake_lock; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_usb); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_usb\n", __func__); + goto err_supply_unreg_bat; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_ac); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_ac\n", __func__); + goto err_supply_unreg_usb; + } + + if (!battery->pdata->bat_gpio_init()) { + dev_err(battery->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_supply_unreg_ac; + } + + /* create work queue */ + battery->monitor_wqueue = + create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!battery->monitor_wqueue) { + dev_err(battery->dev, + "%s: Fail to Create Workqueue\n", __func__); + goto err_supply_unreg_ac; + } + INIT_WORK(&battery->monitor_work, sec_bat_monitor_work); + INIT_WORK(&battery->cable_work, sec_bat_cable_work); + + if (battery->pdata->bat_irq) { + ret = request_threaded_irq(battery->pdata->bat_irq, + NULL, sec_bat_irq_thread, + battery->pdata->bat_irq_attr, + "battery-irq", battery); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + return ret; + } + + ret = enable_irq_wake(battery->pdata->bat_irq); + if (ret < 0) + dev_err(battery->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + ret = sec_bat_create_attrs(battery->psy_bat.dev); + if (ret) { + dev_err(battery->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_supply_unreg_ac; + } + + switch (pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + INIT_DELAYED_WORK_DEFERRABLE(&battery->polling_work, + sec_bat_polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + battery->last_poll_time = alarm_get_elapsed_realtime(); + alarm_init(&battery->polling_alarm, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + sec_bat_alarm); + break; + default: + break; + } + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + + pdata->initial_check(); + + dev_dbg(battery->dev, + "%s: SEC Battery Driver Loaded\n", __func__); + return 0; + +err_supply_unreg_ac: + power_supply_unregister(&battery->psy_ac); +err_supply_unreg_usb: + power_supply_unregister(&battery->psy_usb); +err_supply_unreg_bat: + power_supply_unregister(&battery->psy_bat); +err_wake_lock: + wake_lock_destroy(&battery->monitor_wake_lock); + wake_lock_destroy(&battery->cable_wake_lock); + wake_lock_destroy(&battery->vbus_wake_lock); + mutex_destroy(&battery->adclock); + kfree(battery); + + return ret; +} + +static int __devexit sec_battery_remove(struct platform_device *pdev) +{ + struct sec_battery_info *battery = platform_get_drvdata(pdev); + int i; + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + cancel_delayed_work(&battery->polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + alarm_cancel(&battery->polling_alarm); + break; + default: + break; + } + + flush_workqueue(battery->monitor_wqueue); + destroy_workqueue(battery->monitor_wqueue); + wake_lock_destroy(&battery->monitor_wake_lock); + wake_lock_destroy(&battery->cable_wake_lock); + wake_lock_destroy(&battery->vbus_wake_lock); + + mutex_destroy(&battery->adclock); + for (i = 0; i < SEC_BAT_ADC_CHANNEL_FULL_CHECK; i++) + adc_exit(battery->pdata, i); + + power_supply_unregister(&battery->psy_ac); + power_supply_unregister(&battery->psy_usb); + power_supply_unregister(&battery->psy_bat); + + kfree(battery); + + return 0; +} + +static int sec_battery_prepare(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + cancel_work_sync(&battery->monitor_work); + + battery->polling_in_sleep = true; + + sec_bat_set_polling(battery); + + if (battery->pdata->polling_type == + SEC_BATTERY_MONITOR_WORKQUEUE) + cancel_delayed_work(&battery->polling_work); + + return 0; +} + +static int sec_battery_suspend(struct device *dev) +{ + return 0; +} + +static int sec_battery_resume(struct device *dev) +{ + return 0; +} + +static void sec_battery_complete(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->monitor_work); + + return; +} + +static void sec_battery_shutdown(struct device *dev) +{ +} + +static const struct dev_pm_ops sec_battery_pm_ops = { + .prepare = sec_battery_prepare, + .suspend = sec_battery_suspend, + .resume = sec_battery_resume, + .complete = sec_battery_complete, +}; + +static struct platform_driver sec_battery_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_battery_pm_ops, + .shutdown = sec_battery_shutdown, + }, + .probe = sec_battery_probe, + .remove = __devexit_p(sec_battery_remove), +}; + +static int __init sec_battery_init(void) +{ + return platform_driver_register(&sec_battery_driver); +} + +static void __exit sec_battery_exit(void) +{ + platform_driver_unregister(&sec_battery_driver); +} + +late_initcall(sec_battery_init); +module_exit(sec_battery_exit); + +MODULE_DESCRIPTION("Samsung Battery Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/sec_charger.c b/drivers/battery/sec_charger.c new file mode 100644 index 0000000..9947027 --- /dev/null +++ b/drivers/battery/sec_charger.c @@ -0,0 +1,395 @@ +/* + * sec_charger.c + * Samsung Mobile Charger Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG + +#include <linux/battery/sec_charger.h> + +static int sec_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = charger->charging_current ? 1 : 0; + break; + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!sec_hal_chg_get_property(charger->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + + switch (psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) + charger->is_charging = false; + else + charger->is_charging = true; + + /* current setting */ + charger->charging_current = + charger->pdata->charging_current[ + val->intval].fast_charging_current; + + if (!sec_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + charger->charging_current = val->intval; + + if (!sec_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void sec_chg_isr_work(struct work_struct *work) +{ + struct sec_charger_info *charger = + container_of(work, struct sec_charger_info, isr_work.work); + union power_supply_propval val; + + dev_info(&charger->client->dev, + "%s: Charger Interrupt\n", __func__); + + if (charger->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) { + if (!sec_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_STATUS, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Discharging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_NOT_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but NOT Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_FULL: + dev_info(&charger->client->dev, + "%s: Interrupted by Full\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_STATUS, val); + break; + + case POWER_SUPPLY_STATUS_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Status\n", __func__); + break; + } + } + + if (charger->pdata->ovp_uvlo_check_type == + SEC_BATTERY_OVP_UVLO_CHGINT) { + if (!sec_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_HEALTH, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + dev_err(&charger->client->dev, + "%s: Interrupted but Hot/Cold\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_DEAD: + dev_err(&charger->client->dev, + "%s: Interrupted but Dead\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + dev_info(&charger->client->dev, + "%s: Interrupted by OVP/UVLO\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_HEALTH, val); + break; + + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + dev_err(&charger->client->dev, + "%s: Interrupted but Unspec\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_GOOD: + dev_err(&charger->client->dev, + "%s: Interrupted but Good\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Health\n", __func__); + break; + } + } +} + + +static irqreturn_t sec_chg_irq_thread(int irq, void *irq_data) +{ + struct sec_charger_info *charger = irq_data; + + if ((charger->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) || + (charger->pdata->ovp_uvlo_check_type == + SEC_BATTERY_OVP_UVLO_CHGINT)) + schedule_delayed_work(&charger->isr_work, 0); + + return IRQ_HANDLED; +} + +static int sec_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_charger_attrs); i++) { + rc = device_create_file(dev, &sec_charger_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &sec_charger_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t sec_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_charger_attrs; + int i = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + case CHG_REGS: + i = sec_hal_chg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - sec_charger_attrs; + int ret = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + ret = sec_hal_chg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int __devinit sec_charger_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = + to_i2c_adapter(client->dev.parent); + struct sec_charger_info *charger; + int ret = 0; + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->client = client; + charger->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, charger); + + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_BATTERY; + charger->psy_chg.get_property = sec_chg_get_property; + charger->psy_chg.set_property = sec_chg_set_property; + charger->psy_chg.properties = sec_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); + + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + + if (!sec_hal_chg_init(charger->client)) { + dev_err(&client->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &charger->psy_chg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + if (charger->pdata->chg_irq) { + ret = request_threaded_irq(charger->pdata->chg_irq, + NULL, sec_chg_irq_thread, + charger->pdata->chg_irq_attr, + "charger-irq", charger); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + return ret; + } + + if (charger->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) { + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + dev_err(&client->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + INIT_DELAYED_WORK_DEFERRABLE( + &charger->isr_work, sec_chg_isr_work); + } + + ret = sec_chg_create_attrs(charger->psy_chg.dev); + if (ret) { + dev_err(&client->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_free; + } + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loaded\n", __func__); + return 0; + +err_free: + kfree(charger); + + return ret; +} + +static int __devexit sec_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int sec_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + if (!sec_hal_chg_suspend(client)) + dev_err(&client->dev, + "%s: Failed to Suspend Charger\n", __func__); + + return 0; +} + +static int sec_charger_resume(struct i2c_client *client) +{ + if (!sec_hal_chg_resume(client)) + dev_err(&client->dev, + "%s: Failed to Resume Charger\n", __func__); + + return 0; +} + +static void sec_charger_shutdown(struct i2c_client *client) +{ +} + +static const struct i2c_device_id sec_charger_id[] = { + {"sec-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_charger_id); + +static struct i2c_driver sec_charger_driver = { + .driver = { + .name = "sec-charger", + }, + .probe = sec_charger_probe, + .remove = __devexit_p(sec_charger_remove), + .suspend = sec_charger_suspend, + .resume = sec_charger_resume, + .shutdown = sec_charger_shutdown, + .id_table = sec_charger_id, +}; + +static int __init sec_charger_init(void) +{ + return i2c_add_driver(&sec_charger_driver); +} + +static void __exit sec_charger_exit(void) +{ + i2c_del_driver(&sec_charger_driver); +} + +module_init(sec_charger_init); +module_exit(sec_charger_exit); + +MODULE_DESCRIPTION("Samsung Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/sec_fuelgauge.c b/drivers/battery/sec_fuelgauge.c new file mode 100644 index 0000000..677ee78 --- /dev/null +++ b/drivers/battery/sec_fuelgauge.c @@ -0,0 +1,423 @@ +/* + * sec_fuelgauge.c + * Samsung Mobile Fuel Gauge Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG + +#include <linux/battery/sec_fuelgauge.h> + +/* capacity is 0.1% unit */ +static void sec_fg_get_scaled_capacity( + struct sec_fuelgauge_info *fuelgauge, + union power_supply_propval *val) +{ + val->intval = (val->intval < fuelgauge->pdata->capacity_min) ? + 0 : ((val->intval - fuelgauge->pdata->capacity_min) * 1000 / + (fuelgauge->pdata->capacity_max - + fuelgauge->pdata->capacity_min)); + + dev_dbg(&fuelgauge->client->dev, + "%s: scaled capacity (%d.%d)\n", + __func__, val->intval/10, val->intval%10); +} + +/* capacity is integer */ +static void sec_fg_get_atomic_capacity( + struct sec_fuelgauge_info *fuelgauge, + union power_supply_propval *val) +{ + if (fuelgauge->capacity_old < val->intval) + val->intval = fuelgauge->capacity_old + 1; + else if (fuelgauge->capacity_old > val->intval) + val->intval = fuelgauge->capacity_old - 1; + + /* updated old capacity */ + fuelgauge->capacity_old = val->intval; +} + +static int sec_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_fuelgauge_info *fuelgauge = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + if (!sec_hal_fg_get_property(fuelgauge->client, psp, val)) + return -EINVAL; + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_SCALE) + sec_fg_get_scaled_capacity(fuelgauge, val); + + /* capacity should be between 0% and 100% + * (0.1% degree) + */ + if (val->intval > 1000) + val->intval = 1000; + if (val->intval < 0) + val->intval = 0; + + /* get only integer part */ + val->intval /= 10; + + /* (Only for atomic capacity) + * In initial time, capacity_old is 0. + * and in resume from sleep, + * capacity_old is too different from actual soc. + * should update capacity_old + * by val->intval in booting or resume. + */ + if (fuelgauge->initial_update_of_soc) { + /* updated old capacity */ + fuelgauge->capacity_old = val->intval; + fuelgauge->initial_update_of_soc = false; + break; + } + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) + sec_fg_get_atomic_capacity(fuelgauge, val); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_fg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_fuelgauge_info *fuelgauge = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (val->intval == POWER_SUPPLY_STATUS_FULL) + sec_hal_fg_full_charged(fuelgauge->client); + break; + case POWER_SUPPLY_PROP_ONLINE: + fuelgauge->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) + fuelgauge->is_charging = false; + else + fuelgauge->is_charging = true; + case POWER_SUPPLY_PROP_CAPACITY: + if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) { + if (!sec_hal_fg_reset(fuelgauge->client)) + return -EINVAL; + else + break; + } + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + if (!sec_hal_fg_set_property(fuelgauge->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void sec_fg_isr_work(struct work_struct *work) +{ + struct sec_fuelgauge_info *fuelgauge = + container_of(work, struct sec_fuelgauge_info, isr_work.work); + bool is_fuel_alerted_now; + + is_fuel_alerted_now = + sec_hal_fg_is_fuelalerted(fuelgauge->client); + + dev_info(&fuelgauge->client->dev, + "%s: Fuel-alert %salerted!\n", + __func__, is_fuel_alerted_now ? "" : "NOT "); + + if (is_fuel_alerted_now) + wake_lock(&fuelgauge->fuel_alert_wake_lock); + else + wake_unlock(&fuelgauge->fuel_alert_wake_lock); + + if (!(fuelgauge->pdata->repeated_fuelalert) && + (fuelgauge->is_fuel_alerted == is_fuel_alerted_now)) { + dev_dbg(&fuelgauge->client->dev, + "%s: Fuel-alert Repeated (%d)\n", + __func__, fuelgauge->is_fuel_alerted); + return; + } + + /* process for fuel gauge chip */ + sec_hal_fg_fuelalert_process(fuelgauge, is_fuel_alerted_now); + + /* process for others */ + fuelgauge->pdata->fuelalert_process(is_fuel_alerted_now); + + fuelgauge->is_fuel_alerted = is_fuel_alerted_now; +} + +static irqreturn_t sec_fg_irq_thread(int irq, void *irq_data) +{ + struct sec_fuelgauge_info *fuelgauge = irq_data; + + if (fuelgauge->pdata->fuel_alert_soc >= 0) + schedule_delayed_work(&fuelgauge->isr_work, 0); + + return IRQ_HANDLED; +} + +static int sec_fg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_fg_attrs); i++) { + rc = device_create_file(dev, &sec_fg_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &sec_fg_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t sec_fg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_fg_attrs; + int i = 0; + + switch (offset) { + case FG_REG: + case FG_DATA: + case FG_REGS: + i = sec_hal_fg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_fg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - sec_fg_attrs; + int ret = 0; + + switch (offset) { + case FG_REG: + case FG_DATA: + ret = sec_hal_fg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int __devinit sec_fuelgauge_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct sec_fuelgauge_info *fuelgauge; + int ret = 0; + + dev_dbg(&client->dev, + "%s: SEC Fuelgauge Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL); + if (!fuelgauge) + return -ENOMEM; + + mutex_init(&fuelgauge->fg_lock); + + fuelgauge->client = client; + fuelgauge->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, fuelgauge); + + fuelgauge->psy_fg.name = "sec-fuelgauge"; + fuelgauge->psy_fg.type = POWER_SUPPLY_TYPE_BATTERY; + fuelgauge->psy_fg.get_property = sec_fg_get_property; + fuelgauge->psy_fg.set_property = sec_fg_set_property; + fuelgauge->psy_fg.properties = sec_fuelgauge_props; + fuelgauge->psy_fg.num_properties = + ARRAY_SIZE(sec_fuelgauge_props); + + if (!fuelgauge->pdata->fg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + + if (!sec_hal_fg_init(fuelgauge->client)) { + dev_err(&client->dev, + "%s: Failed to Initialize Fuelgauge\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &fuelgauge->psy_fg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_fg\n", __func__); + goto err_free; + } + + if (fuelgauge->pdata->fg_irq) { + ret = request_threaded_irq(fuelgauge->pdata->fg_irq, + NULL, sec_fg_irq_thread, + fuelgauge->pdata->fg_irq_attr, + "fuelgauge-irq", fuelgauge); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + return ret; + } + + ret = enable_irq_wake(fuelgauge->pdata->fg_irq); + if (ret < 0) + dev_err(&client->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + + INIT_DELAYED_WORK_DEFERRABLE( + &fuelgauge->isr_work, sec_fg_isr_work); + } + + fuelgauge->is_fuel_alerted = false; + if (fuelgauge->pdata->fuel_alert_soc >= 0) { + if (sec_hal_fg_fuelalert_init(fuelgauge->client, + fuelgauge->pdata->fuel_alert_soc)) + wake_lock_init(&fuelgauge->fuel_alert_wake_lock, + WAKE_LOCK_SUSPEND, "fuel_alerted"); + else { + dev_err(&client->dev, + "%s: Failed to Initialize Fuel-alert\n", + __func__); + goto err_irq; + } + } + + fuelgauge->initial_update_of_soc = true; + + ret = sec_fg_create_attrs(fuelgauge->psy_fg.dev); + if (ret) { + dev_err(&client->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_irq; + } + + dev_dbg(&client->dev, + "%s: SEC Fuelgauge Driver Loaded\n", __func__); + return 0; + +err_irq: + wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock); +err_free: + kfree(fuelgauge); + + return ret; +} + +static int __devexit sec_fuelgauge_remove( + struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + if (fuelgauge->pdata->fuel_alert_soc >= 0) + wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock); + + return 0; +} + +static int sec_fuelgauge_suspend( + struct i2c_client *client, pm_message_t state) +{ + if (!sec_hal_fg_suspend(client)) + dev_err(&client->dev, + "%s: Failed to Suspend Fuelgauge\n", __func__); + + return 0; +} + +static int sec_fuelgauge_resume(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + if (!sec_hal_fg_resume(client)) + dev_err(&client->dev, + "%s: Failed to Resume Fuelgauge\n", __func__); + + fuelgauge->initial_update_of_soc = true; + + return 0; +} + +static void sec_fuelgauge_shutdown(struct i2c_client *client) +{ +} + +static const struct i2c_device_id sec_fuelgauge_id[] = { + {"sec-fuelgauge", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_fuelgauge_id); + +static struct i2c_driver sec_fuelgauge_driver = { + .driver = { + .name = "sec-fuelgauge", + }, + .probe = sec_fuelgauge_probe, + .remove = __devexit_p(sec_fuelgauge_remove), + .suspend = sec_fuelgauge_suspend, + .resume = sec_fuelgauge_resume, + .shutdown = sec_fuelgauge_shutdown, + .id_table = sec_fuelgauge_id, +}; + +static int __init sec_fuelgauge_init(void) +{ + return i2c_add_driver(&sec_fuelgauge_driver); +} + +static void __exit sec_fuelgauge_exit(void) +{ + i2c_del_driver(&sec_fuelgauge_driver); +} + +module_init(sec_fuelgauge_init); +module_exit(sec_fuelgauge_exit); + +MODULE_DESCRIPTION("Samsung Fuel Gauge Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |