aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/battery
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/battery
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_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/Kconfig168
-rw-r--r--drivers/battery/Makefile30
-rw-r--r--drivers/battery/battery-factory.c398
-rw-r--r--drivers/battery/battery-factory.h39
-rw-r--r--drivers/battery/bq24190_charger.c740
-rw-r--r--drivers/battery/max17042_fuelgauge.c2436
-rw-r--r--drivers/battery/max17047_fuelgauge.c1210
-rw-r--r--drivers/battery/max17050_fuelgauge.c2192
-rw-r--r--drivers/battery/max77693_charger.c1644
-rw-r--r--drivers/battery/samsung_battery.c1991
-rw-r--r--drivers/battery/samsung_battery_s2plus.c1273
-rw-r--r--drivers/battery/sec_battery.c2199
-rw-r--r--drivers/battery/sec_charger.c395
-rw-r--r--drivers/battery/sec_fuelgauge.c423
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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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,
+ &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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");