aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power
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/power
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/power')
-rw-r--r--drivers/power/Kconfig121
-rw-r--r--drivers/power/Makefile22
-rw-r--r--drivers/power/charger-manager.c1662
-rw-r--r--drivers/power/max17042_battery.c5
-rw-r--r--drivers/power/max17042_fuelgauge_px.c1622
-rw-r--r--drivers/power/max17042_fuelgauge_u1.c1022
-rw-r--r--drivers/power/max17042_fuelgauge_u1_kor.c855
-rw-r--r--drivers/power/max8922_charger_s2plus.c320
-rw-r--r--drivers/power/max8922_charger_u1.c314
-rw-r--r--drivers/power/max8997_charger.c216
-rw-r--r--drivers/power/max8997_charger_px.c485
-rw-r--r--drivers/power/max8997_charger_u1.c478
-rw-r--r--drivers/power/pda_power.c71
-rw-r--r--drivers/power/power_supply_core.c30
-rw-r--r--drivers/power/s3c_adc_battery.c16
-rw-r--r--drivers/power/samsung_fake_battery.c546
-rw-r--r--drivers/power/sec_battery_px.c2148
-rw-r--r--drivers/power/sec_battery_u1.c3442
-rw-r--r--drivers/power/sec_battery_u1_kor.c2299
-rw-r--r--drivers/power/smb136_charger.c465
-rw-r--r--drivers/power/smb136_charger_q1.c531
-rw-r--r--drivers/power/smb328_charger.c1046
-rwxr-xr-xdrivers/power/smb347_charger.c497
23 files changed, 18188 insertions, 25 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index e57b50b..9474894 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -177,7 +177,7 @@ config BATTERY_S3C_ADC
tristate "Battery driver for Samsung ADC based monitoring"
depends on S3C_ADC
help
- Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery
+ Say Y to enable support for batteries with samsung chip.
config CHARGER_PCF50633
tristate "NXP PCF50633 MBC"
@@ -235,4 +235,123 @@ config CHARGER_GPIO
This driver can be build as a module. If so, the module will be
called gpio-charger.
+config BATTERY_SAMSUNG
+ tristate "Fake battery driver for samsung smdk boards"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y to enable support for batteries with samsung chip.
+
+config BATTERY_SAMSUNG_S2PLUS
+ tristate "Fake battery driver for samsung s2plus boards"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y to enable support for batteries with s2plus board.
+
+config CHARGER_MAX8997
+ tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
+ depends on MFD_MAX8997 && REGULATOR_MAX8997
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX8997/LP3974 PMICs.
+
+config CHARGER_MAX8997_U1
+ tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver for Samsung Galaxy S2 ICS upgrade"
+ depends on MFD_MAX8997 && REGULATOR_MAX8997
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX8997/LP3974 PMICs.
+
+config CHARGER_MAX8997_PX
+ tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver for Samsung Galaxy Tab ICS upgrade"
+ depends on MFD_MAX8997 && REGULATOR_MAX8997
+
+config BATTERY_SEC_U1
+ tristate "Battery driver for Samsung Galaxy S2"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y to enable support for batteries with samsung chip.
+
+config BATTERY_SEC_PX
+ tristate "Battery driver for Samsung Galaxy Tab"
+ depends on PLAT_SAMSUNG
+
+config CHARGER_MAX8922_U1
+ tristate "MAX8922 battery charger support for Samsung Galaxy S2 ICS upgrade"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y here to enable support for the battery charger in the Maxim
+ MAX8922 Charger IC.
+
+config CHARGER_MAX8922_S2PLUS
+ tristate "MAX8922 battery charger support for Samsung Galaxy S2 ICS upgrade"
+ depends on PLAT_SAMSUNG
+ default n
+ help
+ Say Y here to enable support for the battery charger in the Maxim
+ MAX8922 Charger IC.
+
+config BATTERY_MAX17042_FUELGAUGE_U1
+ tristate "Maxim MAX17042/8997/8966 Fuel Gauge for Samsung Galaxy S2 ICS upgrade"
+ 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. MAX8997 and MAX8966 are
+ multi-function devices that include fuel gauages that are compatible
+ with MAX17042.
+
+config BATTERY_MAX17042_FUELGAUGE_PX
+ tristate "Maxim MAX17042/8997/8966 Fuel Gauge for Samsung Galaxy Tab ICS upgrade"
+ 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. MAX8997 and MAX8966 are
+ multi-function devices that include fuel gauages that are compatible
+ with MAX17042.
+
+config SMB136_CHARGER
+ tristate "SMB136 battery charger support"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y here to enable support for the battery charger in the SUMMIT
+ SMB136 Charger IC.
+
+config SMB136_CHARGER_Q1
+ tristate "SMB136 battery charger support Q1 HWREV01"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y here to enable support for the battery charger in the SUMMIT
+ SMB136 Charger IC.
+ Support Q1 HWREV01.
+
+config SMB328_CHARGER
+ tristate "SMB328 battery charger support"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y here to enable support for the battery charger in the SUMMIT
+ SMB328 Charger IC.
+
+config SMB347_CHARGER
+ tristate "SMB347 battery charger support"
+ depends on PLAT_SAMSUNG
+ help
+ Say Y here to enable support for the battery charger in the SUMMIT
+ SMB328 Charger IC.
+
+config CHARGER_MANAGER
+ tristate "Battery charger manager for multiple chargers"
+ help
+ Say Y to enable charger-manager support, which allows multiple
+ chargers attached to a battery and multiple batteries attached to a
+ system. The charger-manager also can monitor charging status in
+ runtime and in suspend-to-RAM by waking up the system periodically
+ with help of pm-loop support.
+
+config SAMSUNG_LPM_MODE
+ bool "Off charging mode support in sec battery driver"
+ depends on BATTERY_SEC_PX
+ help
+ Say Y to include support for samsungs off charging support
+
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 009a90f..9be94fd 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -36,3 +36,25 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
+obj-$(CONFIG_BATTERY_SAMSUNG) += samsung_fake_battery.o
+obj-$(CONFIG_BATTERY_SAMSUNG_S2PLUS) += samsung_fake_battery.o
+obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
+obj-$(CONFIG_CHARGER_MAX8997_U1) += max8997_charger_u1.o
+obj-$(CONFIG_CHARGER_MAX8922_U1) += max8922_charger_u1.o
+obj-$(CONFIG_CHARGER_MAX8922_S2PLUS) += max8922_charger_s2plus.o
+obj-$(CONFIG_CHARGER_MAX8997_PX) += max8997_charger_px.o
+ifeq ($(CONFIG_TARGET_LOCALE_KOR),y)
+obj-$(CONFIG_BATTERY_SEC_U1) += sec_battery_u1_kor.o
+obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE_U1) += max17042_fuelgauge_u1_kor.o
+else
+obj-$(CONFIG_BATTERY_SEC_U1) += sec_battery_u1.o
+obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE_U1) += max17042_fuelgauge_u1.o
+endif
+obj-$(CONFIG_BATTERY_SEC_PX) += sec_battery_px.o
+obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE_PX) += max17042_fuelgauge_px.o
+obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_SMB136_CHARGER) += smb136_charger.o
+obj-$(CONFIG_SMB136_CHARGER_Q1) += smb136_charger_q1.o
+obj-$(CONFIG_SMB328_CHARGER) += smb328_charger.o
+obj-$(CONFIG_SMB347_CHARGER) += smb347_charger.o
+
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
new file mode 100644
index 0000000..5dfbf60
--- /dev/null
+++ b/drivers/power/charger-manager.c
@@ -0,0 +1,1662 @@
+/* linux/drivers/power/charger-manager.c
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * Samsung SoC based charger control. This driver enables to control
+ * charger during suspend-to-mem.
+ *
+ * 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/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power/charger-manager.h>
+#include <linux/regulator/consumer.h>
+#include <linux/jack.h>
+
+static const char * const default_event_names[] = {
+ [CM_EVENT_UNDESCRIBED] = "Undescribed",
+ [CM_EVENT_BATT_FULL] = "Battery Full",
+ [CM_EVENT_BATT_IN] = "Battery Inserted",
+ [CM_EVENT_BATT_OUT] = "Battery Pulled Out",
+ [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach",
+ [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop",
+ [CM_EVENT_OTHERS] = "Other battery events"
+};
+
+/*
+ * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
+ * delayed works so that we can run delayed works with CM_JIFFIES_SMALL
+ * without any delays.
+ */
+#define CM_JIFFIES_SMALL (2)
+
+/* If y is valid (> 0) and smaller than x, do x = y */
+#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x))
+
+/*
+ * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking
+ * rtc alarm. It should be 2 or larger
+ */
+#define CM_RTC_SMALL (2)
+
+#define UEVENT_BUF_SIZE 32
+
+LIST_HEAD(cm_list);
+static DEFINE_MUTEX(cm_list_mtx);
+
+static int charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+static void fullbatt_handler(struct charger_manager *cm);
+
+/* About in-suspend (suspend-again) monitoring */
+static struct rtc_device *rtc_dev;
+static struct rtc_wkalrm rtc_wkalarm_save; /* Backup RTC alarm */
+static unsigned long rtc_wkalarm_save_; /* 0 if not available */
+static bool cm_suspended;
+static bool cm_rtc_set;
+static unsigned long cm_suspend_duration_ms;
+
+/* About normal (not suspended) monitoring */
+static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */
+static unsigned long next_polling; /* Next appointed polling time */
+static struct workqueue_struct *cm_wq; /* init at driver add */
+static struct delayed_work cm_monitor_work; /* init at driver add */
+
+/* Global charger-manager description */
+static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
+
+static int is_full(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ int ret = 0;
+
+ ret = charger_get_property(&cm->charger_psy,
+ POWER_SUPPLY_PROP_STATUS, &val);
+
+ if (!ret && val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+ ret = charger_get_property(&cm->charger_psy,
+ POWER_SUPPLY_PROP_CHARGE_FULL, &val);
+ if (!ret && val.intval) {
+ printk(KERN_DEBUG"[CM %s:%d fully Charged.\n",
+ __func__, __LINE__);
+ fullbatt_handler(cm);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * is_batt_present - See if the battery presents in place.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_batt_present(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ bool present = false;
+ int i, ret;
+
+ switch (cm->desc->battery_present) {
+ case CM_ASSUME_ALWAYS_TRUE:
+ present = true;
+ break;
+ case CM_ASSUME_ALWAYS_FALSE:
+ present = false;
+ break;
+ case CM_FUEL_GAUGE:
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ if (ret == 0 && val.intval)
+ present = true;
+ break;
+ case CM_CHARGER_STAT:
+ val.intval = 0;
+ /* If one of them thinks it exists, it exists. */
+ for (i = 0; cm->charger_stat[i]; i++) {
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ if (ret == 0 && val.intval) {
+ present = true;
+ break;
+ }
+ }
+ break;
+ }
+
+ return present;
+}
+
+/**
+ * is_ext_pwr_online - See if an external power source is attached to charge
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if at least one of the chargers of the battery has an external
+ * power source attached to charge the battery regardless of whether it is
+ * actually charging or not.
+ */
+static bool is_ext_pwr_online(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ bool online = false;
+ int i, ret;
+
+ /* If at least one of them has one, it's yes. */
+ for (i = 0; cm->charger_stat[i]; i++) {
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_ONLINE, &val);
+ if (ret == 0 && val.intval) {
+ online = true;
+ break;
+ }
+ }
+
+ return online;
+}
+
+/**
+ * get_batt_uV - Get the voltage level of the battery
+ * @cm: the Charger Manager representing the battery.
+ * @uV: the voltage level returned.
+ *
+ * Returns 0 if there is no error.
+ * Returns a negative value on error.
+ */
+static int get_batt_uV(struct charger_manager *cm, int *uV)
+{
+ union power_supply_propval val;
+ int ret;
+
+ if (cm->fuel_gauge)
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
+ else
+ return -ENODEV;
+
+ if (ret)
+ return ret;
+
+ *uV = (val.intval > cm->desc->fullbatt_uV) ?
+ cm->desc->fullbatt_uV : val.intval;
+ return 0;
+}
+
+/**
+ * is_charging - Returns true if the battery is being charged.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_charging(struct charger_manager *cm)
+{
+ int i, ret;
+ bool charging = false;
+ union power_supply_propval val;
+ static bool present_warned;
+ bool warned = false;
+
+ /* If there is no battery, it cannot be charged */
+ if (!is_batt_present(cm))
+ return false;
+
+ /* If at least one of the charger is charging, return yes */
+ for (i = 0; cm->charger_stat[i]; i++) {
+ /* 1. The charger sholuld not be DISABLED */
+ if (cm->emergency_stop)
+ continue;
+ if (cm->user_prohibit)
+ continue;
+ if (!cm->charger_enabled)
+ continue;
+
+ /* 2. The charger should be online (ext-power) */
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_ONLINE, &val);
+ if (ret) {
+ dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+ if (val.intval == 0)
+ continue;
+
+ /* 3. The charger should have the battery connected */
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ /* PRESENT is optional. assume it's "yes" */
+ if (ret && !present_warned) {
+ dev_warn(cm->dev, "Cannot read PRESENT value from %s"
+ ".\n", cm->desc->psy_charger_stat[i]);
+ warned = true;
+ } else if (!ret && val.intval == 0)
+ continue;
+
+ /*
+ * 4. The charger should not be FULL, DISCHARGING,
+ * or NOT_CHARGING.
+ */
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_STATUS, &val);
+ if (ret) {
+ dev_warn(cm->dev, "Cannot read STATUS value from %s.\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+ if (val.intval == POWER_SUPPLY_STATUS_FULL ||
+ val.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
+ val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
+ continue;
+
+ /* Then, this is charging. */
+ charging = true;
+ break;
+ }
+
+ if (warned)
+ present_warned = true;
+
+ return charging;
+}
+
+/**
+ * try_charger_enable - Enable/Disable chargers altogether
+ * @cm: the Charger Manager representing the battery.
+ * @enable: true: enable / false: force_disable
+ *
+ * Note that Charger Manager keeps the charger enabled regardless whether
+ * the charger is charging or not (because battery is full or no external
+ * power source exists) except when CM needs to disable chargers forcibly
+ * bacause of emergency causes; when the battery is overheated of too cold.
+ */
+static int try_charger_enable(struct charger_manager *cm, bool enable)
+{
+ int i;
+ int err = 0;
+ struct charger_desc *desc = cm->desc;
+
+ printk(KERN_INFO"[CM] %s:%d status:%d", __func__, __LINE__, enable);
+
+ /* Ignore if it's redundent command */
+ if (enable && cm->charger_enabled)
+ return 0;
+ if (!enable && !cm->charger_enabled)
+ return 0;
+
+ if (enable) {
+ if (cm->emergency_stop || cm->user_prohibit)
+ return -EAGAIN; /* Do it again later */
+ for (i = 0 ; i < desc->num_charger_regulators ; i++)
+ regulator_enable(desc->charger_regulators[i].consumer);
+ } else {
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ regulator_force_disable(
+ desc->charger_regulators[i].consumer);
+ }
+ }
+
+ if (!err)
+ cm->charger_enabled = enable;
+
+ return err;
+}
+
+/**
+ * try_charger_restart - Restart charging.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Restart charging by turning off and on the charger.
+ */
+static int try_charger_restart(struct charger_manager *cm)
+{
+ int err;
+
+ if (cm->emergency_stop)
+ return -EAGAIN;
+ if (cm->user_prohibit)
+ return -EAGAIN;
+
+ err = try_charger_enable(cm, false);
+ if (err)
+ return err;
+
+ return try_charger_enable(cm, true);
+}
+
+/**
+ * uevent_notify - Let users know something has changed.
+ * @cm: the Charger Manager representing the battery.
+ * @event: the event string.
+ *
+ * If @event is null, it implies that uevent_notify is called
+ * by resume function. When called in the resume function, cm_suspended
+ * should be already reset to false in order to let uevent_notify
+ * notify the recent event during the suspend to users. While
+ * suspended, uevent_notify does not notify users, but tracks
+ * events so that uevent_notify can notify users later after resumed.
+ */
+static void uevent_notify(struct charger_manager *cm, const char *event)
+{
+ static char env_str[UEVENT_BUF_SIZE + 1] = "";
+ static char env_str_save[UEVENT_BUF_SIZE + 1] = "";
+
+ if (cm_suspended) {
+ /* Nothing in suspended-event buffer */
+ if (env_str_save[0] == 0) {
+ if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+ return; /* status not changed */
+ strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+ return;
+ }
+
+ if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
+ return; /* Duplicated. */
+ else
+ strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+
+ return;
+ }
+
+ /* It's called at RESUME */
+ if (event == NULL) {
+ /* No messages pending */
+ if (!env_str_save[0])
+ return;
+
+ strncpy(env_str, env_str_save, UEVENT_BUF_SIZE);
+ kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+ env_str_save[0] = 0;
+
+ return;
+ }
+
+ /* The system is running and it's not in resume */
+
+ /* status not changed */
+ if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+ return;
+
+ /* save the status and notify the update */
+ strncpy(env_str, event, UEVENT_BUF_SIZE);
+ kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+
+ dev_info(cm->dev, event);
+}
+
+/**
+ * fullbatt_vchk - Check voltage drop some times after "FULL" event.
+ * @work: the work_struct appointing the function
+ *
+ * If a user has designated "fullbatt_vchkdrop_ms/uV" values with
+ * charger_desc, Charger Manager checks voltage drop after the battery
+ * "FULL" event. It checks whether the voltage has dropped more than
+ * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms.
+ */
+static void fullbatt_vchk(struct work_struct *work)
+{
+ struct delayed_work *dwork =
+ container_of(work, struct delayed_work, work);
+ struct charger_manager *cm = container_of(dwork,
+ struct charger_manager, fullbatt_vchk_work);
+ struct charger_desc *desc = cm->desc;
+ int batt_uV, err;
+
+ /* remove the appointment for fullbatt_vchk */
+ cm->fullbatt_vchk_jiffies_at = 0;
+
+ if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
+ return;
+
+ err = get_batt_uV(cm, &batt_uV);
+ if (err) {
+ dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err);
+ return;
+ }
+
+ dev_dbg(cm->dev, "VBATT dropped %uuV after full-batt.\n",
+ cm->fullbatt_vchk_uV - batt_uV);
+
+ if ((cm->fullbatt_vchk_uV - batt_uV) > desc->fullbatt_vchkdrop_uV) {
+ try_charger_restart(cm);
+ uevent_notify(cm, "Recharge");
+ }
+}
+
+/**
+ * _cm_monitor - Monitor the temperature and return true for exceptions.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if there is an event to notify for the battery.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool _cm_monitor(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ int temp = desc->is_temperature_error(&cm->last_temp_mC);
+
+ printk(KERN_DEBUG"[CM] %s:%d temp:%d\n", __func__, __LINE__, temp);
+
+ if (!is_batt_present(cm)) {
+ dev_dbg(cm->dev, "Battery is not present.\nSystem shutdown.\n");
+ if (pm_power_off)
+ pm_power_off();
+ }
+
+ is_full(cm);
+
+ dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n",
+ cm->last_temp_mC / 1000, cm->last_temp_mC % 1000);
+
+
+ /* It has been stopped already */
+ if (temp && cm->emergency_stop)
+ return false;
+
+ /* It has been charging already */
+ if (!temp && !cm->emergency_stop)
+ return false;
+
+ if (temp) {
+ cm->emergency_stop = temp;
+ try_charger_enable(cm, false);
+ if (temp > 0)
+ uevent_notify(cm, "OVERHEAD");
+ else
+ uevent_notify(cm, "COLD");
+ } else {
+ cm->emergency_stop = 0;
+ if (!try_charger_enable(cm, true))
+ uevent_notify(cm, "CHARGING");
+ }
+
+ return true;
+}
+
+/**
+ * cm_monitor - Monitor every battery.
+ *
+ * Returns true if there is an event to notify from any of the batteries.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool cm_monitor(void)
+{
+ bool stop = false;
+ struct charger_manager *cm;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry)
+ stop |= _cm_monitor(cm);
+
+ mutex_unlock(&cm_list_mtx);
+
+ return stop;
+}
+
+/**
+ * is_polling_required - Return true if need to continue polling for this CM.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_polling_required(struct charger_manager *cm)
+{
+ switch (cm->desc->polling_mode) {
+ case CM_POLL_DISABLE:
+ return false;
+ case CM_POLL_ALWAYS:
+ return true;
+ case CM_POLL_EXTERNAL_POWER_ONLY:
+ return is_ext_pwr_online(cm);
+ case CM_POLL_CHARGING_ONLY:
+ return is_charging(cm);
+ default:
+ dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
+ cm->desc->polling_mode);
+ }
+
+ return false;
+}
+
+/**
+ * _setup_polling - Setup the next instance of polling.
+ * @work: work_struct of the function _setup_polling.
+ */
+static void _setup_polling(struct work_struct *work)
+{
+ unsigned long min = ULONG_MAX;
+ struct charger_manager *cm;
+ bool keep_polling = false;
+ unsigned long _next_polling;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (is_polling_required(cm) && cm->desc->polling_interval_ms) {
+ keep_polling = true;
+
+ if (min > cm->desc->polling_interval_ms)
+ min = cm->desc->polling_interval_ms;
+ }
+ }
+
+ polling_jiffy = msecs_to_jiffies(min);
+ if (polling_jiffy <= CM_JIFFIES_SMALL)
+ polling_jiffy = CM_JIFFIES_SMALL + 1;
+
+ if (!keep_polling)
+ polling_jiffy = ULONG_MAX;
+ if (polling_jiffy == ULONG_MAX)
+ goto out;
+
+ WARN(cm_wq == NULL, "charger-manager: workqueue not initialized"
+ ". try it later. %s\n", __func__);
+
+ _next_polling = jiffies + polling_jiffy;
+
+ if (!delayed_work_pending(&cm_monitor_work) ||
+ (delayed_work_pending(&cm_monitor_work) &&
+ time_after(next_polling, _next_polling))) {
+ cancel_delayed_work(&cm_monitor_work);
+ next_polling = jiffies + polling_jiffy;
+ queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy);
+ }
+
+out:
+ mutex_unlock(&cm_list_mtx);
+}
+static DECLARE_WORK(setup_polling, _setup_polling);
+
+/**
+ * cm_monitor_poller - The Monitor / Poller.
+ * @work: work_struct of the function cm_monitor_poller
+ *
+ * During non-suspended state, cm_monitor_poller is used to poll and monitor
+ * the batteries.
+ */
+static void cm_monitor_poller(struct work_struct *work)
+{
+ cm_monitor();
+ schedule_work(&setup_polling);
+}
+
+/**
+ * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL
+ * @cm: the Charger Manager representing the battery.
+ */
+static void fullbatt_handler(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+
+ if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
+ goto out;
+
+ if (cm_suspended)
+ cm->cancel_suspend = true;
+
+ cancel_delayed_work(&cm->fullbatt_vchk_work);
+ queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
+ msecs_to_jiffies(desc->fullbatt_vchkdrop_ms));
+ cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies(
+ desc->fullbatt_vchkdrop_ms);
+
+ if (cm->fullbatt_vchk_jiffies_at == 0)
+ cm->fullbatt_vchk_jiffies_at = 1;
+
+out:
+ dev_info(cm->dev, "IRQHANDLE: Battery Fully Charged.\n");
+ uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
+}
+
+/**
+ * battout_handler - Event handler for CM_EVENT_BATT_OUT
+ * @cm: the Charger Manager representing the battery.
+ */
+static void battout_handler(struct charger_manager *cm)
+{
+ if (cm_suspended)
+ cm->cancel_suspend = true;
+
+ if (!is_batt_present(cm)) {
+ dev_emerg(cm->dev, "Battery Pulled Out!\n");
+ uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]);
+ } else {
+ uevent_notify(cm, "Battery Reinserted?");
+ }
+}
+
+/**
+ * misc_event_handler - Handler for other evnets
+ * @cm: the Charger Manager representing the battery.
+ * @type: the Charger Manager representing the battery.
+ */
+static void misc_event_handler(struct charger_manager *cm,
+ enum cm_event_types type)
+{
+ if (cm_suspended)
+ cm->cancel_suspend = true;
+
+ if (!delayed_work_pending(&cm_monitor_work) &&
+ is_polling_required(cm) && cm->desc->polling_interval_ms)
+ schedule_work(&setup_polling);
+ uevent_notify(cm, default_event_names[type]);
+}
+
+/**
+ * cm_setup_timer - For in-suspend monitoring setup wakeup alarm for suspend_again.
+ *
+ * Returns true if the alarm is set for Charger Manager to use.
+ * Returns false if
+ * cm_setup_timer fails to set an alarm,
+ * cm_setup_timer does not need to set an alarm for Charger Manager,
+ * or an alarm previously configured is to be used.
+ */
+static bool cm_setup_timer(void)
+{
+ struct charger_manager *cm;
+ unsigned int wakeup_ms = UINT_MAX;
+ bool ret = false;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry) {
+ unsigned int fbchk_ms = 0;
+
+ /* fullbatt_vchk is required. setup timer for that */
+ if (cm->fullbatt_vchk_jiffies_at) {
+ fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at
+ - jiffies);
+ if (cm->fullbatt_vchk_jiffies_at <= jiffies ||
+ msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) {
+ fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+ fbchk_ms = 0;
+ }
+ }
+ CM_MIN_VALID(wakeup_ms, fbchk_ms);
+
+ /* Skip if polling is not required for this CM */
+ switch (cm->desc->polling_mode) {
+ case CM_POLL_DISABLE:
+ continue;
+ case CM_POLL_ALWAYS:
+ break;
+ case CM_POLL_EXTERNAL_POWER_ONLY:
+ if (!is_ext_pwr_online(cm))
+ continue;
+ break;
+ case CM_POLL_CHARGING_ONLY:
+ if (!is_charging(cm) && !cm->emergency_stop)
+ continue;
+ break;
+ default:
+ dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
+ cm->desc->polling_mode);
+ break;
+ }
+
+ if (cm->desc->polling_interval_ms == 0)
+ continue;
+
+ CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms);
+ }
+
+ /* TODO: Reviewing Here */
+
+ mutex_unlock(&cm_list_mtx);
+
+ if (wakeup_ms < UINT_MAX && wakeup_ms > 0) {
+ pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms);
+ if (rtc_dev) {
+ struct rtc_wkalrm tmp;
+ unsigned long time, now;
+ unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000);
+
+ /*
+ * Set alarm with the polling interval (wakeup_ms)
+ * except when rtc_wkalarm_save comes first.
+ * However, the alarm time should be NOW +
+ * CM_RTC_SMALL or later.
+ */
+ tmp.enabled = 1;
+ rtc_read_time(rtc_dev, &tmp.time);
+ rtc_tm_to_time(&tmp.time, &now);
+ if (add < CM_RTC_SMALL)
+ add = CM_RTC_SMALL;
+ time = now + add;
+
+ ret = true;
+
+ if (rtc_wkalarm_save.enabled && rtc_wkalarm_save_ &&
+ rtc_wkalarm_save_ < time) {
+ if (rtc_wkalarm_save_ < now + CM_RTC_SMALL)
+ time = now + CM_RTC_SMALL;
+ else
+ time = rtc_wkalarm_save_;
+
+ /* The timer is not appointed by CM */
+ ret = false;
+ }
+
+ pr_info("Waking up after %lu secs.\n",
+ time - now);
+
+ rtc_time_to_tm(time, &tmp.time);
+ rtc_set_alarm(rtc_dev, &tmp);
+ cm_suspend_duration_ms += wakeup_ms;
+ return ret;
+ }
+ }
+
+ if (rtc_dev)
+ rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
+ return false;
+}
+
+static int charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct charger_manager *cm = container_of(psy,
+ struct charger_manager, charger_psy);
+ struct charger_desc *desc = cm->desc;
+ int i, ret = 0, uV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (is_charging(cm))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (is_ext_pwr_online(cm))
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (cm->emergency_stop > 0)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (cm->emergency_stop < 0)
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (is_batt_present(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = get_batt_uV(cm, &i);
+ val->intval = i;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CURRENT_NOW, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ /* in thenth of centigrade */
+ if (cm->last_temp_mC == INT_MIN)
+ desc->is_temperature_error(&cm->last_temp_mC);
+ val->intval = cm->last_temp_mC / 100;
+ if (!desc->measure_battery_temp)
+ ret = -ENODEV;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ /* in thenth of centigrade */
+ if (cm->last_temp_mC == INT_MIN)
+ desc->is_temperature_error(&cm->last_temp_mC);
+ val->intval = cm->last_temp_mC / 100;
+ if (!desc->measure_ambient_temp)
+ ret = -ENODEV;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (!cm->fuel_gauge) {
+ ret = -ENODEV;
+ break;
+ }
+
+ if (!is_batt_present(cm)) {
+ /* There is no battery. Assume 100% */
+ val->intval = 100;
+ break;
+ }
+
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CAPACITY, val);
+ if (ret)
+ break;
+
+ if (val->intval > 100) {
+ /* More than 100%? */
+ val->intval = 100;
+ break;
+ }
+ if (val->intval < 0) {
+ /* Lower than 0%? */
+ val->intval = 0;
+ }
+
+ /* Do not adjust SOC when charging: voltage is overrated */
+ if (is_charging(cm))
+ break;
+
+ /*
+ * If the capacity value is inconsistent, calibrate it base on
+ * the battery voltage values and the thresholds given as desc
+ */
+ ret = get_batt_uV(cm, &uV);
+ if (ret) {
+ /* Voltage information not available. No calibration */
+ ret = 0;
+ break;
+ }
+
+ if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+ !is_charging(cm)) {
+ val->intval = 100;
+ break;
+ }
+
+ printk(KERN_DEBUG"[CM] %s:%d capacity:%d\n",
+ __func__, __LINE__, val->intval);
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (is_ext_pwr_online(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ if (is_ext_pwr_online(cm)) {
+ /* Not full if it's charging. */
+ if (is_charging(cm)) {
+ val->intval = 0;
+ break;
+ }
+
+ } else {
+ val->intval = 0;
+ break;
+ }
+
+ /* FIXME: use STATUS information */
+
+ /* Full if it's over the fullbatt voltage */
+ ret = get_batt_uV(cm, &uV);
+ if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+ !is_charging(cm)) {
+ val->intval = 1;
+ break;
+ }
+
+ /* Full if the cap is 100 */
+ if (cm->fuel_gauge) {
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CAPACITY, val);
+ if (!ret &&
+ (val->intval >= (100 - desc->soc_margin)) &&
+ !is_charging(cm)) {
+ val->intval = 1;
+ break;
+ }
+ }
+
+ val->intval = 0;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ if (is_charging(cm)) {
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ val);
+ if (ret) {
+ val->intval = 1;
+ ret = 0;
+ } else {
+ /* If CHARGE_NOW is supplied, use it */
+ val->intval = (val->intval > 0) ?
+ val->intval : 1;
+ }
+ } else {
+ val->intval = 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+#define NUM_CHARGER_PSY_OPTIONAL (4)
+static enum power_supply_property default_charger_props[] = {
+ /* Guaranteed to provide */
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ /*
+ * Optional properties are:
+ * POWER_SUPPLY_PROP_CHARGE_NOW,
+ * POWER_SUPPLY_PROP_CURRENT_NOW,
+ * POWER_SUPPLY_PROP_TEMP, and
+ * POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ */
+};
+
+static struct power_supply psy_default = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY, /* Need to reconsider */
+ /* .supplied_to = {"battery"}, */
+ /* .num_supplicants = 1, */
+ .properties = default_charger_props,
+ .num_properties = ARRAY_SIZE(default_charger_props),
+ .get_property = charger_get_property,
+};
+
+static bool _cm_fbchk_in_suspend(struct charger_manager *cm)
+{
+ unsigned long jiffy_now = jiffies;
+
+ if (!cm->fullbatt_vchk_jiffies_at)
+ return false;
+
+ if (g_desc && g_desc->assume_timer_stops_in_suspend)
+ jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms);
+
+ /* Execute now if it's going to be executed not too long after */
+ jiffy_now += CM_JIFFIES_SMALL;
+
+ if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) {
+ fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+ return true;
+ }
+
+ return false;
+}
+
+bool cm_suspend_again(void)
+{
+ struct charger_manager *cm;
+ bool ret = false;
+
+ if (!g_desc)
+ return false;
+ if (!g_desc->is_rtc_only_wakeup_reason)
+ return false;
+ if (!g_desc->is_rtc_only_wakeup_reason())
+ return false;
+ if (!cm_rtc_set)
+ return false;
+ if (cm_wq == NULL) /* CM not initialized */
+ return false;
+
+ if (cm_monitor())
+ goto out;
+
+ ret = true;
+ mutex_lock(&cm_list_mtx);
+ list_for_each_entry(cm, &cm_list, entry) {
+ _cm_fbchk_in_suspend(cm);
+
+ if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
+ cm->status_save_batt != is_batt_present(cm))
+ ret = false;
+ }
+ mutex_unlock(&cm_list_mtx);
+
+ cm_rtc_set = cm_setup_timer();
+out:
+ /* It's about the time when the non-CM appointed timer goes off */
+ if (rtc_wkalarm_save.enabled) {
+ unsigned long now;
+ struct rtc_time tmp;
+
+ rtc_read_time(rtc_dev, &tmp);
+ rtc_tm_to_time(&tmp, &now);
+
+ if (rtc_wkalarm_save_ &&
+ now + CM_RTC_SMALL >= rtc_wkalarm_save_)
+ return false;
+ /* TODO: Test rtc aie handlers' reaction to this */
+ }
+ pr_emerg("%s:%d\n", __func__, __LINE__);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cm_suspend_again);
+
+int setup_charger_manager(struct charger_global_desc *gd)
+{
+ if (!gd)
+ return -EINVAL;
+
+ if (rtc_dev)
+ rtc_class_close(rtc_dev);
+ rtc_dev = NULL;
+ g_desc = NULL;
+
+ if (!gd->is_rtc_only_wakeup_reason) {
+ pr_err("The callback is_wktimer_only_wkreason is not given.\n");
+ return -EINVAL;
+ }
+
+ if (gd->rtc) {
+ rtc_dev = rtc_class_open(gd->rtc);
+ if (IS_ERR_OR_NULL(rtc_dev)) {
+ rtc_dev = NULL;
+ /* Retry at probe. RTC may be not registered yet */
+ }
+ } else {
+ pr_warn("No wktimer is given for charger manager."
+ "In-suspend monitoring won't work.\n");
+ }
+
+ g_desc = gd;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(setup_charger_manager);
+
+bool is_charger_manager_active(void)
+{
+ /* Should have called setup_charger_manager */
+ if (!g_desc)
+ return false;
+
+ /* Should have at least one instance of charger_manager */
+ if (list_empty(&cm_list))
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(is_charger_manager_active);
+
+#ifdef CONFIG_EXTCON
+/*
+ * This function enable or disable charger for charging according to
+ * cable state when charger cable is attached or detached. So, each
+ * cable has requested different current limit to protect over-current
+ * issue and then it set current limit of charger(regulator) according to
+ * a kind of charger cable before enabling charger.
+ */
+static void charger_extcon_work(struct work_struct *work)
+{
+ struct charger_cable *cable =
+ container_of(work, struct charger_cable, wq);
+ int ret;
+
+ if (cable->attached && cable->min_uA != 0 && cable->max_uA != 0) {
+ ret = regulator_set_current_limit(cable->charger->consumer,
+ cable->min_uA, cable->max_uA);
+ if (ret < 0) {
+ pr_err("Cannot set current limit of %s (%s)\n",
+ cable->charger->regulator_name, cable->name);
+ return;
+ }
+
+ pr_info("Set current limit of %s : %duA ~ %duA\n",
+ cable->charger->regulator_name,
+ cable->min_uA, cable->max_uA);
+ }
+
+ try_charger_enable(cable->cm, cable->attached);
+
+#ifdef CONFIG_JACK_MON
+ /*
+ * FIXME: Extcon framework will be used instead of jack handler(
+ * CONFIG_JACK_MON). But, SLP Platform have still used jack handler
+ * for external connector (e.g., TA, USB and so on).
+ * The jack handler will be removed after replacing from jack handler
+ * to Extcon framework.
+ */
+ jack_event_handler("charger", cable->attached);
+#endif
+}
+
+/* This function executes workqueue when charger cable is attached/detached. */
+static int charger_extcon_notifier(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ struct charger_cable *cable =
+ container_of(self, struct charger_cable, nb);
+
+ cable->attached = event;
+ schedule_work(&cable->wq);
+
+ return NOTIFY_DONE;
+}
+
+/* This function initialize charger cable with Extcon framework */
+static int charger_extcon_init(struct charger_manager *cm,
+ struct charger_cable *cable)
+{
+ int ret = 0;
+
+ /*
+ * Charger manager use Extcon framework to identify
+ * the charger cable among various external connector
+ * cable (e.g., TA, USB, Dock and so on).
+ */
+ INIT_WORK(&cable->wq, charger_extcon_work);
+ cable->nb.notifier_call = charger_extcon_notifier;
+ ret = extcon_register_interest(&cable->extcon_dev,
+ cable->extcon_name, cable->name, &cable->nb);
+ if (ret < 0) {
+ pr_info("Cannot register extcon_dev for %s(cable: %s).\n",
+ cable->extcon_name,
+ cable->name);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+#endif /* CONFIG_EXTCON */
+
+static int charger_manager_probe(struct platform_device *pdev)
+{
+ struct charger_desc *desc = dev_get_platdata(&pdev->dev);
+ struct charger_manager *cm;
+ int ret = 0, i, j;
+ union power_supply_propval val;
+
+ if (g_desc && !rtc_dev && g_desc->rtc) {
+ rtc_dev = rtc_class_open(g_desc->rtc);
+ if (IS_ERR_OR_NULL(rtc_dev)) {
+ rtc_dev = NULL;
+ dev_err(&pdev->dev, "Cannot get RTC %s.\n",
+ g_desc->rtc);
+ ret = -ENODEV;
+ goto err_alloc;
+ }
+ }
+
+ if (!desc) {
+ dev_err(&pdev->dev, "No platform data (desc) found.\n");
+ ret = -ENODEV;
+ goto err_alloc;
+ }
+
+ cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL);
+ if (!cm) {
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Basic Values. Unspecified are Null or 0 */
+ cm->dev = &pdev->dev;
+ cm->desc = desc;
+ cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
+
+ /*
+ * The following two do not need to be errors.
+ * Users may intentionally ignore those two features.
+ */
+ if (desc->fullbatt_uV == 0) {
+ dev_info(&pdev->dev, "Ignoring full-battery voltage threshold"
+ " as it is not supplied.");
+ }
+
+ cm->fullbatt_vchk_uV = desc->fullbatt_uV;
+
+ if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) {
+ dev_info(&pdev->dev, "Disabling full-battery voltage drop "
+ "checking mechanism as it is not supplied.");
+ desc->fullbatt_vchkdrop_ms = 0;
+ desc->fullbatt_vchkdrop_uV = 0;
+ }
+
+ if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "charger_regulators undefined.\n");
+ goto err_no_charger;
+ }
+
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ struct charger_regulator *charger
+ = &desc->charger_regulators[i];
+
+ charger->consumer = regulator_get(&pdev->dev,
+ charger->regulator_name);
+ if (charger->consumer == NULL) {
+ dev_err(&pdev->dev, "Cannot find charger(%s)n",
+ charger->regulator_name);
+ ret = -EINVAL;
+ goto err_no_charger_stat;
+ }
+#ifdef CONFIG_EXTCON
+ for (j = 0 ; j < charger->num_cables ; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+
+ ret = charger_extcon_init(cm, cable);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot find charger(%s)n",
+ charger->regulator_name);
+ goto err_extcon;
+ }
+ cable->charger = charger;
+ cable->cm = cm;
+ }
+#endif
+ }
+
+ if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) {
+ dev_err(&pdev->dev, "No power supply defined.\n");
+ ret = -EINVAL;
+ goto err_extcon;
+ }
+
+ for (i = 0; desc->psy_charger_stat[i]; i++)
+ /* Counting index only */ ;
+
+ cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1),
+ GFP_KERNEL);
+ if (!cm->charger_stat) {
+ ret = -ENOMEM;
+ goto err_extcon;
+ }
+
+ for (i = 0; desc->psy_charger_stat[i]; i++) {
+ cm->charger_stat[i] = power_supply_get_by_name(
+ desc->psy_charger_stat[i]);
+ if (!cm->charger_stat[i]) {
+ dev_err(&pdev->dev, "Cannot find power supply "
+ "\"%s\"\n",
+ desc->psy_charger_stat[i]);
+ ret = -ENODEV;
+ goto err_chg_stat;
+ }
+ }
+
+ cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
+ if (!cm->fuel_gauge) {
+ dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
+ desc->psy_fuel_gauge);
+ ret = -ENODEV;
+ goto err_chg_stat;
+ }
+
+ if (desc->polling_interval_ms == 0 ||
+ msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) {
+ dev_err(&pdev->dev, "polling_interval_ms is too small\n");
+ ret = -EINVAL;
+ goto err_chg_stat;
+ }
+
+ if (!desc->is_temperature_error) {
+ dev_err(&pdev->dev, "there is no is_temperature_error\n");
+ ret = -EINVAL;
+ goto err_chg_stat;
+ }
+
+ platform_set_drvdata(pdev, cm);
+
+ memcpy(&cm->charger_psy, &psy_default,
+ sizeof(psy_default));
+ if (!desc->psy_name) {
+ strncpy(cm->psy_name_buf, psy_default.name,
+ PSY_NAME_MAX);
+ } else {
+ strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX);
+ }
+ cm->charger_psy.name = cm->psy_name_buf;
+
+ /* Allocate for psy properties because they may vary */
+ cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property)
+ * (ARRAY_SIZE(default_charger_props) +
+ NUM_CHARGER_PSY_OPTIONAL),
+ GFP_KERNEL);
+ if (!cm->charger_psy.properties) {
+ dev_err(&pdev->dev, "Cannot allocate for psy properties.\n");
+ ret = -ENOMEM;
+ goto err_chg_stat;
+ }
+ memcpy(cm->charger_psy.properties, default_charger_props,
+ sizeof(enum power_supply_property) *
+ ARRAY_SIZE(default_charger_props));
+ cm->charger_psy.num_properties = psy_default.num_properties;
+
+ /* Find which optional psy-properties are available */
+ if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_CHARGE_NOW;
+ cm->charger_psy.num_properties++;
+ }
+ if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val)) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_CURRENT_NOW;
+ cm->charger_psy.num_properties++;
+ }
+ if (desc->measure_ambient_temp) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ cm->charger_psy.num_properties++;
+ }
+ if (desc->measure_battery_temp) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_TEMP;
+ cm->charger_psy.num_properties++;
+ }
+
+ if (power_supply_register(NULL, &cm->charger_psy)) {
+ dev_err(&pdev->dev, "Cannot register charger-manager with"
+ " name \"%s\".\n", cm->charger_psy.name);
+ ret = -EINVAL;
+ goto err_psy;
+ }
+
+ /* Fullbat vchk should be ready before registering irq handlers */
+ INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
+
+ /* Add to the list */
+ mutex_lock(&cm_list_mtx);
+ list_add(&cm->entry, &cm_list);
+ mutex_unlock(&cm_list_mtx);
+
+ schedule_work(&setup_polling);
+
+ return 0;
+
+err_psy:
+ kfree(cm->charger_psy.properties);
+err_chg_stat:
+ kfree(cm->charger_stat);
+#ifdef CONFIG_EXTCON
+err_extcon:
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ struct charger_regulator *charger
+ = &desc->charger_regulators[i];
+ for (j = 0 ; j < charger->num_cables ; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+ extcon_unregister_interest(&cable->extcon_dev);
+ }
+ }
+#endif
+err_no_charger_stat:
+ for (i = 0 ; i < desc->num_charger_regulators ; i++)
+ regulator_put(desc->charger_regulators[i].consumer);
+err_no_charger:
+ kfree(cm);
+err_alloc:
+ return ret;
+}
+
+static int __devexit charger_manager_remove(struct platform_device *pdev)
+{
+ struct charger_manager *cm = platform_get_drvdata(pdev);
+ struct charger_desc *desc = cm->desc;
+ int i, j;
+
+ /* Remove from the list */
+ mutex_lock(&cm_list_mtx);
+ list_del(&cm->entry);
+ mutex_unlock(&cm_list_mtx);
+
+ schedule_work(&setup_polling);
+
+ power_supply_unregister(&cm->charger_psy);
+
+#ifdef CONFIG_EXTCON
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ struct charger_regulator *charger
+ = &desc->charger_regulators[i];
+ for (j = 0 ; j < charger->num_cables ; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+ extcon_unregister_interest(&cable->extcon_dev);
+ }
+ }
+#endif
+
+ for (i = 0 ; i < desc->num_charger_regulators ; i++)
+ regulator_put(desc->charger_regulators[i].consumer);
+
+ kfree(cm->charger_psy.properties);
+ kfree(cm->charger_stat);
+
+ kfree(cm);
+ return 0;
+}
+
+const struct platform_device_id charger_manager_id[] = {
+ { "charger-manager", 0 },
+ { },
+};
+
+static int cm_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct charger_manager *cm = platform_get_drvdata(pdev);
+
+ if (cm->cancel_suspend) {
+ cm->cancel_suspend = false;
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int cm_suspend_prepare(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct charger_manager *cm = platform_get_drvdata(pdev);
+
+ if (!cm_suspended) {
+ if (rtc_dev) {
+ struct rtc_time tmp;
+ unsigned long now;
+
+ rtc_read_alarm(rtc_dev, &rtc_wkalarm_save);
+ rtc_read_time(rtc_dev, &tmp);
+
+ if (rtc_wkalarm_save.enabled) {
+ rtc_tm_to_time(&rtc_wkalarm_save.time,
+ &rtc_wkalarm_save_);
+ rtc_tm_to_time(&tmp, &now);
+ if (now > rtc_wkalarm_save_)
+ rtc_wkalarm_save_ = 0;
+ } else {
+ rtc_wkalarm_save_ = 0;
+ }
+ }
+ cm_suspended = true;
+ }
+
+ cancel_delayed_work(&cm->fullbatt_vchk_work);
+ cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
+ cm->status_save_batt = is_batt_present(cm);
+
+ if (!cm_rtc_set) {
+ cm_suspend_duration_ms = 0;
+ cm_rtc_set = cm_setup_timer();
+ }
+
+ return 0;
+}
+
+static void cm_suspend_complete(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct charger_manager *cm = platform_get_drvdata(pdev);
+
+ if (cm_suspended) {
+ if (rtc_dev) {
+ struct rtc_wkalrm tmp;
+
+ rtc_read_alarm(rtc_dev, &tmp);
+ rtc_wkalarm_save.pending = tmp.pending;
+ rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
+ }
+ cm_suspended = false;
+ cm_rtc_set = false;
+ }
+
+ /* Re-enqueue delayed work (fullbatt_vchk_work) */
+ if (cm->fullbatt_vchk_jiffies_at) {
+ unsigned long delay = 0;
+ unsigned long now = jiffies;
+
+ if (time_after_eq(now + CM_JIFFIES_SMALL,
+ cm->fullbatt_vchk_jiffies_at)) {
+ delay = (unsigned long)((long)(now + CM_JIFFIES_SMALL)
+ - (long)(cm->fullbatt_vchk_jiffies_at));
+ delay = jiffies_to_msecs(delay);
+ } else {
+ delay = 0;
+ }
+
+ /*
+ * Account for cm_suspend_duration_ms if
+ * assume_timer_stops_in_suspend is active
+ */
+ if (g_desc && g_desc->assume_timer_stops_in_suspend) {
+ if (delay > cm_suspend_duration_ms)
+ delay -= cm_suspend_duration_ms;
+ else
+ delay = 0;
+ }
+
+ queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
+ msecs_to_jiffies(delay));
+ }
+ cm->cancel_suspend = false;
+ uevent_notify(cm, NULL);
+}
+
+static const struct dev_pm_ops charger_manager_pm = {
+ .prepare = cm_suspend_prepare,
+ .suspend_noirq = cm_suspend_noirq,
+ .complete = cm_suspend_complete,
+};
+
+static struct platform_driver charger_manager_driver = {
+ .driver = {
+ .name = "charger-manager",
+ .owner = THIS_MODULE,
+ .pm = &charger_manager_pm,
+ },
+ .probe = charger_manager_probe,
+ .remove = __devexit_p(charger_manager_remove),
+ .id_table = charger_manager_id,
+};
+
+static int __init charger_manager_init(void)
+{
+ cm_wq = create_freezable_workqueue("charger_manager");
+ INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller);
+
+ return platform_driver_register(&charger_manager_driver);
+}
+/* Charger manager depends on other devices. Init this later than others */
+late_initcall(charger_manager_init);
+
+static void __exit charger_manager_cleanup(void)
+{
+ destroy_workqueue(cm_wq);
+ cm_wq = NULL;
+
+ platform_driver_unregister(&charger_manager_driver);
+}
+module_exit(charger_manager_cleanup);
+
+static bool find_power_supply(struct charger_manager *cm,
+ struct power_supply *psy)
+{
+ int i;
+ bool found = false;
+
+ for (i = 0; cm->charger_stat[i]; i++) {
+ if (psy == cm->charger_stat[i]) {
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+}
+
+void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
+ char *msg)
+{
+ struct charger_manager *cm;
+ bool found_power_supply = false;
+
+ if (psy == NULL)
+ return;
+
+ mutex_lock(&cm_list_mtx);
+ list_for_each_entry(cm, &cm_list, entry) {
+ found_power_supply = find_power_supply(cm, psy);
+ if (found_power_supply)
+ break;
+ }
+ mutex_unlock(&cm_list_mtx);
+
+ if (!found_power_supply)
+ return;
+
+ switch (type) {
+ case CM_EVENT_BATT_FULL:
+ fullbatt_handler(cm);
+ break;
+ case CM_EVENT_BATT_OUT:
+ battout_handler(cm);
+ break;
+ case CM_EVENT_BATT_IN:
+ case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP:
+ misc_event_handler(cm, type);
+ break;
+ case CM_EVENT_UNDESCRIBED:
+ case CM_EVENT_OTHERS:
+ uevent_notify(cm, msg ? msg : default_event_names[type]);
+ break;
+ default:
+ dev_err(cm->dev, "%s type not specified.\n", __func__);
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(cm_notify_event);
+
+struct charger_manager *get_charger_manager(char *psy_name)
+{
+ struct power_supply *psy = power_supply_get_by_name(psy_name);
+
+ return container_of(psy, struct charger_manager, charger_psy);
+}
+EXPORT_SYMBOL_GPL(get_charger_manager);
+
+void cm_prohibit_charging(struct charger_manager *cm)
+{
+ cm->user_prohibit = true;
+ try_charger_enable(cm, false);
+}
+EXPORT_SYMBOL_GPL(cm_prohibit_charging);
+
+void cm_allow_charging(struct charger_manager *cm)
+{
+ cm->user_prohibit = false;
+ try_charger_enable(cm, true);
+}
+EXPORT_SYMBOL_GPL(cm_allow_charging);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("Charger Manager");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("charger-manager");
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
index c5c8805..4842eaa 100644
--- a/drivers/power/max17042_battery.c
+++ b/drivers/power/max17042_battery.c
@@ -174,7 +174,10 @@ static int __devinit max17042_probe(struct i2c_client *client,
i2c_set_clientdata(client, chip);
- chip->battery.name = "max17042_battery";
+ if (chip->pdata->psy_name)
+ chip->battery.name = chip->pdata->psy_name;
+ else
+ chip->battery.name = "max17042_battery";
chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
chip->battery.get_property = max17042_get_property;
chip->battery.properties = max17042_battery_props;
diff --git a/drivers/power/max17042_fuelgauge_px.c b/drivers/power/max17042_fuelgauge_px.c
new file mode 100644
index 0000000..6244f2b
--- /dev/null
+++ b/drivers/power/max17042_fuelgauge_px.c
@@ -0,0 +1,1622 @@
+/*
+ * max17042_battery.c
+ * fuel-gauge systems for lithium-ion (Li+) batteries
+ *
+ * Copyright (C) 2010 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/types.h>
+#include <linux/irq.h>
+#include <linux/pm.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/vmalloc.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/wakelock.h>
+#include <linux/blkdev.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/rtc.h>
+#include <mach/gpio.h>
+#include <linux/power/sec_battery_px.h>
+#include <linux/power/max17042_fuelgauge_px.h>
+
+#define PRINT_COUNT 1
+
+static struct i2c_client *fg_i2c_client;
+struct max17042_chip *max17042_chip_data;
+
+static int fg_get_battery_type(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ return chip->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(u8 addr)
+{
+ struct i2c_client *client = fg_i2c_client;
+ 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(u8 addr, u16 w_data)
+{
+ struct i2c_client *client = fg_i2c_client;
+ 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(u8 addr, u16 *r_data)
+{
+ struct i2c_client *client = fg_i2c_client;
+ 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;
+}
+
+void fg_write_and_verify_register(u8 addr, u16 w_data)
+{
+ u16 r_data;
+ u8 retry_cnt = 2;
+
+ while (retry_cnt) {
+ fg_write_register(addr, w_data);
+ r_data = fg_read_register(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(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+ u32 average_vcell;
+ u16 w_data;
+ u32 temp;
+ u32 temp2;
+ u16 fullcap, remcap, mixcap, avcap;
+
+ 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);
+
+ fullcap = fg_read_register(FULLCAP_REG);
+ remcap = fg_read_register(REMCAP_REP_REG);
+ mixcap = fg_read_register(REMCAP_MIX_REG);
+ avcap = fg_read_register(REMCAP_AV_REG);
+
+ pr_info("avg_vcell(%d), fullcap(%d), remcap(%d), mixcap(%d), avcap(%d)\n",
+ average_vcell, fullcap/2, remcap/2, mixcap/2, avcap/2);
+ msleep(20);
+}
+
+static int fg_read_voltage_now(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+ u32 voltage_now;
+ 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;
+ voltage_now = temp / 1000;
+
+ temp = ((w_data & 0xF000) >> 4) * 78125;
+ temp2 = temp / 1000;
+ voltage_now += (temp2 << 4);
+
+ return voltage_now;
+}
+
+static int fg_read_vcell(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = 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 (!(chip->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_avg_vcell(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+ u32 avg_vcell;
+ u16 w_data;
+ u32 temp;
+ u32 temp2;
+
+ if (fg_i2c_read(client, AVR_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;
+ avg_vcell = temp / 1000000;
+
+ temp = ((w_data & 0xF000) >> 4) * 78125;
+ temp2 = temp / 1000000;
+ avg_vcell += (temp2 << 4);
+
+ return avg_vcell;
+}
+
+static int fg_read_vfocv(void)
+{
+ struct i2c_client *client = fg_i2c_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(void)
+{
+ struct i2c_client *client = fg_i2c_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(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2] = { 0, };
+ int temper = 0;
+
+ if (fg_check_battery_present()) {
+ 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 */
+ if (fg_get_battery_type() == SDI_BATTERY_TYPE) {
+ if (temper >= 47000 && temper < 60000) {
+ temper = temper * SDI_TRIM1_1 / 100;
+ temper -= SDI_TRIM1_2;
+ } else if (temper >= 60000) {
+ temper = temper * SDI_TRIM2_1 / 100;
+ temper -= 51000;
+ }
+ }
+ }
+ } else
+ temper = 20000;
+
+ if (!(chip->info.pr_cnt % PRINT_COUNT))
+ pr_info("%s: TEMPERATURE(%d), data(0x%04x)\n", __func__,
+ temper, (data[1]<<8) | data[0]);
+
+ return temper;
+}
+
+static int fg_read_soc(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ u32 soc = 0;
+
+ if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) {
+ pr_err("%s: Failed to read SOCREP\n", __func__);
+ return -1;
+ }
+
+ soc = data[1];
+
+ if (!(chip->info.pr_cnt % PRINT_COUNT))
+ pr_info("%s: SOC(%d), data(0x%04x)\n", __func__,
+ soc, (data[1]<<8) | data[0]);
+
+ return soc;
+}
+
+static int fg_read_vfsoc(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+
+ if (fg_i2c_read(client, VFSOC_REG, data, 2) < 0) {
+ pr_err("%s: Failed to read VFSOC\n", __func__);
+ return -1;
+ }
+
+ return (int)data[1];
+}
+
+static int fg_read_current(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = 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;
+
+ i_current = temp * MAX17042_CURRENT_UNIT;
+ 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;
+
+ avg_current = temp * MAX17042_CURRENT_UNIT;
+ if (sign)
+ avg_current *= -1;
+
+ if (!(chip->info.pr_cnt++ % PRINT_COUNT)) {
+ fg_test_print();
+ pr_info("%s: CURRENT(%dmA), AVG_CURRENT(%dmA)\n", __func__,
+ i_current, avg_current);
+ chip->info.pr_cnt = 1;
+ /* Read max17042's all registers every 5 minute. */
+ fg_periodic_read();
+ }
+
+ return i_current;
+}
+
+static int fg_read_avg_current(void)
+{
+ struct i2c_client *client = fg_i2c_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;
+
+ avg_current = temp * MAX17042_CURRENT_UNIT;
+
+ if (sign)
+ avg_current *= -1;
+
+ return avg_current;
+}
+
+int fg_reset_soc(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE)
+ u32 fullcap;
+ int vfocv = 0;
+#endif
+
+ /* delay for current stablization */
+ msleep(500);
+
+ pr_info("%s: %s - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n",
+ __func__, "Before quick-start", fg_read_vcell(),
+ fg_read_vfocv(), fg_read_vfsoc(), fg_read_soc());
+ pr_info("%s: Before quick-start - current(%d), avg current(%d)\n",
+ __func__, fg_read_current(),
+ fg_read_avg_current());
+
+ if (!chip->pdata->check_jig_status()) {
+ pr_info("%s: Return by No JIG_ON signal\n", __func__);
+ return 0;
+ }
+
+ fg_write_register(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(FULLCAP_REG, chip->info.capacity);
+ msleep(500);
+
+ pr_info("%s: %s - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n",
+ __func__, "After quick-start", fg_read_vcell(),
+ fg_read_vfocv(), fg_read_vfsoc(), fg_read_soc());
+ pr_info("%s: After quick-start - current(%d), avg current(%d)\n",
+ __func__, fg_read_current(), fg_read_avg_current());
+ fg_write_register(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) || defined(CONFIG_MACH_P8LTE)
+#define QUICKSTART_POWER_OFF_VOLTAGE 3400
+ vfocv = fg_read_vfocv();
+ if (vfocv < QUICKSTART_POWER_OFF_VOLTAGE) {
+ pr_info("%s: Power off condition(%d)\n", __func__, vfocv);
+
+ fullcap = fg_read_register(FULLCAP_REG);
+ /* FullCAP * 0.009 */
+ fg_write_register(REMCAP_REP_REG, (u16)(fullcap * 9 / 1000));
+ msleep(200);
+ pr_info("%s: new soc=%d, vfocv=%d\n", __func__,
+ fg_read_soc(), vfocv);
+ }
+
+ pr_info("%s: Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n",
+ __func__, fg_read_vfocv(), fg_read_vfsoc(), fg_read_soc());
+#endif
+
+ return 0;
+}
+
+
+int fg_reset_capacity(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ return fg_write_register(DESIGNCAP_REG, chip->info.vfcapacity-1);
+}
+
+int fg_adjust_capacity(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = 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());
+
+ chip->info.soc_restart_flag = 1;
+
+ return 0;
+}
+
+void fg_low_batt_compensation(u32 level)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ int read_val;
+ u32 temp;
+
+ pr_info("%s: Adjust SOCrep to %d!!\n", __func__, level);
+
+ read_val = fg_read_register(FULLCAP_REG);
+ if (read_val < 0)
+ return;
+
+ /* RemCapREP (05h) = FullCap(10h) x 0.0304 or 0.0104 */
+ temp = read_val * (level*100 + 4) / 10000;
+ fg_write_register(REMCAP_REP_REG, (u16)temp);
+
+ /* (0x67) * 0.0039 = 0.4%*/
+ temp = 0;
+ temp = (u16)((level << 8) | 0x67);
+ fg_write_register(SOCREP_REG, temp);
+
+ chip->info.low_batt_comp_flag = 1;
+}
+
+void fg_periodic_read(void)
+{
+ int i;
+ u16 data[0x10];
+ struct timespec ts;
+ struct rtc_time tm;
+
+ getnstimeofday(&ts);
+ rtc_time_to_tm(ts.tv_sec, &tm);
+
+ pr_info("[MAX17042] %d/%d/%d %02d:%02d,",
+ tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour,
+ tm.tm_min);
+
+ for (i = 0; i < 16; i++) {
+ fg_read_16register(i, data);
+
+ pr_info("%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_info("%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;
+
+ msleep(20);
+ }
+ pr_info("\n");
+}
+
+static void fg_read_model_data(void)
+{
+ u16 data0[16], data1[16], data2[16];
+ int i;
+ int relock_check;
+
+ pr_info("[FG_Model] ");
+
+ /* Unlock model access */
+ fg_write_register(0x62, 0x0059);
+ fg_write_register(0x63, 0x00C4);
+
+ /* Read model data */
+ fg_read_16register(0x80, data0);
+ fg_read_16register(0x90, data1);
+ fg_read_16register(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(0x62, 0x0000);
+ fg_write_register(0x63, 0x0000);
+
+ /* Read model data again */
+ fg_read_16register(0x80, data0);
+ fg_read_16register(0x90, data1);
+ fg_read_16register(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(void)
+{
+ struct i2c_client *client = fg_i2c_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((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((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(void)
+{
+ struct i2c_client *client = fg_i2c_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(u32 is_recharging, u32 pre_update)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ static int new_fcap;
+ pr_info("%s: is_recharging(%d), pre_update(%d)\n", __func__,
+ is_recharging, pre_update);
+
+ new_fcap = fg_read_register(FULLCAP_REG);
+ if (new_fcap < 0)
+ new_fcap = chip->info.capacity;
+
+ pr_info("%s: prevFCap(0x%04x), newFcap(0x%04x)\n", __func__,
+ chip->info.prev_fullcap, new_fcap);
+ if (new_fcap > (chip->info.capacity * 110 / 100)) {
+ new_fcap = (chip->info.capacity * 110) / 100;
+ pr_info("%s: init FCap +10%%: newFcap(0x%04x)\n",
+ __func__, new_fcap);
+ fg_write_register(REMCAP_REP_REG, (u16)(new_fcap));
+ fg_write_register(FULLCAP_REG, (u16)(new_fcap));
+ } else if (new_fcap < (chip->info.capacity * 50 / 100)) {
+ new_fcap = (chip->info.capacity * 50) / 100;
+ pr_info("%s: init FCap -50%%: newFcap(0x%04x)\n",
+ __func__, new_fcap);
+ fg_write_register(REMCAP_REP_REG, (u16)(new_fcap));
+ fg_write_register(FULLCAP_REG, (u16)(new_fcap));
+ } else {
+ if (new_fcap > (chip->info.prev_fullcap * 110 / 100)) {
+ new_fcap = (chip->info.prev_fullcap * 110) / 100;
+ pr_info("%s: prev FCap +10%%: newFcap(0x%04x)\n",
+ __func__, new_fcap);
+ fg_write_register(REMCAP_REP_REG, (u16)(new_fcap));
+ fg_write_register(FULLCAP_REG, (u16)(new_fcap));
+ } else if (new_fcap < (chip->info.prev_fullcap * 90 / 100)) {
+ new_fcap = (chip->info.prev_fullcap * 90) / 100;
+ pr_info("%s: prev FCap -10%%: newFcap(0x%04x)\n",
+ __func__, new_fcap);
+ fg_write_register(REMCAP_REP_REG, (u16)(new_fcap));
+ fg_write_register(FULLCAP_REG, (u16)(new_fcap));
+ } else {
+ pr_info("%s: keep FCap: newFcap(0x%04x)\n",
+ __func__, new_fcap);
+ }
+ }
+
+ /* 4. Write RepSOC(06h)=100%; */
+ fg_write_register(SOCREP_REG, (u16)(0x64 << 8));
+
+ /* 5. Write MixSOC(0Dh)=100%; */
+ fg_write_register(SOCMIX_REG, (u16)(0x64 << 8));
+
+ /* 6. Write AVSOC(0Eh)=100%; */
+ fg_write_register(SOCAV_REG, (u16)(0x64 << 8));
+
+ /* if pre_update case, skip updating PrevFullCAP value. */
+ if (!pre_update)
+ chip->info.prev_fullcap = fg_read_register(FULLCAP_REG);
+
+ pr_info("%s: FullCap(0x%04x), RemCap(0x%04x)\n", __func__,
+ fg_read_register(FULLCAP_REG),
+ fg_read_register(REMCAP_REP_REG));
+
+ fg_periodic_read();
+}
+
+void fg_check_vf_fullcap_range(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ static int new_vffcap;
+ u16 print_flag = 1;
+ pr_debug("%s\n", __func__);
+
+ new_vffcap = fg_read_register(FULLCAP_NOM_REG);
+ if (new_vffcap < 0)
+ new_vffcap = chip->info.vfcapacity;
+
+ pr_info("%s: prevVFFCap(0x%04x), newVFFcap(0x%04x)\n", __func__,
+ chip->info.prev_vffcap, new_vffcap);
+ if (new_vffcap > (chip->info.vfcapacity * 110 / 100)) {
+ new_vffcap = (chip->info.vfcapacity * 110) / 100;
+ pr_info("%s: init VFFCap +10%%: newVFFcap(0x%04x)\n",
+ __func__, new_vffcap);
+ fg_write_register(DQACC_REG, (u16)(new_vffcap / 4));
+ fg_write_register(DPACC_REG, (u16)0x3200);
+ } else if (new_vffcap < (chip->info.vfcapacity * 50 / 100)) {
+ new_vffcap = (chip->info.vfcapacity * 50) / 100;
+ pr_info("%s: init VFFCap -50%%: newVFFcap(0x%04x)\n",
+ __func__, new_vffcap);
+ fg_write_register(DQACC_REG, (u16)(new_vffcap / 4));
+ fg_write_register(DPACC_REG, (u16)0x3200);
+ } else {
+ if (new_vffcap > (chip->info.prev_vffcap * 105 / 100)) {
+ new_vffcap = (chip->info.prev_vffcap * 105) / 100;
+ pr_info("%s: prev VFFCap +10%%: newVFFcap(0x%04x)\n",
+ __func__, new_vffcap);
+ fg_write_register(DQACC_REG, (u16)(new_vffcap / 4));
+ fg_write_register(DPACC_REG, (u16)0x3200);
+ } else if (new_vffcap < (chip->info.prev_vffcap * 90 / 100)) {
+ new_vffcap = (chip->info.prev_vffcap * 90) / 100;
+ pr_info("%s: prev VFFCap -10%%: newVFFcap(0x%04x)\n",
+ __func__, new_vffcap);
+ fg_write_register(DQACC_REG, (u16)(new_vffcap / 4));
+ fg_write_register(DPACC_REG, (u16)0x3200);
+ } else {
+ pr_info("%s: keep VFFCap: newVFFcap(0x%04x)\n",
+ __func__, new_vffcap);
+ print_flag = 0;
+ }
+ }
+
+ /* delay for register setting (dQacc, dPacc) */
+ if (print_flag)
+ msleep(300);
+
+ chip->info.prev_vffcap = fg_read_register(FULLCAP_NOM_REG);
+
+ if (print_flag)
+ pr_info("%s: VfFullCap(0x%04x), dQacc(0x%04x), dPacc(0x%04x)\n",
+ __func__,
+ fg_read_register(FULLCAP_NOM_REG),
+ fg_read_register(DQACC_REG),
+ fg_read_register(DPACC_REG));
+}
+
+int fg_check_cap_corruption(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ int vfsoc = fg_read_vfsoc();
+ int repsoc = fg_read_soc();
+ int mixcap = fg_read_register(REMCAP_MIX_REG);
+ int vfocv = fg_read_register(VFOCV_REG);
+ int remcap = fg_read_register(REMCAP_REP_REG);
+ int fullcapacity = fg_read_register(FULLCAP_REG);
+ int vffullcapacity = fg_read_register(FULLCAP_NOM_REG);
+ u32 temp, temp2, new_vfocv, pr_vfocv;
+ int designcap;
+ int ret = 0;
+
+ /* If usgin Jig or low batt compensation flag is set,
+ then skip checking. */
+ if (chip->pdata->check_jig_status()) {
+ fg_write_register(DESIGNCAP_REG, chip->info.vfcapacity - 1);
+ designcap = fg_read_register(DESIGNCAP_REG);
+ pr_info("%s: return by jig, vfcap(0x%04x), designcap(0x%04x)\n",
+ __func__, chip->info.vfcapacity, designcap);
+ 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 >= 70)
+ && ((remcap >= (fullcapacity * 995 / 1000))
+ && (remcap <= (fullcapacity * 1005 / 1000))))
+ || chip->info.low_batt_comp_flag
+ || chip->info.soc_restart_flag) {
+
+ chip->info.prev_repsoc = repsoc;
+ chip->info.prev_remcap = remcap;
+ chip->info.prev_fullcapacity = fullcapacity;
+ if (chip->info.soc_restart_flag)
+ chip->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) < chip->info.prev_vfsoc)
+ || (vfsoc > (chip->info.prev_vfsoc+5)))
+ || (((repsoc+5) < chip->info.prev_repsoc)
+ || (repsoc > (chip->info.prev_repsoc+5)))
+ || (((mixcap+530) < chip->info.prev_mixcap)
+ || (mixcap > (chip->info.prev_mixcap+530)))) {
+ fg_periodic_read();
+
+ pr_info("[FG_Recovery] (B) VfSOC(%d), prevVfSOC(%d),",
+ vfsoc, chip->info.prev_vfsoc);
+ pr_info(" RepSOC(%d), prevRepSOC(%d), MixCap(%d),",
+ repsoc, chip->info.prev_repsoc, (mixcap/2));
+ pr_info(" prevMixCap(%d),VfOCV(0x%04x, %d)\n",
+ (chip->info.prev_mixcap/2), vfocv, pr_vfocv);
+
+ mutex_lock(&chip->fg_lock);
+
+ fg_write_and_verify_register(REMCAP_MIX_REG,
+ chip->info.prev_mixcap);
+ fg_write_register(VFOCV_REG, chip->info.prev_vfocv);
+ mdelay(200);
+
+ fg_write_and_verify_register(REMCAP_REP_REG,
+ chip->info.prev_remcap);
+ vfsoc = fg_read_register(VFSOC_REG);
+ fg_write_register(0x60, 0x0080);
+ fg_write_and_verify_register(0x48, vfsoc);
+ fg_write_register(0x60, 0x0000);
+
+ fg_write_and_verify_register(0x45,
+ (chip->info.prev_vfcapacity / 4));
+ fg_write_and_verify_register(0x46, 0x3200);
+ fg_write_and_verify_register(FULLCAP_REG,
+ chip->info.prev_fullcapacity);
+ fg_write_and_verify_register(FULLCAP_NOM_REG,
+ chip->info.prev_vfcapacity);
+
+ mutex_unlock(&chip->fg_lock);
+
+ msleep(200);
+
+ /* ocv calculation for print */
+ new_vfocv = fg_read_register(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),",
+ fg_read_vfsoc(),
+ fg_read_soc());
+ pr_info(" newMixCap(%d), newVfOCV(0x%04x, %d)\n",
+ (fg_read_register(REMCAP_MIX_REG)/2),
+ new_vfocv,
+ pr_vfocv);
+
+ fg_periodic_read();
+
+ ret = 1;
+ } else {
+ chip->info.prev_vfsoc = vfsoc;
+ chip->info.prev_repsoc = repsoc;
+ chip->info.prev_remcap = remcap;
+ chip->info.prev_mixcap = mixcap;
+ chip->info.prev_fullcapacity = fullcapacity;
+ chip->info.prev_vfcapacity = vffullcapacity;
+ chip->info.prev_vfocv = vfocv;
+ }
+
+ return ret;
+}
+
+void fg_set_full_charged(void)
+{
+ pr_info("[FG_Set_Full] (B) FullCAP(%d), RemCAP(%d)\n",
+ (fg_read_register(FULLCAP_REG)/2),
+ (fg_read_register(REMCAP_REP_REG)/2));
+
+ fg_write_register(FULLCAP_REG, (u16)fg_read_register(REMCAP_REP_REG));
+
+ pr_info("[FG_Set_Full] (A) FullCAP(%d), RemCAP(%d)\n",
+ (fg_read_register(FULLCAP_REG)/2),
+ (fg_read_register(REMCAP_REP_REG)/2));
+}
+
+static void display_low_batt_comp_cnt(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 type_str[10];
+
+ if (fg_get_battery_type() == SDI_BATTERY_TYPE)
+ sprintf(type_str, "SDI");
+ else if (fg_get_battery_type() == ATL_BATTERY_TYPE)
+ sprintf(type_str, "ATL");
+ else
+ sprintf(type_str, "Unknown");
+
+ pr_info("Check Array(%s):[%d,%d], [%d,%d], [%d,%d], [%d,%d], [%d,%d]\n",
+ type_str,
+ chip->info.low_batt_comp_cnt[0][0],
+ chip->info.low_batt_comp_cnt[0][1],
+ chip->info.low_batt_comp_cnt[1][0],
+ chip->info.low_batt_comp_cnt[1][1],
+ chip->info.low_batt_comp_cnt[2][0],
+ chip->info.low_batt_comp_cnt[2][1],
+ chip->info.low_batt_comp_cnt[3][0],
+ chip->info.low_batt_comp_cnt[3][1],
+ chip->info.low_batt_comp_cnt[4][0],
+ chip->info.low_batt_comp_cnt[4][1]);
+}
+
+static void add_low_batt_comp_cnt(int range, int level)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ int i;
+ int j;
+
+ /* Increase the requested count value, and reset others. */
+ chip->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
+ chip->info.low_batt_comp_cnt[i][j] = 0;
+ }
+ }
+}
+
+#define POWER_OFF_SOC_HIGH_MARGIN 0x200
+#define POWER_OFF_VOLTAGE_HIGH_MARGIN 3500
+#define POWER_OFF_VOLTAGE_NOW_LOW_MARGIN 3350
+#define POWER_OFF_VOLTAGE_AVG_LOW_MARGIN 3400
+
+void prevent_early_late_poweroff(int vcell, int *fg_soc)
+{
+ struct i2c_client *client = fg_i2c_client;
+ int repsoc, repsoc_data = 0;
+ int read_val;
+ int avg_vcell;
+ 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;
+
+ avg_vcell = fg_read_avg_vcell();
+ pr_info("%s: soc=%d%%(0x%04x), vcell=%d avg_vcell=%d\n",
+ __func__, repsoc, repsoc_data, vcell, avg_vcell);
+
+ if (vcell > POWER_OFF_VOLTAGE_HIGH_MARGIN) {
+ read_val = fg_read_register(FULLCAP_REG);
+ /* FullCAP * 0.013 */
+ fg_write_register(REMCAP_REP_REG, (u16)(read_val * 13 / 1000));
+ msleep(200);
+ *fg_soc = fg_read_soc();
+ pr_info("%s: new soc=%d, vcell=%d, avg_vcell=%d\n",
+ __func__, *fg_soc, vcell, avg_vcell);
+ } else if ((vcell < POWER_OFF_VOLTAGE_NOW_LOW_MARGIN) &&
+ (avg_vcell < POWER_OFF_VOLTAGE_AVG_LOW_MARGIN)) {
+ read_val = fg_read_register(FULLCAP_REG);
+ /* FullCAP * 0.009 */
+ fg_write_register(REMCAP_REP_REG, (u16)(read_val * 9 / 1000));
+ msleep(200);
+ *fg_soc = fg_read_soc();
+ pr_info("%s: new soc=%d, vcell=%d, avg_vcell=%d\n",
+ __func__, *fg_soc, vcell, avg_vcell);
+ }
+}
+
+void reset_low_batt_comp_cnt(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ memset(chip->info.low_batt_comp_cnt, 0,
+ sizeof(chip->info.low_batt_comp_cnt));
+}
+
+static int check_low_batt_comp_condtion(int *nLevel)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = 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 (chip->info.low_batt_comp_cnt[i][j] \
+ >= MAX_LOW_BATT_CHECK_CNT) {
+ display_low_batt_comp_cnt();
+ ret = 1;
+ *nLevel = j*2 + 1;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int get_low_batt_threshold(int range, int level, int nCurrent)
+{
+ int ret = 0;
+
+ if (fg_get_battery_type() == SDI_BATTERY_TYPE) {
+ switch (range) {
+/* P4 & P8 needs one more level */
+#if defined(CONFIG_MACH_P4NOTE) \
+ || defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE)
+ 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() == 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(int fg_soc, int fg_vcell, int fg_current)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = 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 (!chip->info.low_batt_comp_flag && \
+ (fg_vcell <= chip->info.check_start_vol)) {
+
+ fg_avg_current = fg_read_avg_current();
+ 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( \
+ CURRENT_RANGE_MAX_NUM,
+ 1, fg_min_current))
+ add_low_batt_comp_cnt(CURRENT_RANGE_MAX_NUM, 1);
+ else if (fg_soc >= 4 && \
+ fg_vcell < get_low_batt_threshold( \
+ CURRENT_RANGE_MAX_NUM,
+ 3, fg_min_current))
+ add_low_batt_comp_cnt(CURRENT_RANGE_MAX_NUM, 3);
+ else
+ bCntReset = 1;
+ }
+/* P4 & P8 needs more level */
+#if defined(CONFIG_MACH_P4NOTE) \
+ || defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE)
+ else if (fg_min_current >= CURRENT_RANGE5 && \
+ fg_min_current < CURRENT_RANGE4) {
+ if (fg_soc >= 2 && fg_vcell < \
+ get_low_batt_threshold(4, 1, fg_min_current))
+
+ add_low_batt_comp_cnt(4, 1);
+ else if (fg_soc >= 4 && fg_vcell < \
+ get_low_batt_threshold(4, 3, fg_min_current))
+
+ add_low_batt_comp_cnt(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(3, 1, fg_min_current))
+
+ add_low_batt_comp_cnt(3, 1);
+ else if (fg_soc >= 4 && fg_vcell < \
+ get_low_batt_threshold(3, 3, fg_min_current))
+ add_low_batt_comp_cnt(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(2, 1, fg_min_current))
+
+ add_low_batt_comp_cnt(2, 1);
+ else if (fg_soc >= 4 && fg_vcell < \
+ get_low_batt_threshold(2, 3, fg_min_current))
+
+ add_low_batt_comp_cnt(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(1, 1, fg_min_current))
+
+ add_low_batt_comp_cnt(1, 1);
+ else if (fg_soc >= 4 && fg_vcell < \
+ get_low_batt_threshold(1, 3, fg_min_current))
+
+ add_low_batt_comp_cnt(1, 3);
+ else
+ bCntReset = 1;
+ }
+
+
+ if (check_low_batt_comp_condtion(&new_level)) {
+#if defined(CONFIG_MACH_P4NOTE) || \
+ defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE)
+ /*
+ * Disable 3% low battery compensation
+ * duplicated action with 1% low battery compensation
+ */
+ if (new_level < 2)
+#endif
+ fg_low_batt_compensation(new_level);
+ reset_low_batt_comp_cnt();
+ }
+
+ if (bCntReset)
+ reset_low_batt_comp_cnt();
+
+ /* if compensation finished, then read SOC again!!*/
+ if (chip->info.low_batt_comp_flag) {
+ pr_info("%s: MIN_CURRENT(%d), AVG_CURRENT(%d),",
+ __func__, fg_min_current, fg_avg_current);
+ pr_info(" CURRENT(%d), SOC(%d), VCELL(%d)\n",
+ fg_current, fg_soc, fg_vcell);
+#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE)
+ /* 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());
+#else
+ fg_soc = fg_read_soc();
+ pr_info("%s: SOC is set to %d\n", __func__, fg_soc);
+#endif
+ }
+ }
+
+#if defined(CONFIG_MACH_P4NOTE) || defined(CONFIG_MACH_P2)
+ /* Prevent power off over 3500mV */
+ /* Force power off under 3400mV */
+ prevent_early_late_poweroff(fg_vcell, &fg_soc);
+#endif
+
+ return fg_soc;
+}
+
+static void fg_set_battery_type(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ u16 data;
+ u8 type_str[10];
+
+ data = fg_read_register(DESIGNCAP_REG);
+
+ if ((data == chip->pdata->sdi_vfcapacity) || \
+ (data == chip->pdata->sdi_vfcapacity-1))
+ chip->info.battery_type = SDI_BATTERY_TYPE;
+ else if ((data == chip->pdata->atl_vfcapacity) || \
+ (data == chip->pdata->atl_vfcapacity-1))
+ chip->info.battery_type = ATL_BATTERY_TYPE;
+ else {
+ pr_info("%s: Unknown battery is set to SDI type.\n", __func__);
+ chip->info.battery_type = SDI_BATTERY_TYPE;
+ }
+
+ if (chip->info.battery_type == SDI_BATTERY_TYPE)
+ sprintf(type_str, "SDI");
+ else if (chip->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 (chip->info.battery_type) {
+ case ATL_BATTERY_TYPE:
+ chip->info.capacity = chip->pdata->atl_capacity;
+ chip->info.vfcapacity = chip->pdata->atl_vfcapacity;
+ break;
+
+ case SDI_BATTERY_TYPE:
+ default:
+ chip->info.capacity = chip->pdata->sdi_capacity;
+ chip->info.vfcapacity = chip->pdata->sdi_vfcapacity;
+ break;
+ }
+
+ /* If not initialized yet, then init threshold values. */
+ if (!chip->info.check_start_vol) {
+ if (chip->info.battery_type == SDI_BATTERY_TYPE)
+ chip->info.check_start_vol = \
+ chip->pdata->sdi_low_bat_comp_start_vol;
+ else if (chip->info.battery_type == ATL_BATTERY_TYPE)
+ chip->info.check_start_vol = \
+ chip->pdata->atl_low_bat_comp_start_vol;
+ }
+
+}
+
+int get_fuelgauge_capacity(enum capacity_type type)
+{
+ int cap = -1;
+ pr_debug("%s\n", __func__);
+
+ switch (type) {
+ case CAPACITY_TYPE_FULL:
+ cap = fg_read_register(FULLCAP_REG);
+ break;
+ case CAPACITY_TYPE_MIX:
+ cap = fg_read_register(REMCAP_MIX_REG);
+ break;
+ case CAPACITY_TYPE_AV:
+ cap = fg_read_register(REMCAP_AV_REG);
+ break;
+ case CAPACITY_TYPE_REP:
+ cap = fg_read_register(REMCAP_REP_REG);
+ break;
+ default:
+ pr_info("%s: invalid type(%d)\n", __func__, type);
+ cap = -EINVAL;
+ break;
+ }
+
+ pr_debug("%s: type(%d), cap(0x%x, %d)\n", __func__,
+ type, cap, (cap / 2));
+ return cap;
+}
+
+int get_fuelgauge_value(int data)
+{
+ int ret;
+
+ switch (data) {
+ case FG_LEVEL:
+ ret = fg_read_soc();
+ break;
+
+ case FG_TEMPERATURE:
+ ret = fg_read_temp();
+ break;
+
+ case FG_VOLTAGE:
+ ret = fg_read_vcell();
+ break;
+
+ case FG_CURRENT:
+ ret = fg_read_current();
+ break;
+
+ case FG_CURRENT_AVG:
+ ret = fg_read_avg_current();
+ break;
+
+ case FG_BATTERY_TYPE:
+ ret = fg_get_battery_type();
+ break;
+
+ case FG_CHECK_STATUS:
+ ret = fg_check_status_reg();
+ break;
+
+ case FG_VF_SOC:
+ ret = fg_read_vfsoc();
+ break;
+
+ case FG_VOLTAGE_NOW:
+ ret = fg_read_voltage_now();
+ break;
+
+ default:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+static int fg_i2c_remove(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ kfree(chip);
+ return 0;
+}
+
+static int
+fg_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct max17042_chip *chip;
+
+ if (!client->dev.platform_data) {
+ pr_err("%s: No platform data\n", __func__);
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pr_info("failed to allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+ i2c_set_clientdata(client, chip);
+
+ pr_info("%s: fuelgauge driver loading\n", __func__);
+
+ max17042_chip_data = chip;
+ /* rest of the initialisation goes here. */
+ pr_info("Fuel gauge attach success!!!\n");
+
+ fg_i2c_client = client;
+ fg_set_battery_type();
+
+ /* Init parameters to prevent wrong compensation. */
+ chip->info.prev_fullcap = fg_read_register(FULLCAP_REG);
+ chip->info.prev_vffcap = fg_read_register(FULLCAP_NOM_REG);
+ /* Init FullCAP of first full charging. */
+ chip->info.full_charged_cap = chip->info.prev_fullcap;
+
+ chip->info.prev_vfsoc = fg_read_vfsoc();
+ chip->info.prev_repsoc = fg_read_soc();
+ chip->info.prev_remcap = fg_read_register(REMCAP_REP_REG);
+ chip->info.prev_mixcap = fg_read_register(REMCAP_MIX_REG);
+ chip->info.prev_vfocv = fg_read_register(VFOCV_REG);
+ chip->info.prev_fullcapacity = chip->info.prev_fullcap;
+ chip->info.prev_vfcapacity = chip->info.prev_vffcap;
+
+ mutex_init(&chip->fg_lock);
+
+ fg_alert_init();
+ fg_read_model_data();
+ fg_periodic_read();
+ return 0;
+}
+
+static const struct i2c_device_id fg_device_id[] = {
+ {"fuelgauge", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, fg_device_id);
+
+static struct i2c_driver fg_i2c_driver = {
+ .driver = {
+ .name = "fuelgauge",
+ .owner = THIS_MODULE,
+ },
+ .probe = fg_i2c_probe,
+ .remove = fg_i2c_remove,
+ .id_table = fg_device_id,
+};
+
+static int __init max17042_init(void)
+{
+ return i2c_add_driver(&fg_i2c_driver);
+}
+subsys_initcall(max17042_init);
+
+static void __exit max17042_exit(void)
+{
+ i2c_del_driver(&fg_i2c_driver);
+}
+module_exit(max17042_exit);
+
+MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/power/max17042_fuelgauge_u1.c b/drivers/power/max17042_fuelgauge_u1.c
new file mode 100644
index 0000000..1434c72
--- /dev/null
+++ b/drivers/power/max17042_fuelgauge_u1.c
@@ -0,0 +1,1022 @@
+/*
+ * max17042-fuelgauge.c
+ * fuel-gauge systems for lithium-ion (Li+) batteries
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ *
+ * based on max17040_battery.c
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/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/power/max17042_fuelgauge_u1.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+
+static ssize_t sec_fg_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static ssize_t sec_fg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+struct max17042_chip {
+ struct i2c_client *client;
+ struct delayed_work work;
+ struct power_supply battery;
+ struct max17042_platform_data *pdata;
+
+ int vcell; /* battery voltage */
+ int avgvcell; /* average battery voltage */
+ int vfocv; /* calculated battery voltage */
+ int soc; /* battery capacity */
+ int raw_soc; /* fuel gauge raw data */
+ int temperature;
+ int fuel_alert_soc; /* fuel alert threshold */
+ bool is_fuel_alerted; /* fuel alerted */
+ struct wake_lock fuel_alert_wake_lock;
+ bool is_enable; /* can be fuel guage enable */
+
+#ifdef RECAL_SOC_FOR_MAXIM
+ int cnt;
+ int recalc_180s;
+ int boot_cnt;
+#endif
+};
+
+static int max17042_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ switch (val->intval) {
+ case 0: /*vcell */
+ val->intval = chip->vcell;
+ break;
+ case 1: /*vfocv */
+ val->intval = chip->vfocv;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = chip->avgvcell;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ switch (val->intval) {
+ case 0: /*normal soc */
+ val->intval = chip->soc;
+ break;
+ case 1: /*raw soc */
+ val->intval = chip->raw_soc;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = chip->temperature / 100;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+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)
+ dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n",
+ __func__, ret, reg);
+
+ 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)
+ dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n",
+ __func__, ret, reg);
+
+ return ret;
+}
+
+static void max17042_write_reg_array(struct i2c_client *client,
+ struct max17042_reg_data *data, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i += 3)
+ max17042_write_reg(client, (data + i)->reg_addr,
+ ((u8 *) (data + i)) + 1);
+}
+
+static void max17042_init_regs(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ /* struct max17042_platform_data *pdata = client->dev.platform_data; */
+
+ dev_info(&client->dev, "%s\n", __func__);
+
+ if (chip->is_enable) {
+ /* max17042_write_reg_array(client, pdata->init,
+ pdata->init_size); */
+
+ 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_alert_init(struct i2c_client *client)
+{
+ struct max17042_platform_data *pdata = client->dev.platform_data;
+
+ dev_info(&client->dev, "%s\n", __func__);
+
+ max17042_write_reg_array(client, pdata->alert_init,
+ pdata->alert_init_size);
+}
+
+static int max17042_read_vfocv(struct i2c_client *client)
+{
+ u8 data[2];
+ u32 vfocv = 0;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ if (chip->is_enable) {
+ if (max17042_read_reg(client, MAX17042_REG_VFOCV, data) < 0)
+ return -1;
+
+ vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000;
+
+ chip->vfocv = vfocv;
+
+ return vfocv;
+ } else
+ return 4000;
+
+}
+
+#if 0
+static int max17042_read_vfsoc(struct i2c_client *client)
+{
+ u8 data[2];
+ u32 vfsoc = 0;
+
+#ifndef NO_READ_I2C_FOR_MAXIM
+ if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0)
+ return -1;
+
+ vfsoc = data[1];
+
+ return vfsoc;
+#else
+ return 100;
+#endif
+}
+#else
+static void max17042_get_soc(struct i2c_client *client);
+static int max17042_read_vfsoc(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ max17042_get_soc(client);
+
+ return chip->soc;
+}
+#endif
+
+static void max17042_reset_soc(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (chip->is_enable) {
+ dev_info(&client->dev, "%s : Before quick-start - "
+ "VfOCV(%d), VfSOC(%d)\n",
+ __func__, max17042_read_vfocv(client),
+ max17042_read_vfsoc(client));
+
+ if (max17042_read_reg(client, MAX17042_REG_MISCCFG, data) < 0)
+ return;
+
+ /* Set bit10 makes quick start */
+ data[1] |= (0x1 << 2);
+ max17042_write_reg(client, MAX17042_REG_MISCCFG, data);
+
+ msleep(500);
+
+ dev_info(&client->dev, "%s : After quick-start - "
+ "VfOCV(%d), VfSOC(%d)\n",
+ __func__, max17042_read_vfocv(client),
+ max17042_read_vfsoc(client));
+ }
+
+ return;
+}
+
+static void max17042_get_vcell(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (chip->is_enable) {
+ if (max17042_read_reg(client, MAX17042_REG_VCELL, data) < 0)
+ return;
+
+ chip->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625;
+
+ if (max17042_read_reg(client, MAX17042_REG_AVGVCELL, data) < 0)
+ return;
+
+ chip->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625;
+ } else {
+ chip->vcell = 4000000;
+ chip->avgvcell = 4000000;
+ }
+}
+
+static int max17042_recalc_soc(struct i2c_client *client, int boot_cnt)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ struct power_supply *psy = power_supply_get_by_name("battery");
+ union power_supply_propval value;
+ u8 data[2];
+ u16 temp_vfocv = 0;
+ int soc;
+
+ if (chip->is_enable) {
+ /* AverageVcell : 175.8ms * 256 = 45s sampling */
+ if (boot_cnt < 4) /* 30s using VCELL */
+ max17042_read_reg(client, MAX17042_REG_VCELL, data);
+ else
+ max17042_read_reg(client, MAX17042_REG_AVGVCELL, data);
+ temp_vfocv = (data[1] << 8);
+ temp_vfocv |= data[0];
+
+ if (psy != NULL) {
+ psy->get_property(psy,
+ POWER_SUPPLY_PROP_ONLINE, &value);
+
+ if (value.intval == 0)
+ temp_vfocv = temp_vfocv + 0x0380; /* +70mV */
+ else
+ temp_vfocv = temp_vfocv - 0x0380; /* -70mV */
+
+ dev_info(&client->dev, "cable = %d, ", value.intval);
+ } else
+ temp_vfocv = temp_vfocv + 0x0380; /* +70mV */
+
+ data[1] = temp_vfocv >> 8;
+ data[0] = 0x00FF & temp_vfocv;
+ dev_info(&client->dev, "forced write to vfocv %d mV\n",
+ (temp_vfocv >> 4) * 125 / 100);
+ max17042_write_reg(client, MAX17042_REG_VFOCV, data);
+
+ msleep(200);
+
+ max17042_read_reg(client, MAX17042_REG_SOC_VF, data);
+ soc = min((int)data[1], 100);
+
+ max17042_read_reg(client, MAX17042_REG_VCELL, data);
+ dev_info(&client->dev, "new vcell = %d, vfocv = %d, soc = %d\n",
+ ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000,
+ max17042_read_vfocv(client), soc);
+ } else
+ soc = 70;
+
+ return soc;
+}
+
+static void max17042_get_soc(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ int soc;
+ int diff = 0;
+
+ if (chip->is_enable) {
+ if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0)
+ return;
+ dev_info(&chip->client->dev, "%s : soc(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ soc = (data[1] * 100) + (data[0] * 100 / 256);
+
+ chip->raw_soc = min(soc / 100, 100);
+
+#ifdef RECAL_SOC_FOR_MAXIM
+ if (chip->pdata->need_soc_recal()) {
+ dev_info(&client->dev,
+ "%s : recalculate soc\n", __func__);
+
+ /*modified 3.6V cut-off */
+ /*raw 3% ~ 95% */
+ soc = (soc < 300) ? 0 : ((soc - 300) * 100 / 9200) + 1;
+
+ if (chip->boot_cnt < 4)
+ chip->boot_cnt++;
+
+ dev_info(&client->dev,
+ "vcell = %d, vfocv = %d, soc = %d\n",
+ chip->vcell, max17042_read_vfocv(client), soc);
+
+ if (soc < 5) {
+ dev_info(&client->dev,
+ "recalc force soc = %d, diff = %d!\n",
+ soc, diff);
+ chip->recalc_180s = 1;
+ }
+
+ /*when using fuelgauge level, diff is calculated */
+ if (chip->recalc_180s == 0 && chip->boot_cnt != 1) {
+ if (chip->soc > soc)
+ diff = chip->soc - soc;
+ else
+ diff = soc - chip->soc;
+ } else /*during recalc, diff is not valid */
+ diff = 0;
+
+ if (diff > 10) {
+ dev_info(&client->dev,
+ "recalc 180s soc = %d, diff = %d!\n",
+ soc, diff);
+ chip->recalc_180s = 1;
+ }
+
+ if (chip->recalc_180s == 1) {
+ if (chip->cnt < 18) {
+ chip->cnt++;
+ soc = max17042_recalc_soc(client,
+ chip->boot_cnt);
+ } else {
+ chip->recalc_180s = 0;
+ chip->cnt = 0;
+ }
+ }
+ } else {
+ /*modified 3.4V cut-off */
+ /*raw 1.6% ~ 97.6% */
+ soc = (soc > 100) ? ((soc - 60) * 100 / 9700) : 0;
+ /*raw 1.5% ~ 95% */
+ /*soc = (soc < 150) ? 0 : ((soc - 150) * 100 / 9350) + 1; */
+ }
+#else
+ /* adjusted soc by adding 0.45 */
+ soc += 45;
+ soc /= 100;
+#endif
+ } else
+ soc = 70;
+
+ soc = min(soc, 100);
+
+ chip->soc = soc;
+
+ dev_info(&client->dev, "%s : use raw (%d), soc (%d)\n",
+ __func__, chip->raw_soc, soc);
+}
+
+static void max17042_get_temperature(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ s32 temper = 0;
+
+ if (chip->is_enable) {
+ if (max17042_read_reg(client, MAX17042_REG_TEMPERATURE, data) < 0)
+ return;
+
+ /* data[] store 2's compliment format number */
+ if (data[1] & (0x1 << 7)) {
+ /* Negative */
+ temper = ((~(data[1])) & 0xFF) + 1;
+ temper *= (-1000);
+ } else {
+ temper = data[1] & 0x7F;
+ temper *= 1000;
+ temper += data[0] * 39 / 10;
+ }
+
+ dev_dbg(&client->dev, "%s: MAX17042 Temperature = %d\n",
+ __func__, chip->temperature);
+ } else
+ temper = 40;
+
+ chip->temperature = temper;
+}
+
+static void max17042_get_version(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (chip->is_enable) {
+ if (max17042_read_reg(client, MAX17042_REG_VERSION, data) < 0)
+ return;
+
+ dev_info(&client->dev, "MAX17042 Fuel-Gauge Ver %d%d\n",
+ data[0], data[1]);
+ }
+}
+
+static int max17042_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+ u8 data[2];
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP:
+ if (chip->pdata->enable_gauging_temperature &&
+ chip->is_enable) {
+ data[0] = 0;
+ data[1] = val->intval;
+ max17042_write_reg(chip->client,
+ MAX17042_REG_TEMPERATURE, data);
+ } else
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void max17042_work(struct work_struct *work)
+{
+ struct max17042_chip *chip;
+
+ chip = container_of(work, struct max17042_chip, work.work);
+
+ max17042_get_vcell(chip->client);
+ max17042_read_vfocv(chip->client);
+ max17042_get_soc(chip->client);
+
+ if (chip->pdata->enable_gauging_temperature)
+ max17042_get_temperature(chip->client);
+
+ /* polling check for fuel alert for booting in low battery*/
+ if (chip->raw_soc < chip->fuel_alert_soc) {
+ if (!(chip->is_fuel_alerted) &&
+ chip->pdata->low_batt_cb) {
+ wake_lock(&chip->fuel_alert_wake_lock);
+ chip->pdata->low_batt_cb();
+ chip->is_fuel_alerted = true;
+
+ dev_info(&chip->client->dev,
+ "fuel alert activated by polling check (raw:%d)\n",
+ chip->raw_soc);
+ } else
+ dev_info(&chip->client->dev,
+ "fuel alert already activated (raw:%d)\n",
+ chip->raw_soc);
+ } else if (chip->raw_soc == chip->fuel_alert_soc) {
+ if (chip->is_fuel_alerted) {
+ wake_unlock(&chip->fuel_alert_wake_lock);
+ chip->is_fuel_alerted = false;
+
+ dev_info(&chip->client->dev,
+ "fuel alert deactivated by polling check (raw:%d)\n",
+ chip->raw_soc);
+ }
+ }
+
+#ifdef LOG_REG_FOR_MAXIM
+ {
+ int reg;
+ int i;
+ u8 data[2];
+ u8 buf[1024];
+
+ i = 0;
+ for (reg = 0; reg < 0x50; reg++) {
+ max17042_read_reg(chip->client, reg, data);
+ i += sprintf(buf + i, "0x%02x%02xh,", data[1], data[0]);
+ }
+ printk(KERN_INFO "%s", buf);
+
+ i = 0;
+ for (reg = 0xe0; reg < 0x100; reg++) {
+ max17042_read_reg(chip->client, reg, data);
+ i += sprintf(buf + i, "0x%02x%02xh,", data[1], data[0]);
+ }
+ printk(KERN_INFO "%s", buf);
+ }
+#endif
+
+ schedule_delayed_work(&chip->work, MAX17042_LONG_DELAY);
+}
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+#define SEC_FG_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, \
+ .mode = 0664 }, \
+ .show = sec_fg_show_property, \
+ .store = sec_fg_store, \
+}
+
+static struct device_attribute sec_fg_attrs[] = {
+ SEC_FG_ATTR(fg_reset_soc),
+ SEC_FG_ATTR(fg_read_soc),
+};
+
+enum {
+ FG_RESET_SOC = 0,
+ FG_READ_SOC,
+};
+
+static ssize_t sec_fg_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+
+ int i = 0;
+ const ptrdiff_t off = attr - sec_fg_attrs;
+
+ switch (off) {
+ case FG_READ_SOC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ max17042_read_vfsoc(chip->client));
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t sec_fg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+
+ int x = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - sec_fg_attrs;
+
+ switch (off) {
+ case FG_RESET_SOC:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ if (x == 1)
+ max17042_reset_soc(chip->client);
+ ret = count;
+ }
+#if 1
+/* tester requested to sustain unity with parental models but
+ keep source code for later */
+ {
+ struct power_supply *psy =
+ power_supply_get_by_name("battery");
+ union power_supply_propval value;
+
+ if (!psy) {
+ pr_err("%s: fail to get battery ps\n", __func__);
+ return -ENODEV;
+ }
+
+ value.intval = 0; /* dummy value */
+ psy->set_property(psy,
+ POWER_SUPPLY_PROP_CAPACITY, &value);
+ }
+#endif
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int fuelgauge_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 fg_attrs_failed;
+ }
+ goto succeed;
+
+fg_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sec_fg_attrs[i]);
+succeed:
+ return rc;
+}
+
+static bool max17042_check_status(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ bool ret = false;
+
+ if (chip->is_enable) {
+ /* check if Smn was generated */
+ if (max17042_read_reg(client, MAX17042_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;
+ max17042_write_reg(client, MAX17042_REG_STATUS, data);
+ msleep(200);
+ }
+ } else
+ ret = true;
+
+ return ret;
+}
+
+static irqreturn_t max17042_irq_thread(int irq, void *irq_data)
+{
+ u8 data[2];
+ bool max17042_alert_status = false;
+ struct max17042_chip *chip = irq_data;
+
+ /* update SOC */
+ max17042_get_soc(chip->client);
+
+ max17042_alert_status = max17042_check_status(chip->client);
+
+ if (max17042_alert_status /* && !(chip->is_fuel_alerted)*/) {
+ if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data)
+ < 0)
+ return IRQ_HANDLED;
+
+ data[1] |= (0x1 << 3);
+
+ if (chip->pdata->low_batt_cb && !(chip->is_fuel_alerted)) {
+ wake_lock(&chip->fuel_alert_wake_lock);
+ chip->pdata->low_batt_cb();
+ chip->is_fuel_alerted = true;
+ } else
+ dev_err(&chip->client->dev,
+ "failed to call low_batt_cb()\n");
+
+ max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data);
+
+ dev_info(&chip->client->dev,
+ "%s : low batt alerted!! config_reg(%02x%02x)\n",
+ __func__, data[1], data[0]);
+ } else if (!max17042_alert_status /* && (chip->is_fuel_alerted)*/) {
+ if (chip->is_fuel_alerted)
+ wake_unlock(&chip->fuel_alert_wake_lock);
+ chip->is_fuel_alerted = false;
+
+ if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data)
+ < 0)
+ return IRQ_HANDLED;
+
+ data[1] &= (~(0x1 << 3));
+
+ max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data);
+
+ dev_info(&chip->client->dev,
+ "%s : low batt released!! config_reg(%02x%02x)\n",
+ __func__, data[1], data[0]);
+ }
+
+ max17042_read_reg(chip->client, MAX17042_REG_VCELL, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_VCELL(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ max17042_read_reg(chip->client, MAX17042_REG_TEMPERATURE, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_TEMPERATURE(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_CONFIG(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ max17042_read_reg(chip->client, MAX17042_REG_VFOCV, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_VFOCV(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ max17042_read_reg(chip->client, MAX17042_REG_SOC_VF, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_SOC_VF(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ dev_err(&chip->client->dev,
+ "%s : PMIC IRQ (%d), FUEL GAUGE IRQ (%d)\n",
+ __func__, gpio_get_value(GPIO_PMIC_IRQ),
+ gpio_get_value(chip->pdata->alert_gpio));
+
+#if 0
+ **max17042_read_reg(chip->client, MAX17042_REG_STATUS, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_STATUS(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_VALRT_TH, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_VALRT_TH(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_TALRT_TH, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_TALRT_TH(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_SALRT_TH, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_SALRT_TH(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_AVGVCELL, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_AVGVCELL(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_VERSION, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_VERSION(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_LEARNCFG, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_LEARNCFG(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_MISCCFG, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_MISCCFG(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_CGAIN, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_CGAIN(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ **max17042_read_reg(chip->client, MAX17042_REG_RCOMP, data);
+ dev_info(&chip->client->dev, "%s : MAX17042_REG_RCOMP(%02x%02x)\n",
+ __func__, data[1], data[0]);
+#endif
+
+ return IRQ_HANDLED;
+}
+
+static int max17042_irq_init(struct max17042_chip *chip)
+{
+ int ret;
+ u8 data[2];
+
+ /* 1. Set max17042 alert configuration. */
+ max17042_alert_init(chip->client);
+
+ chip->is_fuel_alerted = false;
+
+ /* 2. Request irq */
+ if (chip->pdata->alert_irq) {
+ ret = request_threaded_irq(chip->pdata->alert_irq, NULL,
+ max17042_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "max17042 fuel alert", chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "failed to reqeust IRQ\n");
+ return ret;
+ }
+
+ ret = enable_irq_wake(chip->pdata->alert_irq);
+ if (ret < 0)
+ dev_err(&chip->client->dev,
+ "failed to enable wakeup src %d\n", ret);
+ }
+
+ if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data)
+ < 0)
+ return -1;
+
+ /*Enable Alert (Aen = 1) */
+ data[0] |= (0x1 << 2);
+
+ max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data);
+
+ dev_info(&chip->client->dev, "%s : config_reg(%02x%02x) irq(%d)\n",
+ __func__, data[1], data[0], chip->pdata->alert_irq);
+
+ return 0;
+}
+
+static int __devinit max17042_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct max17042_chip *chip;
+ int i;
+ struct max17042_reg_data *data;
+ int ret;
+ u8 i2c_data[2];
+
+ dev_info(&client->dev, "%s: MAX17042 Driver Loading\n", __func__);
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+
+ chip->battery.name = "fuelgauge";
+ chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->battery.get_property = max17042_get_property;
+ chip->battery.set_property = max17042_set_property;
+ chip->battery.properties = max17042_battery_props;
+ chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props);
+
+#ifdef RECAL_SOC_FOR_MAXIM
+ chip->cnt = 0;
+ chip->recalc_180s = 0;
+ chip->boot_cnt = 0;
+#endif
+
+ ret = power_supply_register(&client->dev, &chip->battery);
+ if (ret) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ kfree(chip);
+ return ret;
+ }
+
+ if (max17042_read_reg(client, MAX17042_REG_VERSION, i2c_data) < 0)
+ chip->is_enable = false;
+ else
+ chip->is_enable = true;
+
+ dev_info(&client->dev, "%s : is enable (%d)\n",
+ __func__, chip->is_enable);
+
+ /* initialize fuel gauge registers */
+ max17042_init_regs(client);
+
+ if (chip->is_enable) {
+ /* register low batt intr */
+ chip->pdata->alert_irq = gpio_to_irq(chip->pdata->alert_gpio);
+
+ wake_lock_init(&chip->fuel_alert_wake_lock, WAKE_LOCK_SUSPEND,
+ "fuel_alerted");
+
+ data = chip->pdata->alert_init;
+ for (i = 0; i < chip->pdata->alert_init_size; i += 3)
+ if ((data + i)->reg_addr ==
+ MAX17042_REG_SALRT_TH)
+ chip->fuel_alert_soc =
+ (data + i)->reg_data1;
+
+ dev_info(&client->dev, "fuel alert soc (%d)\n",
+ chip->fuel_alert_soc);
+
+ ret = max17042_irq_init(chip);
+ if (ret)
+ goto err_kfree;
+ }
+
+ max17042_get_version(client);
+
+ /* create fuelgauge attributes */
+ fuelgauge_create_attrs(chip->battery.dev);
+
+ INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17042_work);
+ schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY);
+
+ dev_info(&client->dev, "%s: MAX17042 Driver Loaded\n", __func__);
+
+ return 0;
+
+err_kfree:
+ wake_lock_destroy(&chip->fuel_alert_wake_lock);
+ kfree(chip);
+ return ret;
+}
+
+static int __devexit max17042_remove(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ power_supply_unregister(&chip->battery);
+ cancel_delayed_work(&chip->work);
+ wake_lock_destroy(&chip->fuel_alert_wake_lock);
+ kfree(chip);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int max17042_suspend(struct i2c_client *client, pm_message_t state)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&chip->work);
+ return 0;
+}
+
+static int max17042_resume(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY);
+ return 0;
+}
+
+#else
+
+#define max17042_suspend NULL
+#define max17042_resume NULL
+
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id max17042_id[] = {
+ {"max17042", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, max17042_id);
+
+static struct i2c_driver max17042_i2c_driver = {
+ .driver = {
+ .name = "max17042",
+ },
+ .probe = max17042_probe,
+ .remove = __devexit_p(max17042_remove),
+ .suspend = max17042_suspend,
+ .resume = max17042_resume,
+ .id_table = max17042_id,
+};
+
+static int __init max17042_init(void)
+{
+ return i2c_add_driver(&max17042_i2c_driver);
+}
+
+module_init(max17042_init);
+
+static void __exit max17042_exit(void)
+{
+ i2c_del_driver(&max17042_i2c_driver);
+}
+
+module_exit(max17042_exit);
+
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max17042_fuelgauge_u1_kor.c b/drivers/power/max17042_fuelgauge_u1_kor.c
new file mode 100644
index 0000000..a759005
--- /dev/null
+++ b/drivers/power/max17042_fuelgauge_u1_kor.c
@@ -0,0 +1,855 @@
+/*
+ * max17042-fuelgauge.c
+ * fuel-gauge systems for lithium-ion (Li+) batteries
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ *
+ * based on max17040_battery.c
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/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/power/max17042_fuelgauge_u1.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+
+static ssize_t sec_fg_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static ssize_t sec_fg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+struct max17042_chip {
+ struct i2c_client *client;
+ struct delayed_work work;
+ struct power_supply battery;
+ struct max17042_platform_data *pdata;
+
+ int vcell; /* battery voltage */
+ int avgvcell; /* average battery voltage */
+ int vfocv; /* calculated battery voltage */
+ int soc; /* battery capacity */
+ int raw_soc; /* fuel gauge raw data */
+ int config;
+ int rcomp;
+ int status;
+ int temperature;
+ bool is_fuel_alerted; /* fuel alerted */
+};
+
+static int max17042_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ switch (val->intval) {
+ case 0: /*vcell */
+ val->intval = chip->vcell;
+ break;
+ case 1: /*vfocv */
+ val->intval = chip->vfocv;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = chip->avgvcell;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ switch (val->intval) {
+ case 0: /*normal soc */
+ val->intval = chip->soc;
+ break;
+ case 1: /*raw soc */
+ val->intval = chip->raw_soc;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = chip->temperature / 100; /* result unit 0.1'C */
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+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)
+ dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n",
+ __func__, ret, reg);
+
+ 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)
+ dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n",
+ __func__, ret, reg);
+
+ return ret;
+}
+
+static void max17042_write_reg_array(struct i2c_client *client,
+ struct max17042_reg_data *data, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i += 3)
+ max17042_write_reg(client, (data + i)->reg_addr,
+ ((u8 *) (data + i)) + 1);
+}
+
+static void max17042_init_regs(struct i2c_client *client)
+{
+ u8 data[2];
+ /*struct max17042_platform_data *pdata = client->dev.platform_data; */
+
+ dev_info(&client->dev, "%s\n", __func__);
+
+/* max17042_write_reg_array(client, pdata->init,
+ pdata->init_size);*/
+
+ 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_alert_init(struct i2c_client *client)
+{
+ struct max17042_platform_data *pdata = client->dev.platform_data;
+
+ dev_info(&client->dev, "%s\n", __func__);
+
+ max17042_write_reg_array(client, pdata->alert_init,
+ pdata->alert_init_size);
+}
+
+static int max17042_read_vfocv(struct i2c_client *client)
+{
+ u8 data[2];
+ u32 vfocv = 0;
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ if (max17042_read_reg(client, MAX17042_REG_VFOCV, data) < 0)
+ return -1;
+
+ vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000;
+
+ chip->vfocv = vfocv;
+
+ return vfocv;
+}
+
+static int max17042_read_vfsoc(struct i2c_client *client)
+{
+ u8 data[2];
+ u32 vfsoc = 0;
+
+ if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0)
+ return -1;
+
+ vfsoc = data[1];
+
+ return vfsoc;
+}
+
+static void max17042_reset_soc(struct i2c_client *client)
+{
+ u8 data[2];
+
+ dev_info(&client->dev, "%s : Before quick-start - "
+ "VfOCV(%d), VfSOC(%d)\n",
+ __func__, max17042_read_vfocv(client),
+ max17042_read_vfsoc(client));
+
+ if (max17042_read_reg(client, MAX17042_REG_MISCCFG, data) < 0)
+ return;
+
+ /* Set bit10 makes quick start */
+ data[1] |= (0x1 << 2);
+ max17042_write_reg(client, MAX17042_REG_MISCCFG, data);
+
+ msleep(500);
+
+ dev_info(&client->dev, "%s : After quick-start - "
+ "VfOCV(%d), VfSOC(%d)\n",
+ __func__, max17042_read_vfocv(client),
+ max17042_read_vfsoc(client));
+
+#if 0
+ data[0] = 0x0F;
+ data[1] = 0x00;
+ max17042_write_reg(client, 0x60, data);
+#endif
+
+ return;
+}
+
+static void max17042_get_vcell(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (max17042_read_reg(client, MAX17042_REG_VCELL, data) < 0)
+ return;
+
+ chip->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625;
+
+ if (max17042_read_reg(client, MAX17042_REG_AVGVCELL, data) < 0)
+ return;
+
+ chip->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625;
+
+ /* printk(KERN_ERR "%s : vcell = %dmV\n", __func__, chip->vcell); */
+}
+
+static void max17042_get_config(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) < 0)
+ return;
+
+ chip->config = (data[1] << 8) | data[0];
+
+ /* printk(KERN_ERR "%s : %x, %x, config = 0x%x\n", __func__,
+ data[1], data[0], chip->config); */
+}
+
+static void max17042_get_rcomp(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (max17042_read_reg(client, MAX17042_REG_RCOMP, data) < 0)
+ return;
+
+ chip->rcomp = (data[1] << 8) | data[0];
+
+ /* printk(KERN_ERR "%s : %x, %x, rcomp = 0x%x\n", __func__,
+ data[1], data[0], chip->rcomp); */
+}
+
+static void max17042_set_rcomp(struct i2c_client *client, u16 rcomp)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ data[0] = rcomp & 0xff;
+ data[1] = rcomp >> 8;
+ if (max17042_write_reg(client, MAX17042_REG_RCOMP, data) < 0)
+ return;
+}
+
+static void max17042_get_status(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+
+ if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0)
+ return;
+
+ chip->status = (data[1] << 8) | data[0];
+
+ /* printk(KERN_ERR "%s : %x, %x = status = 0x%x\n", __func__,
+ data[1], data[0], chip->status); */
+}
+
+static void max17042_get_soc(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ unsigned int soc, psoc;
+ int temp_soc;
+ static int fg_zcount;
+
+ if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0)
+ return;
+
+ psoc = (data[1]*100)+(data[0]*100/256);
+ chip->raw_soc = psoc;
+
+ if (psoc > 100) {
+ temp_soc = ((psoc-60)*10000)/9700;
+ /* under 160(psoc), 0% */
+ } else
+ temp_soc = 0;
+
+ soc = temp_soc/100;
+ soc = min(soc, (uint)100);
+
+ if (soc == 0) {
+ fg_zcount++;
+ if (fg_zcount >= 3) {
+ /* pr_info("[fg] real 0%\n"); */
+ soc = 0;
+ fg_zcount = 0;
+ } else
+ soc = chip->soc;
+ } else
+ fg_zcount = 0;
+
+ chip->soc = soc;
+
+ /* pr_info("soc = %d%%, raw_soc = %d\n",
+ chip->soc, chip->raw_soc); */
+}
+
+static void max17042_get_temperature(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+ u8 data[2];
+ s32 temper = 0;
+
+ if (max17042_read_reg(client, MAX17042_REG_TEMPERATURE, data) < 0)
+ return;
+
+ /* data[] store 2's compliment format number */
+ if (data[1] & (0x1 << 7)) {
+ /* Negative */
+ temper = ((~(data[1])) & 0xFF) + 1;
+ temper *= (-1000);
+ } else {
+ temper = data[1] & 0x7F;
+ temper *= 1000;
+ temper += data[0] * 39 / 10;
+ }
+
+ dev_dbg(&client->dev, "%s: MAX17042 Temperature = %d\n",
+ __func__, chip->temperature);
+
+ chip->temperature = temper;
+}
+
+static void max17042_get_version(struct i2c_client *client)
+{
+ u8 data[2];
+
+ if (max17042_read_reg(client, MAX17042_REG_VERSION, data) < 0)
+ return;
+
+ dev_info(&client->dev, "MAX17042 Fuel-Gauge Ver %d%d\n",
+ data[0], data[1]);
+}
+
+static void max17042_disable_intr(struct i2c_client *client)
+{
+ u8 data[2];
+
+ pr_info("%s :\n", __func__);
+
+ if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) < 0) {
+ dev_err(&client->dev, "%s : fail to read reg\n", __func__);
+ return;
+ }
+ data[0] &= (~(0x1 << 2));
+
+ max17042_write_reg(client, MAX17042_REG_CONFIG, data);
+}
+
+static void max17042_enable_intr(struct i2c_client *client)
+{
+ u8 data[2];
+
+ pr_info("%s :\n", __func__);
+
+ if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) < 0) {
+ dev_err(&client->dev, "%s : fail to read reg\n", __func__);
+ return;
+ }
+ data[0] |= (0x1 << 2);
+
+ max17042_write_reg(client, MAX17042_REG_CONFIG, data);
+}
+
+static void max17042_clear_intr(struct i2c_client *client)
+{
+ u8 data[2];
+
+ pr_info("%s :\n", __func__);
+
+ if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) {
+ dev_err(&client->dev, "%s : fail to read reg\n", __func__);
+ return;
+ }
+
+ data[1] &= 0x88;
+ max17042_write_reg(client, MAX17042_REG_STATUS, data);
+ msleep(200);
+}
+
+static int max17042_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+ u8 data[2];
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP:
+ if (chip->pdata->enable_gauging_temperature) {
+ data[0] = 0;
+ data[1] = val->intval;
+ /*
+ pr_info("MAX17042"
+ "Temperature(write) = %d\n",
+ data[1]);
+ */
+ max17042_write_reg(chip->client,
+ MAX17042_REG_TEMPERATURE, data);
+ } else
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void max17042_work(struct work_struct *work)
+{
+ struct max17042_chip *chip;
+
+ chip = container_of(work, struct max17042_chip, work.work);
+
+ max17042_get_vcell(chip->client);
+ max17042_read_vfocv(chip->client);
+ max17042_get_soc(chip->client);
+ max17042_get_status(chip->client);
+
+ if (chip->pdata->enable_gauging_temperature)
+ max17042_get_temperature(chip->client);
+
+ if ((chip->status&0x02) == 0x02) {
+ max17042_get_config(chip->client);
+ pr_info("config 0x%x, status 0x%x, vcell 0x%x, row_soc 0x%x\n",
+ chip->config, chip->status, chip->vcell, chip->raw_soc);
+ /*
+ panic("[1]fuel gauge reset occurred!");
+ */
+ }
+
+ if (chip->is_fuel_alerted) {
+ if (!(chip->status&0x0400)) { /* Smn clear check */
+ chip->is_fuel_alerted = false;
+ max17042_enable_intr(chip->client);
+ max17042_get_config(chip->client);
+ pr_info("config 0x%x, status 0x%x\n",
+ chip->config, chip->status);
+ } else {
+ max17042_get_config(chip->client);
+ if (chip->config&0x4) /* intr disable check */
+ max17042_disable_intr(chip->client);
+ }
+ }
+
+ schedule_delayed_work(&chip->work, MAX17042_LONG_DELAY);
+}
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+#define SEC_FG_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, \
+ .mode = 0664 }, \
+ .show = sec_fg_show_property, \
+ .store = sec_fg_store, \
+}
+
+static struct device_attribute sec_fg_attrs[] = {
+ SEC_FG_ATTR(fg_reset_soc),
+ SEC_FG_ATTR(fg_read_soc),
+};
+
+enum {
+ FG_RESET_SOC = 0,
+ FG_READ_SOC,
+};
+
+static ssize_t sec_fg_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+
+ int i = 0;
+ const ptrdiff_t off = attr - sec_fg_attrs;
+
+ switch (off) {
+ case FG_READ_SOC:
+ max17042_get_soc(chip->client);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ chip->soc);
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t sec_fg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip,
+ battery);
+
+ int x = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - sec_fg_attrs;
+
+ switch (off) {
+ case FG_RESET_SOC:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ if (x == 1)
+ max17042_reset_soc(chip->client);
+ ret = count;
+ }
+#if 1
+/* tester requested to sustain unity with parental models but
+ keep source code for later */
+ {
+ struct power_supply *psy =
+ power_supply_get_by_name("battery");
+ union power_supply_propval value;
+
+ if (!psy) {
+ pr_err("%s: fail to get battery ps\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ value.intval = 0; /* dummy value */
+ psy->set_property(psy,
+ POWER_SUPPLY_PROP_CAPACITY, &value);
+ }
+#endif
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int fuelgauge_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 fg_attrs_failed;
+ }
+ goto succeed;
+
+fg_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sec_fg_attrs[i]);
+succeed:
+ return rc;
+}
+
+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 -1;
+
+ 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;
+ max17042_write_reg(client, MAX17042_REG_STATUS, data);
+ msleep(200);
+ }
+
+ return ret;
+}
+
+static irqreturn_t max17042_irq_thread(int irq, void *irq_data)
+{
+ bool max17042_alert_status = false;
+ struct max17042_chip *chip = irq_data;
+
+ max17042_get_soc(chip->client);
+ max17042_alert_status = max17042_check_status(chip->client);
+
+ if (max17042_alert_status) {
+ chip->is_fuel_alerted = true;
+ if (chip->pdata->low_batt_cb)
+ chip->pdata->low_batt_cb();
+ max17042_disable_intr(chip->client);
+ max17042_get_config(chip->client);
+ max17042_get_status(chip->client);
+ pr_info("status = 0x%x\n", chip->status);
+ } else {
+ printk(KERN_ERR "fuelguage invalid alert!\n");
+ }
+
+ dev_info(&chip->client->dev,
+ "%s : PMIC IRQ (%d), FUEL GAUGE IRQ (%d),"
+ "STATUS (0x%x), CONFIG (0x%x)\n",
+ __func__, gpio_get_value(GPIO_PMIC_IRQ),
+ gpio_get_value(chip->pdata->alert_gpio),
+ chip->status, chip->config);
+
+ msleep(200);
+
+ return IRQ_HANDLED;
+}
+
+static int max17042_irq_init(struct max17042_chip *chip)
+{
+ int ret;
+ u8 data[2];
+
+ /* 1. Set max17042 alert configuration. */
+ max17042_alert_init(chip->client);
+
+ chip->is_fuel_alerted = false;
+
+ /* 2. Request irq */
+ if (chip->client->irq) {
+ ret = request_threaded_irq(chip->client->irq, NULL,
+ max17042_irq_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "max17042 fuel alert", chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "failed to reqeust IRQ\n");
+ return ret;
+ }
+
+ ret = enable_irq_wake(chip->client->irq);
+ if (ret < 0)
+ dev_err(&chip->client->dev,
+ "failed to enable wakeup src %d\n", ret);
+ }
+
+ if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data)
+ < 0)
+ return -1;
+
+ /*Enable Alert (Aen = 1) */
+ data[0] |= (0x1 << 2);
+
+ max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data);
+
+ dev_info(&chip->client->dev, "%s : config_reg(%02x%02x)\n",
+ __func__, data[1], data[0]);
+
+ return 0;
+}
+
+static int __devinit max17042_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct max17042_chip *chip;
+ int ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+
+ chip->battery.name = "fuelgauge",
+ chip->battery.type = POWER_SUPPLY_TYPE_BATTERY,
+ chip->battery.get_property = max17042_get_property,
+ chip->battery.set_property = max17042_set_property,
+ chip->battery.properties = max17042_battery_props,
+ chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props),
+ ret = power_supply_register(&client->dev, &chip->battery);
+ if (ret) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ kfree(chip);
+ return ret;
+ }
+
+ /* initialize fuel gauge registers */
+ max17042_init_regs(client);
+
+ /* check rcomp & config */
+ max17042_get_rcomp(client);
+ max17042_get_config(client);
+ pr_info("rcomp = 0x%04x, config = 0x%04x\n",
+ chip->rcomp, chip->config);
+
+ if (chip->rcomp != MAX17042_NEW_RCOMP) {
+ max17042_set_rcomp(client, MAX17042_NEW_RCOMP);
+ pr_info("set new rcomp = 0x%04x\n", MAX17042_NEW_RCOMP);
+ max17042_get_rcomp(client);
+ pr_info("new rcomp = 0x%04x\n", chip->rcomp);
+ }
+
+ /* register low batt intr */
+ ret = max17042_irq_init(chip);
+ if (ret)
+ goto err_kfree;
+
+ max17042_get_version(client);
+
+ /* create fuelgauge attributes */
+ fuelgauge_create_attrs(chip->battery.dev);
+
+ max17042_get_status(client);
+ if (chip->status & 0x7700) {
+ max17042_clear_intr(client);
+ max17042_get_status(client);
+ pr_info("reset status = 0x%x\n", chip->status);
+ }
+
+ INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17042_work);
+ schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY);
+
+ return 0;
+
+err_kfree:
+ kfree(chip);
+ return ret;
+}
+
+static int __devexit max17042_remove(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ power_supply_unregister(&chip->battery);
+ cancel_delayed_work(&chip->work);
+ kfree(chip);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int max17042_suspend(struct i2c_client *client, pm_message_t state)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&chip->work);
+ return 0;
+}
+
+static int max17042_resume(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY);
+ return 0;
+}
+
+#else
+
+#define max17042_suspend NULL
+#define max17042_resume NULL
+
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id max17042_id[] = {
+ {"max17042", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, max17042_id);
+
+static struct i2c_driver max17042_i2c_driver = {
+ .driver = {
+ .name = "max17042",
+ },
+ .probe = max17042_probe,
+ .remove = __devexit_p(max17042_remove),
+ .suspend = max17042_suspend,
+ .resume = max17042_resume,
+ .id_table = max17042_id,
+};
+
+static int __init max17042_init(void)
+{
+ return i2c_add_driver(&max17042_i2c_driver);
+}
+
+module_init(max17042_init);
+
+static void __exit max17042_exit(void)
+{
+ i2c_del_driver(&max17042_i2c_driver);
+}
+
+module_exit(max17042_exit);
+
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8922_charger_s2plus.c b/drivers/power/max8922_charger_s2plus.c
new file mode 100644
index 0000000..86b8af7
--- /dev/null
+++ b/drivers/power/max8922_charger_s2plus.c
@@ -0,0 +1,320 @@
+/*
+ * max8922-charger.c
+ * MAXIM 8922 charger interface driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Jaecheol kim <jc22.kim@samsung.com>
+ *
+ * modify S2PLUS usb charger based on max8922_charger_u1.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 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/power/max8922_charger_u1.h>
+#include <linux/battery/samsung_battery.h>
+
+struct max8922_info {
+ struct device *dev;
+ struct power_supply psy_bat;
+ struct max8922_platform_data *pdata;
+ bool is_usb_cable;
+ int irq_chg_ing;
+};
+
+static enum power_supply_property max8922_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static inline int max8922_is_charging(struct max8922_info *info)
+{
+ int ta_nconnected = gpio_get_value(info->pdata->gpio_ta_nconnected);
+ int chg_ing = gpio_get_value(info->pdata->gpio_chg_ing);
+
+ dev_info(info->dev, "%s: charging state = 0x%x\n", __func__,
+ (ta_nconnected << 1) | chg_ing);
+
+ /*return (ta_nconnected == 0 && chg_ing == 0); */
+ return (ta_nconnected << 1) | chg_ing;
+}
+
+static int max8922_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8922_info *info =
+ container_of(psy, struct max8922_info, psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ switch (max8922_is_charging(info)) {
+ case 0:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 1:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max8922_enable_charging(struct max8922_info *info, bool enable)
+{
+ int gpio_chg_en = info->pdata->gpio_chg_en;
+ unsigned long flags;
+
+ dev_info(info->dev, "%s: %s charging,%s\n", __func__,
+ enable ? "enable" : "disable",
+ info->is_usb_cable ? "USB" : "TA");
+
+ if (enable) {
+ if (info->is_usb_cable) {
+ /* Charging by USB cable */
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH);
+ } else {
+ /* Charging by TA cable */
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH);
+ mdelay(5);
+
+ local_irq_save(flags);
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW);
+ udelay(300);
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH);
+ local_irq_restore(flags);
+ }
+ } else
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW);
+
+ msleep(300);
+ return max8922_is_charging(info);
+}
+
+static int max8922_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max8922_info *info =
+ container_of(psy, struct max8922_info, psy_bat);
+ bool enable;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */
+ info->is_usb_cable = (val->intval <= CHARGER_USB_CURRENT);
+ break;
+ case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */
+ enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING);
+ max8922_enable_charging(info, enable);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static irqreturn_t max8922_chg_ing_irq(int irq, void *data)
+{
+ struct max8922_info *info = data;
+ int ret = 0;
+
+ dev_info(info->dev, "chg_ing IRQ occurred!\n");
+
+ if (gpio_get_value(info->pdata->gpio_ta_nconnected))
+ return IRQ_HANDLED;
+
+ if (info->pdata->topoff_cb)
+ ret = info->pdata->topoff_cb();
+
+ if (ret) {
+ dev_err(info->dev, "%s: error from topoff_cb(%d)\n", __func__,
+ ret);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static __devinit int max8922_probe(struct platform_device *pdev)
+{
+ struct max8922_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct max8922_info *info;
+ int ret;
+
+ dev_info(&pdev->dev, "%s : MAX8922 Charger 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 = pdata;
+
+ info->psy_bat.name = "max8922-charger",
+ info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ info->psy_bat.properties = max8922_battery_props,
+ info->psy_bat.num_properties = ARRAY_SIZE(max8922_battery_props),
+ info->psy_bat.get_property = max8922_get_property,
+ info->psy_bat.set_property = max8922_set_property,
+ ret = power_supply_register(&pdev->dev, &info->psy_bat);
+ if (ret) {
+ dev_err(info->dev, "Failed to register psy_bat\n");
+ goto err_kfree;
+ }
+
+ if (pdata->cfg_gpio) {
+ ret = pdata->cfg_gpio();
+ if (ret) {
+ dev_err(info->dev, "failed to configure GPIO\n");
+ goto err_kfree;
+ }
+ }
+
+ if (gpio_is_valid(pdata->gpio_chg_en)) {
+ if (!pdata->gpio_chg_en) {
+ dev_err(info->dev, "gpio_chg_en defined as 0\n");
+ WARN_ON(!pdata->gpio_chg_en);
+ ret = -EIO;
+ goto err_kfree;
+ }
+ gpio_request(pdata->gpio_chg_en, "MAX8922 CHG_EN");
+ }
+
+ if (gpio_is_valid(pdata->gpio_chg_ing)) {
+ if (!pdata->gpio_chg_ing) {
+ dev_err(info->dev, "gpio_chg_ing defined as 0\n");
+ WARN_ON(!pdata->gpio_chg_ing);
+ ret = -EIO;
+ goto err_kfree;
+ }
+ gpio_request(pdata->gpio_chg_ing, "MAX8922 CHG_ING");
+ }
+
+ if (gpio_is_valid(pdata->gpio_ta_nconnected)) {
+ if (!pdata->gpio_ta_nconnected) {
+ dev_err(info->dev, "gpio_ta_nconnected defined as 0\n");
+ WARN_ON(!pdata->gpio_ta_nconnected);
+ ret = -EIO;
+ goto err_kfree;
+ }
+ gpio_request(pdata->gpio_ta_nconnected,
+ "MAX8922 TA_nCONNECTED");
+ }
+#if 0
+ info->irq_chg_ing = gpio_to_irq(pdata->gpio_chg_ing);
+
+ ret = request_threaded_irq(info->irq_chg_ing, NULL,
+ max8922_chg_ing_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "chg_ing", info);
+ if (ret)
+ dev_err(&pdev->dev, "%s: fail to request chg_ing IRQ:"
+ " %d: %d\n", __func__, info->irq_chg_ing, ret);
+#endif
+
+ return 0;
+err_kfree:
+ power_supply_unregister(&info->psy_bat);
+ platform_set_drvdata(pdev, NULL);
+ kfree(info);
+ return ret;
+}
+
+static int __devexit max8922_remove(struct platform_device *pdev)
+{
+ struct max8922_info *info = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&info->psy_bat);
+
+ gpio_free(info->pdata->gpio_chg_en);
+ gpio_free(info->pdata->gpio_chg_ing);
+ gpio_free(info->pdata->gpio_ta_nconnected);
+
+ kfree(info);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int max8922_suspend(struct device *dev)
+{
+ struct max8922_info *info = dev_get_drvdata(dev);
+
+ if (info && info->irq_chg_ing)
+ disable_irq(info->irq_chg_ing);
+
+ return 0;
+}
+
+static int max8922_resume(struct device *dev)
+{
+ struct max8922_info *info = dev_get_drvdata(dev);
+
+ if (info && info->irq_chg_ing)
+ enable_irq(info->irq_chg_ing);
+
+ return 0;
+}
+#else
+#define max8922_charger_suspend NULL
+#define max8922_charger_resume NULL
+#endif
+
+static const struct dev_pm_ops max8922_pm_ops = {
+ .suspend = max8922_suspend,
+ .resume = max8922_resume,
+};
+
+static struct platform_driver max8922_driver = {
+ .driver = {
+ .name = "max8922-charger",
+ .owner = THIS_MODULE,
+ .pm = &max8922_pm_ops,
+ },
+ .probe = max8922_probe,
+ .remove = __devexit_p(max8922_remove),
+};
+
+static int __init max8922_init(void)
+{
+ return platform_driver_register(&max8922_driver);
+}
+
+static void __exit max8922_exit(void)
+{
+ platform_driver_register(&max8922_driver);
+}
+
+module_init(max8922_init);
+module_exit(max8922_exit);
+
+MODULE_DESCRIPTION("MAXIM 8922 charger control driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8922_charger_u1.c b/drivers/power/max8922_charger_u1.c
new file mode 100644
index 0000000..5cbbb08
--- /dev/null
+++ b/drivers/power/max8922_charger_u1.c
@@ -0,0 +1,314 @@
+/*
+ * max8922-charger.c
+ * MAXIM 8922 charger interface driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/power/max8922_charger_u1.h>
+
+struct max8922_info {
+ struct device *dev;
+ struct power_supply psy_bat;
+ struct max8922_platform_data *pdata;
+ bool is_usb_cable;
+ int irq_chg_ing;
+};
+
+static enum power_supply_property max8922_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static inline int max8922_is_charging(struct max8922_info *info)
+{
+ int ta_nconnected = gpio_get_value(info->pdata->gpio_ta_nconnected);
+ int chg_ing = gpio_get_value(info->pdata->gpio_chg_ing);
+
+ dev_info(info->dev, "%s: charging state = 0x%x\n", __func__,
+ (ta_nconnected << 1) | chg_ing);
+
+ /*return (ta_nconnected == 0 && chg_ing == 0); */
+ return (ta_nconnected << 1) | chg_ing;
+}
+
+static int max8922_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8922_info *info =
+ container_of(psy, struct max8922_info, psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ switch (max8922_is_charging(info)) {
+ case 0:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 1:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max8922_enable_charging(struct max8922_info *info, bool enable)
+{
+ int gpio_chg_en = info->pdata->gpio_chg_en;
+ unsigned long flags;
+
+ dev_info(info->dev, "%s: %s charging,%s\n", __func__,
+ enable ? "enable" : "disable",
+ info->is_usb_cable ? "USB" : "TA");
+
+ if (enable) {
+ if (info->is_usb_cable) {
+ /* Charging by USB cable */
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH);
+ } else {
+ /* Charging by TA cable */
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH);
+ mdelay(5);
+
+ local_irq_save(flags);
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW);
+ udelay(300);
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH);
+ local_irq_restore(flags);
+ }
+ } else
+ gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW);
+
+ msleep(300);
+ return max8922_is_charging(info);
+}
+
+static int max8922_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max8922_info *info =
+ container_of(psy, struct max8922_info, psy_bat);
+ bool enable;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */
+ info->is_usb_cable = (val->intval <= 450);
+ break;
+ case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */
+ enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING);
+ max8922_enable_charging(info, enable);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static irqreturn_t max8922_chg_ing_irq(int irq, void *data)
+{
+ struct max8922_info *info = data;
+ int ret = 0;
+
+ dev_info(info->dev, "chg_ing IRQ occurred!\n");
+
+ if (gpio_get_value(info->pdata->gpio_ta_nconnected))
+ return IRQ_HANDLED;
+
+ if (info->pdata->topoff_cb)
+ ret = info->pdata->topoff_cb();
+
+ if (ret) {
+ dev_err(info->dev, "%s: error from topoff_cb(%d)\n", __func__,
+ ret);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static __devinit int max8922_probe(struct platform_device *pdev)
+{
+ struct max8922_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct max8922_info *info;
+ int ret;
+
+ dev_info(&pdev->dev, "%s : MAX8922 Charger 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 = pdata;
+
+ info->psy_bat.name = "max8922-charger",
+ info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ info->psy_bat.properties = max8922_battery_props,
+ info->psy_bat.num_properties = ARRAY_SIZE(max8922_battery_props),
+ info->psy_bat.get_property = max8922_get_property,
+ info->psy_bat.set_property = max8922_set_property,
+ ret = power_supply_register(&pdev->dev, &info->psy_bat);
+ if (ret) {
+ dev_err(info->dev, "Failed to register psy_bat\n");
+ goto err_kfree;
+ }
+
+ if (pdata->cfg_gpio) {
+ ret = pdata->cfg_gpio();
+ if (ret) {
+ dev_err(info->dev, "failed to configure GPIO\n");
+ goto err_kfree;
+ }
+ }
+
+ if (gpio_is_valid(pdata->gpio_chg_en)) {
+ if (!pdata->gpio_chg_en) {
+ dev_err(info->dev, "gpio_chg_en defined as 0\n");
+ WARN_ON(!pdata->gpio_chg_en);
+ ret = -EIO;
+ goto err_kfree;
+ }
+ gpio_request(pdata->gpio_chg_en, "MAX8922 CHG_EN");
+ }
+
+ if (gpio_is_valid(pdata->gpio_chg_ing)) {
+ if (!pdata->gpio_chg_ing) {
+ dev_err(info->dev, "gpio_chg_ing defined as 0\n");
+ WARN_ON(!pdata->gpio_chg_ing);
+ ret = -EIO;
+ goto err_kfree;
+ }
+ gpio_request(pdata->gpio_chg_ing, "MAX8922 CHG_ING");
+ }
+
+ if (gpio_is_valid(pdata->gpio_ta_nconnected)) {
+ if (!pdata->gpio_ta_nconnected) {
+ dev_err(info->dev, "gpio_ta_nconnected defined as 0\n");
+ WARN_ON(!pdata->gpio_ta_nconnected);
+ ret = -EIO;
+ goto err_kfree;
+ }
+ gpio_request(pdata->gpio_ta_nconnected,
+ "MAX8922 TA_nCONNECTED");
+ }
+#if 0
+ info->irq_chg_ing = gpio_to_irq(pdata->gpio_chg_ing);
+
+ ret = request_threaded_irq(info->irq_chg_ing, NULL,
+ max8922_chg_ing_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "chg_ing", info);
+ if (ret)
+ dev_err(&pdev->dev, "%s: fail to request chg_ing IRQ:"
+ " %d: %d\n", __func__, info->irq_chg_ing, ret);
+#endif
+
+ return 0;
+err_kfree:
+ power_supply_unregister(&info->psy_bat);
+ platform_set_drvdata(pdev, NULL);
+ kfree(info);
+ return ret;
+}
+
+static int __devexit max8922_remove(struct platform_device *pdev)
+{
+ struct max8922_info *info = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&info->psy_bat);
+
+ gpio_free(info->pdata->gpio_chg_en);
+ gpio_free(info->pdata->gpio_chg_ing);
+ gpio_free(info->pdata->gpio_ta_nconnected);
+
+ kfree(info);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int max8922_suspend(struct device *dev)
+{
+ struct max8922_info *info = dev_get_drvdata(dev);
+
+ if (info && info->irq_chg_ing)
+ disable_irq(info->irq_chg_ing);
+
+ return 0;
+}
+
+static int max8922_resume(struct device *dev)
+{
+ struct max8922_info *info = dev_get_drvdata(dev);
+
+ if (info && info->irq_chg_ing)
+ enable_irq(info->irq_chg_ing);
+
+ return 0;
+}
+#else
+#define max8922_charger_suspend NULL
+#define max8922_charger_resume NULL
+#endif
+
+static const struct dev_pm_ops max8922_pm_ops = {
+ .suspend = max8922_suspend,
+ .resume = max8922_resume,
+};
+
+static struct platform_driver max8922_driver = {
+ .driver = {
+ .name = "max8922-charger",
+ .owner = THIS_MODULE,
+ .pm = &max8922_pm_ops,
+ },
+ .probe = max8922_probe,
+ .remove = __devexit_p(max8922_remove),
+};
+
+static int __init max8922_init(void)
+{
+ return platform_driver_register(&max8922_driver);
+}
+
+static void __exit max8922_exit(void)
+{
+ platform_driver_register(&max8922_driver);
+}
+
+module_init(max8922_init);
+module_exit(max8922_exit);
+
+MODULE_DESCRIPTION("MAXIM 8922 charger control driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c
new file mode 100644
index 0000000..9514d74
--- /dev/null
+++ b/drivers/power/max8997_charger.c
@@ -0,0 +1,216 @@
+/*
+ * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@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/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+struct charger_data {
+ struct device *dev;
+ struct max8997_dev *iodev;
+ struct power_supply battery;
+};
+
+static enum power_supply_property max8997_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */
+ POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */
+ POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */
+};
+
+/* Note that the charger control is done by a current regulator "CHARGER" */
+static int max8997_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct charger_data *charger = container_of(psy,
+ struct charger_data, battery);
+ struct i2c_client *i2c = charger->iodev->i2c;
+ int ret;
+ u8 reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+
+ if ((reg & (1 << 0)) == 0x1)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if ((reg & (1 << 1)) && !(reg & (1 << 2)) &&
+ (reg & (1 << 3)) && (reg & (3 << 4)) &&
+ !(reg & (1 << 6)))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if ((reg & (1 << 1)) && !(reg & (1 << 6)))
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ if ((reg & (1 << 2)) == 0x0)
+ val->intval = 1;
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ /* DCINOK */
+ if (reg & (1 << 1))
+ val->intval = 1;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static __devinit int max8997_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct charger_data *charger;
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->eoc_mA) {
+ u8 val = (pdata->eoc_mA - 50) / 10;
+ if (val < 0)
+ val = 0;
+ if (val > 0xf)
+ val = 0xf;
+
+ ret = max8997_update_reg(iodev->i2c,
+ MAX8997_REG_MBCCTRL5, val, 0xf);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot use i2c bus.\n");
+ return ret;
+ }
+ }
+
+ switch (pdata->timeout) {
+ case 5:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x2 << 4, 0x7 << 4);
+ break;
+ case 6:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x3 << 4, 0x7 << 4);
+ break;
+ case 7:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x4 << 4, 0x7 << 4);
+ break;
+ case 0:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x7 << 4, 0x7 << 4);
+ break;
+ default:
+ dev_err(&pdev->dev, "incorrect timeout value (%d)\n",
+ pdata->timeout);
+ return -EINVAL;
+ }
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot use i2c bus.\n");
+ return ret;
+ }
+
+ charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL);
+ if (charger == NULL) {
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, charger);
+
+ charger->battery.name = "max8997_pmic";
+ charger->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ charger->battery.get_property = max8997_battery_get_property;
+ charger->battery.properties = max8997_battery_props;
+ charger->battery.num_properties = ARRAY_SIZE(max8997_battery_props);
+
+ charger->dev = &pdev->dev;
+ charger->iodev = iodev;
+
+ ret = power_supply_register(&pdev->dev, &charger->battery);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ kfree(charger);
+ return ret;
+}
+
+static int __devexit max8997_battery_remove(struct platform_device *pdev)
+{
+ struct charger_data *charger = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&charger->battery);
+ kfree(charger);
+ return 0;
+}
+
+static const struct platform_device_id max8997_battery_id[] = {
+ { "max8997-battery", 0 },
+};
+
+static struct platform_driver max8997_battery_driver = {
+ .driver = {
+ .name = "max8997-battery",
+ .owner = THIS_MODULE,
+ },
+ .probe = max8997_battery_probe,
+ .remove = __devexit_p(max8997_battery_remove),
+ .id_table = max8997_battery_id,
+};
+
+static int __init max8997_battery_init(void)
+{
+ return platform_driver_register(&max8997_battery_driver);
+}
+subsys_initcall(max8997_battery_init);
+
+static void __exit max8997_battery_cleanup(void)
+{
+ platform_driver_unregister(&max8997_battery_driver);
+}
+module_exit(max8997_battery_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8997_charger_px.c b/drivers/power/max8997_charger_px.c
new file mode 100644
index 0000000..9582d66
--- /dev/null
+++ b/drivers/power/max8997_charger_px.c
@@ -0,0 +1,485 @@
+/*
+ * max8997-charger.c
+ * MAXIM 8997 charger interface driver
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+/* MAX8997_REG_STATUS4 */
+#define DCINOK_SHIFT 1
+#define DCINOK_MASK (1 << DCINOK_SHIFT)
+#define DETBAT_SHIFT 2
+#define DETBAT_MASK (1 << DETBAT_SHIFT)
+
+/* MAX8997_REG_MBCCTRL1 */
+#define TFCH_SHIFT 4
+#define TFCH_MASK (7 << TFCH_SHIFT)
+
+/* MAX8997_REG_MBCCTRL2 */
+#define MBCHOSTEN_SHIFT 6
+#define VCHGR_FC_SHIFT 7
+#define MBCHOSTEN_MASK (1 << MBCHOSTEN_SHIFT)
+#define VCHGR_FC_MASK (1 << VCHGR_FC_SHIFT)
+
+/* MAX8997_REG_MBCCTRL3 */
+#define MBCCV_SHIFT 0
+#define MBCCV_MASK (0xF << MBCCV_SHIFT)
+
+/* MAX8997_REG_MBCCTRL4 */
+#define MBCICHFC_SHIFT 0
+#define MBCICHFC_MASK (0xF << MBCICHFC_SHIFT)
+
+/* MAX8997_REG_MBCCTRL5 */
+#define ITOPOFF_SHIFT 0
+#define ITOPOFF_MASK (0xF << ITOPOFF_SHIFT)
+
+/* MAX8997_REG_MBCCTRL6 */
+#define AUTOSTOP_SHIFT 5
+#define AUTOSTOP_MASK (1 << AUTOSTOP_SHIFT)
+
+/* MAX8997_REG_OTPCGHCVS */
+#define OTPCGHCVS_SHIFT 0
+#define OTPCGHCVS_MASK (3 << OTPCGHCVS_SHIFT)
+
+enum {
+ BAT_NOT_DETECTED,
+ BAT_DETECTED
+};
+
+struct chg_data {
+ struct device *dev;
+ struct max8997_dev *max8997;
+ struct power_supply psy_bat;
+ struct max8997_power_data *power;
+ int irq_topoff;
+ int irq_chgins;
+ int irq_chgrm;
+};
+
+static enum power_supply_property max8997_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+/* vf check */
+static bool max8997_check_detbat(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ u8 data = 0;
+ int ret;
+
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data);
+
+ if (ret < 0) {
+ dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ if (data & DETBAT_MASK)
+ printk(KERN_WARNING "%s: batt not detected(0x%x)\n", __func__,
+ data);
+
+ return data & DETBAT_MASK;
+}
+
+/* whether charging enabled or not */
+static bool max8997_check_vdcin(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ u8 data = 0;
+ int ret;
+
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data);
+
+ if (ret < 0) {
+ dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ return data & DCINOK_MASK;
+}
+
+static int max8997_chg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct chg_data *chg = container_of(psy, struct chg_data, psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (max8997_check_vdcin(chg))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (max8997_check_detbat(chg))
+ val->intval = BAT_NOT_DETECTED;
+ else
+ val->intval = BAT_DETECTED;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max8997_disable_charging(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 mask;
+
+ dev_info(chg->dev, "%s: disable charging\n", __func__);
+ mask = MBCHOSTEN_MASK | VCHGR_FC_MASK;
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, 0, mask);
+ if (ret < 0)
+ dev_err(chg->dev, "%s: fail update reg!!!\n", __func__);
+
+ return ret;
+}
+
+static int max8997_set_charging_current(struct chg_data *chg, int chg_current)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 val;
+
+ if (chg_current < 200 || chg_current > 950)
+ return -EINVAL;
+
+ val = ((chg_current - 200) / 50) & 0xf;
+
+ dev_info(chg->dev, "%s: charging current=%d", __func__, chg_current);
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4,
+ (val << MBCICHFC_SHIFT), MBCICHFC_MASK);
+ if (ret)
+ dev_err(chg->dev, "%s: fail to write chg current(%d)\n",
+ __func__, ret);
+
+ return ret;
+}
+
+static int max8997_enable_charging(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 val, mask;
+
+ /* set auto stop disable */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6,
+ (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK);
+ if (ret)
+ dev_err(chg->dev, "%s: failt to disable autostop(%d)\n",
+ __func__, ret);
+
+ /* set fast charging enable and main battery charging enable */
+ val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT);
+ mask = MBCHOSTEN_MASK | VCHGR_FC_MASK;
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask);
+ if (ret)
+ dev_err(chg->dev, "%s: failt to enable charging(%d)\n",
+ __func__, ret);
+
+ return ret;
+}
+
+/* TODO: remove this function later */
+static int max8997_enable_charging_x(struct chg_data *chg, int charge_type)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 val, mask;
+
+ /* enable charging */
+ if (charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ /* ac */
+ dev_info(chg->dev, "%s: TA charging", __func__);
+ /* set fast charging current : 650mA */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4,
+ (9 << MBCICHFC_SHIFT), MBCICHFC_MASK);
+ if (ret)
+ goto err;
+
+ } else if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TRICKLE) {
+ /* usb */
+ dev_info(chg->dev, "%s: USB charging", __func__);
+ /* set fast charging current : 450mA */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4,
+ (5 << MBCICHFC_SHIFT), MBCICHFC_MASK);
+ if (ret)
+ goto err;
+ } else {
+ dev_err(chg->dev, "%s: invalid arg\n", __func__);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* set auto stop disable */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6,
+ (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK);
+ if (ret)
+ goto err;
+
+ /* set fast charging enable and main battery charging enable */
+ val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT);
+ mask = MBCHOSTEN_MASK | VCHGR_FC_MASK;
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_err(chg->dev, "%s: max8997 update reg error!(%d)\n", __func__, ret);
+ return ret;
+}
+
+static int max8997_chg_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ int ret, reg_val;
+ struct chg_data *chg = container_of(psy, struct chg_data, psy_bat);
+ struct i2c_client *i2c = chg->max8997->i2c;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE: /* TODO: remove this */
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE)
+ ret = max8997_disable_charging(chg);
+ else
+ ret = max8997_enable_charging_x(chg, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */
+ ret = max8997_set_charging_current(chg, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */
+ if (val->intval == POWER_SUPPLY_STATUS_CHARGING)
+ ret = max8997_enable_charging(chg);
+ else
+ ret = max8997_disable_charging(chg);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL: /* Set recharging current */
+ if (val->intval < 50 || val->intval > 200) {
+ dev_err(chg->dev, "%s: invalid topoff current(%d)\n",
+ __func__, val->intval);
+ return -EINVAL;
+ }
+ reg_val = (val->intval - 50) / 10;
+
+ dev_info(chg->dev, "%s: Set toppoff current to 0x%x\n",
+ __func__, reg_val);
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL5,
+ (reg_val << ITOPOFF_SHIFT), ITOPOFF_MASK);
+ if (ret) {
+ dev_err(chg->dev, "%s: max8997 update reg error(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static irqreturn_t max8997_chg_topoff_irq(int irq, void *data)
+{
+ struct chg_data *chg = data;
+ int ret = 0;
+
+ if (chg->power->topoff_cb)
+ ret = chg->power->topoff_cb();
+
+ if (ret) {
+ dev_err(chg->dev, "%s: error from topoff_cb(%d)\n", __func__,
+ ret);
+ return IRQ_HANDLED;
+ }
+
+ dev_info(chg->dev, " Topoff IRQ occurred!\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8997_chg_charger_irq(int irq, void *data)
+{
+ struct chg_data *chg = data;
+ int ret = 0;
+ bool insert = false;
+
+ insert = (irq == chg->irq_chgins);
+
+ dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert);
+
+ if (chg->power->set_charger)
+ ret = chg->power->set_charger(insert);
+
+ if (ret) {
+ dev_err(chg->dev, "%s: error from set_charger(%d)\n", __func__,
+ ret);
+ return IRQ_HANDLED;
+ }
+
+ dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert);
+
+ return IRQ_HANDLED;
+}
+
+static __devinit int max8997_charger_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev);
+ struct i2c_client *i2c = max8997->i2c;
+ struct chg_data *chg;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "%s : MAX8997 Charger Driver Loading\n", __func__);
+
+ chg = kzalloc(sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->dev = &pdev->dev;
+ chg->max8997 = max8997;
+ chg->power = pdata->power;
+ chg->irq_topoff = max8997->irq_base + MAX8997_IRQ_TOPOFF;
+
+ chg->irq_chgins = max8997->irq_base + MAX8997_IRQ_CHGINS;
+ chg->irq_chgrm = max8997->irq_base + MAX8997_IRQ_CHGRM;
+
+ chg->psy_bat.name = "max8997-charger",
+ chg->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ chg->psy_bat.properties = max8997_battery_props,
+ chg->psy_bat.num_properties = ARRAY_SIZE(max8997_battery_props),
+ chg->psy_bat.get_property = max8997_chg_get_property,
+ chg->psy_bat.set_property = max8997_chg_set_property,
+
+ platform_set_drvdata(pdev, chg);
+
+ /* TODO: configure by platform data*/
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL1, /* Disable */
+ (0x7 << TFCH_SHIFT), TFCH_MASK);
+ if (ret < 0)
+ goto err_kfree;
+
+ /* TODO: configure by platform data*/
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL3, /* 4.2V */
+ (0x0 << MBCCV_SHIFT), MBCCV_MASK);
+ if (ret < 0)
+ goto err_kfree;
+
+ /* Set OVP voltage threshold */
+ ret = max8997_update_reg(i2c, MAX8997_REG_OTPCGHCVS,
+ (0x01 << OTPCGHCVS_SHIFT), OTPCGHCVS_MASK);
+ if (ret < 0)
+ goto err_kfree;
+
+ /* init power supplier framework */
+ ret = power_supply_register(&pdev->dev, &chg->psy_bat);
+ if (ret) {
+ pr_err("Failed to register power supply psy_bat\n");
+ goto err_kfree;
+ }
+
+ ret = request_threaded_irq(chg->irq_topoff, NULL,
+ max8997_chg_topoff_irq, 0, "chg-topoff", chg);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s: fail to request topoff IRQ: %d: %d\n",
+ __func__, chg->irq_topoff, ret);
+
+ ret = request_threaded_irq(chg->irq_chgins, NULL,
+ max8997_chg_charger_irq, 0, "chg-insert", chg);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s: fail to request chgins IRQ: %d: %d\n",
+ __func__, chg->irq_chgins, ret);
+
+ ret = request_threaded_irq(chg->irq_chgrm, NULL,
+ max8997_chg_charger_irq, 0, "chg-remove", chg);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s: fail to request chgrm IRQ: %d: %d\n",
+ __func__, chg->irq_chgrm, ret);
+
+ return 0;
+
+err_kfree:
+ kfree(chg);
+ return ret;
+}
+
+static int __devexit max8997_charger_remove(struct platform_device *pdev)
+{
+ struct chg_data *chg = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&chg->psy_bat);
+
+ kfree(chg);
+
+ return 0;
+}
+
+static int max8997_charger_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int max8997_charger_resume(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops max8997_charger_pm_ops = {
+ .suspend = max8997_charger_suspend,
+ .resume = max8997_charger_resume,
+};
+
+static struct platform_driver max8997_charger_driver = {
+ .driver = {
+ .name = "max8997-charger",
+ .owner = THIS_MODULE,
+ .pm = &max8997_charger_pm_ops,
+ },
+ .probe = max8997_charger_probe,
+ .remove = __devexit_p(max8997_charger_remove),
+};
+
+static int __init max8997_charger_init(void)
+{
+ return platform_driver_register(&max8997_charger_driver);
+}
+
+static void __exit max8997_charger_exit(void)
+{
+ platform_driver_register(&max8997_charger_driver);
+}
+
+module_init(max8997_charger_init);
+module_exit(max8997_charger_exit);
+
+MODULE_DESCRIPTION("MAXIM 8997 charger control driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8997_charger_u1.c b/drivers/power/max8997_charger_u1.c
new file mode 100644
index 0000000..af4ef44
--- /dev/null
+++ b/drivers/power/max8997_charger_u1.c
@@ -0,0 +1,478 @@
+/*
+ * max8997-charger.c
+ * MAXIM 8997 charger interface driver
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+/* MAX8997_REG_STATUS4 */
+#define DCINOK_SHIFT 1
+#define DCINOK_MASK (1 << DCINOK_SHIFT)
+#define DETBAT_SHIFT 2
+#define DETBAT_MASK (1 << DETBAT_SHIFT)
+
+/* MAX8997_REG_MBCCTRL1 */
+#define TFCH_SHIFT 4
+#define TFCH_MASK (7 << TFCH_SHIFT)
+
+/* MAX8997_REG_MBCCTRL2 */
+#define MBCHOSTEN_SHIFT 6
+#define VCHGR_FC_SHIFT 7
+#define MBCHOSTEN_MASK (1 << MBCHOSTEN_SHIFT)
+#define VCHGR_FC_MASK (1 << VCHGR_FC_SHIFT)
+
+/* MAX8997_REG_MBCCTRL3 */
+#define MBCCV_SHIFT 0
+#define MBCCV_MASK (0xF << MBCCV_SHIFT)
+
+/* MAX8997_REG_MBCCTRL4 */
+#define MBCICHFC_SHIFT 0
+#define MBCICHFC_MASK (0xF << MBCICHFC_SHIFT)
+
+/* MAX8997_REG_MBCCTRL5 */
+#define ITOPOFF_SHIFT 0
+#define ITOPOFF_MASK (0xF << ITOPOFF_SHIFT)
+
+/* MAX8997_REG_MBCCTRL6 */
+#define AUTOSTOP_SHIFT 5
+#define AUTOSTOP_MASK (1 << AUTOSTOP_SHIFT)
+
+enum {
+ BAT_NOT_DETECTED,
+ BAT_DETECTED
+};
+
+struct chg_data {
+ struct device *dev;
+ struct max8997_dev *max8997;
+ struct power_supply psy_bat;
+ struct max8997_power_data *power;
+ int irq_topoff;
+ int irq_chgins;
+ int irq_chgrm;
+};
+
+static enum power_supply_property max8997_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+/* vf check */
+static bool max8997_check_detbat(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ u8 data = 0;
+ int ret;
+
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data);
+
+ if (ret < 0) {
+ dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (data & DETBAT_MASK)
+ dev_info(chg->dev, "%s: batt not detected(0x%x)\n",
+ __func__, data);
+
+ return data & DETBAT_MASK;
+}
+
+/* whether charging enabled or not */
+static bool max8997_check_vdcin(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ u8 data = 0;
+ int ret;
+
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data);
+
+ if (ret < 0) {
+ dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ return data & DCINOK_MASK;
+}
+
+static int max8997_chg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct chg_data *chg = container_of(psy, struct chg_data, psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (max8997_check_vdcin(chg))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (max8997_check_detbat(chg))
+ val->intval = BAT_NOT_DETECTED;
+ else
+ val->intval = BAT_DETECTED;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max8997_disable_charging(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 mask;
+
+ dev_info(chg->dev, "%s: disable charging\n", __func__);
+ mask = MBCHOSTEN_MASK | VCHGR_FC_MASK;
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, 0, mask);
+ if (ret < 0)
+ dev_err(chg->dev, "%s: fail update reg!!!\n", __func__);
+
+ return ret;
+}
+
+static int max8997_set_charging_current(struct chg_data *chg, int chg_current)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 val;
+
+ if (chg_current < 200 || chg_current > 950)
+ return -EINVAL;
+
+ val = ((chg_current - 200) / 50) & 0xf;
+
+ dev_info(chg->dev, "%s: charging current=%d", __func__, chg_current);
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4,
+ (val << MBCICHFC_SHIFT), MBCICHFC_MASK);
+ if (ret)
+ dev_err(chg->dev, "%s: fail to write chg current(%d)\n",
+ __func__, ret);
+
+ return ret;
+}
+
+static int max8997_enable_charging(struct chg_data *chg)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 val, mask;
+
+ /* set auto stop disable */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6,
+ (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK);
+ if (ret)
+ dev_err(chg->dev, "%s: failt to disable autostop(%d)\n",
+ __func__, ret);
+
+ /* set fast charging enable and main battery charging enable */
+ val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT);
+ mask = MBCHOSTEN_MASK | VCHGR_FC_MASK;
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask);
+ if (ret)
+ dev_err(chg->dev, "%s: failt to enable charging(%d)\n",
+ __func__, ret);
+
+ return ret;
+}
+
+/* TODO: remove this function later */
+static int max8997_enable_charging_x(struct chg_data *chg, int charge_type)
+{
+ struct i2c_client *i2c = chg->max8997->i2c;
+ int ret;
+ u8 val, mask;
+
+ /* enable charging */
+ if (charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ /* ac */
+ dev_info(chg->dev, "%s: TA charging", __func__);
+ /* set fast charging current : 650mA */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4,
+ (9 << MBCICHFC_SHIFT), MBCICHFC_MASK);
+ if (ret)
+ goto err;
+
+ } else if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TRICKLE) {
+ /* usb */
+ dev_info(chg->dev, "%s: USB charging", __func__);
+ /* set fast charging current : 450mA */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4,
+ (5 << MBCICHFC_SHIFT), MBCICHFC_MASK);
+ if (ret)
+ goto err;
+ } else {
+ dev_err(chg->dev, "%s: invalid arg\n", __func__);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* set auto stop disable */
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6,
+ (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK);
+ if (ret)
+ goto err;
+
+ /* set fast charging enable and main battery charging enable */
+ val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT);
+ mask = MBCHOSTEN_MASK | VCHGR_FC_MASK;
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_err(chg->dev, "%s: max8997 update reg error!(%d)\n", __func__, ret);
+ return ret;
+}
+
+static int max8997_chg_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ int ret, reg_val;
+ struct chg_data *chg = container_of(psy, struct chg_data, psy_bat);
+ struct i2c_client *i2c = chg->max8997->i2c;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE: /* TODO: remove this */
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE)
+ ret = max8997_disable_charging(chg);
+ else
+ ret = max8997_enable_charging_x(chg, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */
+ ret = max8997_set_charging_current(chg, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */
+ if (val->intval == POWER_SUPPLY_STATUS_CHARGING)
+ ret = max8997_enable_charging(chg);
+ else
+ ret = max8997_disable_charging(chg);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL: /* Set recharging current */
+ if (val->intval < 50 || val->intval > 200) {
+ dev_err(chg->dev, "%s: invalid topoff current(%d)\n",
+ __func__, val->intval);
+ return -EINVAL;
+ }
+ reg_val = (val->intval - 50) / 10;
+
+ dev_info(chg->dev, "%s: Set toppoff current to 0x%x\n",
+ __func__, reg_val);
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL5,
+ (reg_val << ITOPOFF_SHIFT), ITOPOFF_MASK);
+ if (ret) {
+ dev_err(chg->dev, "%s: max8997 update reg error(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static irqreturn_t max8997_chg_topoff_irq(int irq, void *data)
+{
+ struct chg_data *chg = data;
+ int ret = 0;
+
+ if (chg->power->topoff_cb)
+ ret = chg->power->topoff_cb();
+
+ if (ret) {
+ dev_err(chg->dev, "%s: error from topoff_cb(%d)\n", __func__,
+ ret);
+ return IRQ_HANDLED;
+ }
+
+ dev_info(chg->dev, " Topoff IRQ occurred!\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8997_chg_charger_irq(int irq, void *data)
+{
+ struct chg_data *chg = data;
+ int ret = 0;
+ bool insert = false;
+
+ insert = (irq == chg->irq_chgins);
+
+ dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert);
+
+ if (system_rev >= 0x3)
+ return IRQ_HANDLED;
+
+ if (chg->power->set_charger)
+ ret = chg->power->set_charger(insert);
+
+ if (ret) {
+ dev_err(chg->dev, "%s: error from set_charger(%d)\n", __func__,
+ ret);
+ return IRQ_HANDLED;
+ }
+
+ dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert);
+
+ return IRQ_HANDLED;
+}
+
+static __devinit int max8997_charger_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev);
+ struct i2c_client *i2c = max8997->i2c;
+ struct chg_data *chg;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "%s : MAX8997 Charger Driver Loading\n", __func__);
+
+ chg = kzalloc(sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->dev = &pdev->dev;
+ chg->max8997 = max8997;
+ chg->power = pdata->power;
+ chg->irq_topoff = max8997->irq_base + MAX8997_IRQ_TOPOFF;
+
+ chg->irq_chgins = max8997->irq_base + MAX8997_IRQ_CHGINS;
+ chg->irq_chgrm = max8997->irq_base + MAX8997_IRQ_CHGRM;
+
+ chg->psy_bat.name = "max8997-charger",
+ chg->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ chg->psy_bat.properties = max8997_battery_props,
+ chg->psy_bat.num_properties = ARRAY_SIZE(max8997_battery_props),
+ chg->psy_bat.get_property = max8997_chg_get_property,
+ chg->psy_bat.set_property = max8997_chg_set_property,
+
+ platform_set_drvdata(pdev, chg);
+
+ /* TODO: configure by platform data*/
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL1, /* Disable */
+ (0x7 << TFCH_SHIFT), TFCH_MASK);
+ if (ret < 0)
+ goto err_kfree;
+
+ /* TODO: configure by platform data*/
+ ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL3, /* 4.2V */
+ (0x0 << MBCCV_SHIFT), MBCCV_MASK);
+ if (ret < 0)
+ goto err_kfree;
+
+ /* init power supplier framework */
+ ret = power_supply_register(&pdev->dev, &chg->psy_bat);
+ if (ret) {
+ pr_err("Failed to register power supply psy_bat\n");
+ goto err_kfree;
+ }
+
+ ret = request_threaded_irq(chg->irq_topoff, NULL,
+ max8997_chg_topoff_irq, 0, "chg-topoff", chg);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s: fail to request topoff IRQ: %d: %d\n",
+ __func__, chg->irq_topoff, ret);
+
+ ret = request_threaded_irq(chg->irq_chgins, NULL,
+ max8997_chg_charger_irq, 0, "chg-insert", chg);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s: fail to request chgins IRQ: %d: %d\n",
+ __func__, chg->irq_chgins, ret);
+
+ ret = request_threaded_irq(chg->irq_chgrm, NULL,
+ max8997_chg_charger_irq, 0, "chg-remove", chg);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s: fail to request chgrm IRQ: %d: %d\n",
+ __func__, chg->irq_chgrm, ret);
+
+ return 0;
+
+err_kfree:
+ kfree(chg);
+ return ret;
+}
+
+static int __devexit max8997_charger_remove(struct platform_device *pdev)
+{
+ struct chg_data *chg = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&chg->psy_bat);
+
+ kfree(chg);
+
+ return 0;
+}
+
+static int max8997_charger_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int max8997_charger_resume(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops max8997_charger_pm_ops = {
+ .suspend = max8997_charger_suspend,
+ .resume = max8997_charger_resume,
+};
+
+static struct platform_driver max8997_charger_driver = {
+ .driver = {
+ .name = "max8997-charger",
+ .owner = THIS_MODULE,
+ .pm = &max8997_charger_pm_ops,
+ },
+ .probe = max8997_charger_probe,
+ .remove = __devexit_p(max8997_charger_remove),
+};
+
+static int __init max8997_charger_init(void)
+{
+ return platform_driver_register(&max8997_charger_driver);
+}
+
+static void __exit max8997_charger_exit(void)
+{
+ platform_driver_register(&max8997_charger_driver);
+}
+
+module_init(max8997_charger_init);
+module_exit(max8997_charger_exit);
+
+MODULE_DESCRIPTION("MAXIM 8997 charger control driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c
index 69f8aa3..81b7201 100644
--- a/drivers/power/pda_power.c
+++ b/drivers/power/pda_power.c
@@ -14,6 +14,7 @@
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
+#include <linux/notifier.h>
#include <linux/power_supply.h>
#include <linux/pda_power.h>
#include <linux/regulator/consumer.h>
@@ -38,9 +39,8 @@ static struct timer_list supply_timer;
static struct timer_list polling_timer;
static int polling;
-#ifdef CONFIG_USB_OTG_UTILS
static struct otg_transceiver *transceiver;
-#endif
+static struct notifier_block otg_nb;
static struct regulator *ac_draw;
enum {
@@ -222,7 +222,42 @@ static void polling_timer_func(unsigned long unused)
#ifdef CONFIG_USB_OTG_UTILS
static int otg_is_usb_online(void)
{
- return (transceiver->state == OTG_STATE_B_PERIPHERAL);
+ return (transceiver->last_event == USB_EVENT_VBUS ||
+ transceiver->last_event == USB_EVENT_ENUMERATED);
+}
+
+static int otg_is_ac_online(void)
+{
+ return (transceiver->last_event == USB_EVENT_CHARGER);
+}
+
+static int otg_handle_notification(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ switch (event) {
+ case USB_EVENT_CHARGER:
+ ac_status = PDA_PSY_TO_CHANGE;
+ break;
+ case USB_EVENT_VBUS:
+ case USB_EVENT_ENUMERATED:
+ usb_status = PDA_PSY_TO_CHANGE;
+ break;
+ case USB_EVENT_NONE:
+ ac_status = PDA_PSY_TO_CHANGE;
+ usb_status = PDA_PSY_TO_CHANGE;
+ break;
+ default:
+ return NOTIFY_OK;
+ }
+
+ /*
+ * Wait a bit before reading ac/usb line status and setting charger,
+ * because ac/usb status readings may lag from irq.
+ */
+ mod_timer(&charger_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_status));
+
+ return NOTIFY_OK;
}
#endif
@@ -282,6 +317,14 @@ static int pda_power_probe(struct platform_device *pdev)
ret = PTR_ERR(ac_draw);
}
+ transceiver = otg_get_transceiver();
+ if (transceiver && !pdata->is_usb_online) {
+ pdata->is_usb_online = otg_is_usb_online;
+ }
+ if (transceiver && !pdata->is_ac_online) {
+ pdata->is_ac_online = otg_is_ac_online;
+ }
+
if (pdata->is_ac_online) {
ret = power_supply_register(&pdev->dev, &pda_psy_ac);
if (ret) {
@@ -303,13 +346,6 @@ static int pda_power_probe(struct platform_device *pdev)
}
}
-#ifdef CONFIG_USB_OTG_UTILS
- transceiver = otg_get_transceiver();
- if (transceiver && !pdata->is_usb_online) {
- pdata->is_usb_online = otg_is_usb_online;
- }
-#endif
-
if (pdata->is_usb_online) {
ret = power_supply_register(&pdev->dev, &pda_psy_usb);
if (ret) {
@@ -331,6 +367,16 @@ static int pda_power_probe(struct platform_device *pdev)
}
}
+ if (transceiver && pdata->use_otg_notifier) {
+ otg_nb.notifier_call = otg_handle_notification;
+ ret = otg_register_notifier(transceiver, &otg_nb);
+ if (ret) {
+ dev_err(dev, "failure to register otg notifier\n");
+ goto otg_reg_notifier_failed;
+ }
+ polling = 0;
+ }
+
if (polling) {
dev_dbg(dev, "will poll for status\n");
setup_timer(&polling_timer, polling_timer_func, 0);
@@ -343,16 +389,17 @@ static int pda_power_probe(struct platform_device *pdev)
return 0;
+otg_reg_notifier_failed:
+ if (pdata->is_usb_online && usb_irq)
+ free_irq(usb_irq->start, &pda_psy_usb);
usb_irq_failed:
if (pdata->is_usb_online)
power_supply_unregister(&pda_psy_usb);
usb_supply_failed:
if (pdata->is_ac_online && ac_irq)
free_irq(ac_irq->start, &pda_psy_ac);
-#ifdef CONFIG_USB_OTG_UTILS
if (transceiver)
otg_put_transceiver(transceiver);
-#endif
ac_irq_failed:
if (pdata->is_ac_online)
power_supply_unregister(&pda_psy_ac);
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index 329b46b..03810ce 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -41,23 +41,40 @@ static int __power_supply_changed_work(struct device *dev, void *data)
static void power_supply_changed_work(struct work_struct *work)
{
+ unsigned long flags;
struct power_supply *psy = container_of(work, struct power_supply,
changed_work);
dev_dbg(psy->dev, "%s\n", __func__);
- class_for_each_device(power_supply_class, NULL, psy,
- __power_supply_changed_work);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ if (psy->changed) {
+ psy->changed = false;
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
- power_supply_update_leds(psy);
+ class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_changed_work);
- kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+ power_supply_update_leds(psy);
+
+ kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ }
+ if (!psy->changed)
+ wake_unlock(&psy->work_wake_lock);
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
}
void power_supply_changed(struct power_supply *psy)
{
+ unsigned long flags;
+
dev_dbg(psy->dev, "%s\n", __func__);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ psy->changed = true;
+ wake_lock(&psy->work_wake_lock);
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
schedule_work(&psy->changed_work);
}
EXPORT_SYMBOL_GPL(power_supply_changed);
@@ -181,6 +198,9 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
if (rc)
goto device_add_failed;
+ spin_lock_init(&psy->changed_lock);
+ wake_lock_init(&psy->work_wake_lock, WAKE_LOCK_SUSPEND, "power-supply");
+
rc = power_supply_create_triggers(psy);
if (rc)
goto create_triggers_failed;
@@ -190,6 +210,7 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
goto success;
create_triggers_failed:
+ wake_lock_destroy(&psy->work_wake_lock);
device_del(dev);
kobject_set_name_failed:
device_add_failed:
@@ -203,6 +224,7 @@ void power_supply_unregister(struct power_supply *psy)
{
cancel_work_sync(&psy->changed_work);
power_supply_remove_triggers(psy);
+ wake_lock_destroy(&psy->work_wake_lock);
device_unregister(psy->dev);
}
EXPORT_SYMBOL_GPL(power_supply_unregister);
diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c
index d36c289..9f869c1 100644
--- a/drivers/power/s3c_adc_battery.c
+++ b/drivers/power/s3c_adc_battery.c
@@ -1,11 +1,13 @@
/*
- * iPAQ h1930/h1940/rx1950 battery controller driver
- * Copyright (c) Vasily Khoruzhick
- * Based on h1940_battery.c by Arnaud Patard
+ * linux/drivers/power/s3c_adc_battery.c
*
- * This file is subject to the terms and conditions of the GNU General Public
- * License. See the file COPYING in the main directory of this archive for
- * more details.
+ * Battery measurement code for samsung smdk platform.
+ *
+ * Copyright (C) 2011 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.
*
*/
@@ -266,7 +268,7 @@ static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id)
return IRQ_HANDLED;
}
-static int __init s3c_adc_bat_probe(struct platform_device *pdev)
+static int __devinit s3c_adc_bat_probe(struct platform_device *pdev)
{
struct s3c_adc_client *client;
struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
diff --git a/drivers/power/samsung_fake_battery.c b/drivers/power/samsung_fake_battery.c
new file mode 100644
index 0000000..d64ee99
--- /dev/null
+++ b/drivers/power/samsung_fake_battery.c
@@ -0,0 +1,546 @@
+/*
+ * linux/drivers/power/samsung_fake_battery.c
+ *
+ * Battery measurement code for samsung smdk platform.
+ *
+ * Copyright (C) 2011 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/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/wakelock.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <plat/gpio-cfg.h>
+
+#define FAKE_BAT_LEVEL 80
+
+static struct wake_lock vbus_wake_lock;
+
+/* Prototypes */
+static ssize_t samsung_fake_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+static ssize_t samsung_fake_bat_store_property(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+static struct device *dev;
+static int samsung_fake_battery_initial;
+
+static char *status_text[] = {
+ [POWER_SUPPLY_STATUS_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_STATUS_CHARGING] = "Charging",
+ [POWER_SUPPLY_STATUS_DISCHARGING] = "Discharging",
+ [POWER_SUPPLY_STATUS_NOT_CHARGING] = "Not Charging",
+ [POWER_SUPPLY_STATUS_FULL] = "Full",
+};
+
+typedef enum {
+ CHARGER_BATTERY = 0,
+ CHARGER_USB,
+ CHARGER_AC,
+ CHARGER_DISCHARGE
+} charger_type_t;
+
+struct battery_info {
+ u32 batt_id; /* Battery ID from ADC */
+ u32 batt_vol; /* Battery voltage from ADC */
+ u32 batt_vol_adc; /* Battery ADC value */
+ u32 batt_vol_adc_cal; /* Battery ADC value (calibrated)*/
+ u32 batt_temp; /* Battery Temperature (C) from ADC */
+ u32 batt_temp_adc; /* Battery Temperature ADC value */
+ u32 batt_temp_adc_cal; /* Battery Temperature ADC value (calibrated) */
+ u32 batt_current; /* Battery current from ADC */
+ u32 level; /* formula */
+ u32 charging_source; /* 0: no cable, 1:usb, 2:AC */
+ u32 charging_enabled; /* 0: Disable, 1: Enable */
+ u32 batt_health; /* Battery Health (Authority) */
+ u32 batt_is_full; /* 0 : Not full 1: Full */
+};
+
+/* lock to protect the battery info */
+static DEFINE_MUTEX(work_lock);
+
+struct samsung_fake_battery_info {
+ int present;
+ int polling;
+ unsigned long polling_interval;
+
+ struct battery_info bat_info;
+};
+static struct samsung_fake_battery_info samsung_fake_bat_info;
+
+static int samsung_get_bat_level(struct power_supply *bat_ps)
+{
+ return FAKE_BAT_LEVEL;
+}
+
+static int samsung_get_bat_vol(struct power_supply *bat_ps)
+{
+ int bat_vol = 0;
+
+ return bat_vol;
+}
+
+static u32 samsung_get_bat_health(void)
+{
+ return samsung_fake_bat_info.bat_info.batt_health;
+}
+
+static int samsung_get_bat_temp(struct power_supply *bat_ps)
+{
+ int temp = 0;
+
+ return temp;
+}
+
+static int samsung_fake_bat_get_charging_status(void)
+{
+ charger_type_t charger = CHARGER_BATTERY;
+ int ret = 0;
+
+ charger = samsung_fake_bat_info.bat_info.charging_source;
+
+ switch (charger) {
+ case CHARGER_BATTERY:
+ ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHARGER_USB:
+ case CHARGER_AC:
+ if (samsung_fake_bat_info.bat_info.level == 100 &&
+ samsung_fake_bat_info.bat_info.batt_is_full)
+ ret = POWER_SUPPLY_STATUS_FULL;
+ else
+ ret = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case CHARGER_DISCHARGE:
+ ret = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ ret = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+ dev_dbg(dev, "%s : %s\n", __func__, status_text[ret]);
+
+ return ret;
+}
+
+static int samsung_fake_bat_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ dev_dbg(bat_ps->dev, "%s : psp = %d\n", __func__, psp);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = samsung_fake_bat_get_charging_status();
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = samsung_get_bat_health();
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = samsung_fake_bat_info.present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = samsung_fake_bat_info.bat_info.level;
+ dev_dbg(dev, "%s : level = %d\n", __func__,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = samsung_fake_bat_info.bat_info.batt_temp;
+ dev_dbg(bat_ps->dev, "%s : temp = %d\n", __func__,
+ val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int samsung_power_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ charger_type_t charger;
+
+ dev_dbg(bat_ps->dev, "%s : psp = %d\n", __func__, psp);
+
+ charger = samsung_fake_bat_info.bat_info.charging_source;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (bat_ps->type == POWER_SUPPLY_TYPE_MAINS)
+ val->intval = (charger == CHARGER_AC ? 1 : 0);
+ else if (bat_ps->type == POWER_SUPPLY_TYPE_USB)
+ val->intval = (charger == CHARGER_USB ? 1 : 0);
+ else
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define SAMSUNG_FAKE_BAT_ATTR(_name) \
+{ \
+ .attr = { .name = #_name,}, \
+ .show = samsung_fake_bat_show_property, \
+ .store = samsung_fake_bat_store_property, \
+}
+
+static struct device_attribute samsung_fake_battery_attrs[] = {
+ SAMSUNG_FAKE_BAT_ATTR(batt_vol),
+ SAMSUNG_FAKE_BAT_ATTR(batt_vol_adc),
+ SAMSUNG_FAKE_BAT_ATTR(batt_vol_adc_cal),
+ SAMSUNG_FAKE_BAT_ATTR(batt_temp),
+ SAMSUNG_FAKE_BAT_ATTR(batt_temp_adc),
+ SAMSUNG_FAKE_BAT_ATTR(batt_temp_adc_cal),
+};
+
+enum {
+ BATT_VOL = 0,
+ BATT_VOL_ADC,
+ BATT_VOL_ADC_CAL,
+ BATT_TEMP,
+ BATT_TEMP_ADC,
+ BATT_TEMP_ADC_CAL,
+};
+
+static int samsung_fake_bat_create_attrs(struct device * dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(samsung_fake_battery_attrs); i++) {
+ rc = device_create_file(dev, &samsung_fake_battery_attrs[i]);
+ if (rc)
+ goto attrs_failed;
+ }
+ goto succeed;
+
+attrs_failed:
+ while (i--)
+ device_remove_file(dev, &samsung_fake_battery_attrs[i]);
+succeed:
+ return rc;
+}
+
+static ssize_t samsung_fake_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+ const ptrdiff_t off = attr - samsung_fake_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_vol);
+ break;
+ case BATT_VOL_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_vol_adc);
+ break;
+ case BATT_VOL_ADC_CAL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_vol_adc_cal);
+ break;
+ case BATT_TEMP:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_temp);
+ break;
+ case BATT_TEMP_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_temp_adc);
+ break;
+ case BATT_TEMP_ADC_CAL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_temp_adc_cal);
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t samsung_fake_bat_store_property(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int x = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - samsung_fake_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL_ADC_CAL:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ samsung_fake_bat_info.bat_info.batt_vol_adc_cal = x;
+ ret = count;
+ }
+ dev_info(dev, "%s : batt_vol_adc_cal = %d\n", __func__, x);
+ break;
+ case BATT_TEMP_ADC_CAL:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ samsung_fake_bat_info.bat_info.batt_temp_adc_cal = x;
+ ret = count;
+ }
+ dev_info(dev, "%s : batt_temp_adc_cal = %d\n", __func__, x);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property samsung_fake_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static enum power_supply_property samsung_power_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static struct power_supply samsung_power_supplies[] = {
+ {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = samsung_fake_battery_properties,
+ .num_properties = ARRAY_SIZE(samsung_fake_battery_properties),
+ .get_property = samsung_fake_bat_get_property,
+ }, {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = samsung_power_properties,
+ .num_properties = ARRAY_SIZE(samsung_power_properties),
+ .get_property = samsung_power_get_property,
+ }, {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = samsung_power_properties,
+ .num_properties = ARRAY_SIZE(samsung_power_properties),
+ .get_property = samsung_power_get_property,
+ },
+};
+
+static int samsung_cable_status_update(int status)
+{
+ int ret = 0;
+ charger_type_t source = CHARGER_BATTERY;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!samsung_fake_battery_initial)
+ return -EPERM;
+
+ switch (status) {
+ case CHARGER_BATTERY:
+ dev_dbg(dev, "%s : cable NOT PRESENT\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_BATTERY;
+ break;
+ case CHARGER_USB:
+ dev_dbg(dev, "%s : cable USB\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_USB;
+ break;
+ case CHARGER_AC:
+ dev_dbg(dev, "%s : cable AC\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_AC;
+ break;
+ case CHARGER_DISCHARGE:
+ dev_dbg(dev, "%s : Discharge\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_DISCHARGE;
+ break;
+ default:
+ dev_err(dev, "%s : Nat supported status\n", __func__);
+ ret = -EINVAL;
+ }
+ source = samsung_fake_bat_info.bat_info.charging_source;
+
+ if (source == CHARGER_USB || source == CHARGER_AC)
+ wake_lock(&vbus_wake_lock);
+ else
+ wake_lock_timeout(&vbus_wake_lock, HZ / 2);
+
+ /* if the power source changes, all power supplies may change state */
+ power_supply_changed(&samsung_power_supplies[CHARGER_BATTERY]);
+
+ dev_dbg(dev, "%s : call power_supply_changed\n", __func__);
+ return ret;
+}
+
+static void samsung_fake_bat_status_update(struct power_supply *bat_ps)
+{
+ int old_level, old_temp, old_is_full;
+ dev_dbg(dev, "%s ++\n", __func__);
+
+ if (!samsung_fake_battery_initial)
+ return;
+
+ mutex_lock(&work_lock);
+
+ old_temp = samsung_fake_bat_info.bat_info.batt_temp;
+ old_level = samsung_fake_bat_info.bat_info.level;
+ old_is_full = samsung_fake_bat_info.bat_info.batt_is_full;
+ samsung_fake_bat_info.bat_info.batt_temp = samsung_get_bat_temp(bat_ps);
+ samsung_fake_bat_info.bat_info.level = samsung_get_bat_level(bat_ps);
+ samsung_fake_bat_info.bat_info.batt_vol = samsung_get_bat_vol(bat_ps);
+
+ if (old_level != samsung_fake_bat_info.bat_info.level ||
+ old_temp != samsung_fake_bat_info.bat_info.batt_temp ||
+ old_is_full != samsung_fake_bat_info.bat_info.batt_is_full) {
+ power_supply_changed(bat_ps);
+ dev_dbg(dev, "%s : call power_supply_changed\n", __func__);
+ }
+
+ mutex_unlock(&work_lock);
+ dev_dbg(dev, "%s --\n", __func__);
+}
+
+void samsung_cable_check_status(int flag)
+{
+ charger_type_t status = 0;
+
+ if (flag == 0)
+ status = CHARGER_BATTERY;
+ else
+ status = CHARGER_USB;
+
+ samsung_cable_status_update(status);
+}
+EXPORT_SYMBOL(samsung_cable_check_status);
+
+#ifdef CONFIG_PM
+static int samsung_fake_bat_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static void samsung_fake_bat_resume(struct device *dev)
+{
+}
+#else
+#define samsung_fake_bat_suspend NULL
+#define samsung_fake_bat_resume NULL
+#endif /* CONFIG_PM */
+
+static int __devinit samsung_fake_bat_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret = 0;
+
+ dev = &pdev->dev;
+ dev_info(dev, "%s\n", __func__);
+
+ samsung_fake_bat_info.present = 1;
+
+ samsung_fake_bat_info.bat_info.batt_id = 0;
+ samsung_fake_bat_info.bat_info.batt_vol = 0;
+ samsung_fake_bat_info.bat_info.batt_vol_adc = 0;
+ samsung_fake_bat_info.bat_info.batt_vol_adc_cal = 0;
+ samsung_fake_bat_info.bat_info.batt_temp = 0;
+ samsung_fake_bat_info.bat_info.batt_temp_adc = 0;
+ samsung_fake_bat_info.bat_info.batt_temp_adc_cal = 0;
+ samsung_fake_bat_info.bat_info.batt_current = 0;
+ samsung_fake_bat_info.bat_info.level = 0;
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_BATTERY;
+ samsung_fake_bat_info.bat_info.charging_enabled = 0;
+ samsung_fake_bat_info.bat_info.batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /* init power supplier framework */
+ for (i = 0; i < ARRAY_SIZE(samsung_power_supplies); i++) {
+ ret = power_supply_register(&pdev->dev,
+ &samsung_power_supplies[i]);
+ if (ret) {
+ dev_err(dev, "Failed to register"
+ "power supply %d,%d\n", i, ret);
+ goto __end__;
+ }
+ }
+
+ /* create sec detail attributes */
+ samsung_fake_bat_create_attrs(samsung_power_supplies[CHARGER_BATTERY].dev);
+
+ samsung_fake_battery_initial = 1;
+
+ samsung_fake_bat_status_update(
+ &samsung_power_supplies[CHARGER_BATTERY]);
+
+__end__:
+ return ret;
+}
+
+static int __devexit samsung_fake_bat_remove(struct platform_device *pdev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(samsung_power_supplies); i++)
+ power_supply_unregister(&samsung_power_supplies[i]);
+
+ return 0;
+}
+
+static const struct dev_pm_ops samsung_fake_bat_pm_ops = {
+ .prepare = samsung_fake_bat_suspend,
+ .complete = samsung_fake_bat_resume,
+};
+
+static struct platform_driver samsung_fake_bat_driver = {
+ .driver = {
+ .name = "samsung-fake-battery",
+ .owner = THIS_MODULE,
+ .pm = &samsung_fake_bat_pm_ops,
+ },
+ .probe = samsung_fake_bat_probe,
+ .remove = __devexit_p(samsung_fake_bat_remove),
+};
+
+static int __init samsung_fake_bat_init(void)
+{
+ wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
+
+ return platform_driver_register(&samsung_fake_bat_driver);
+}
+
+static void __exit samsung_fake_bat_exit(void)
+{
+ platform_driver_unregister(&samsung_fake_bat_driver);
+}
+
+module_init(samsung_fake_bat_init);
+module_exit(samsung_fake_bat_exit);
+
+MODULE_AUTHOR("HuiSung Kang <hs1218.kang@samsung.com>");
+MODULE_DESCRIPTION("SAMSUNG Fake battery driver for SMDK Board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sec_battery_px.c b/drivers/power/sec_battery_px.c
new file mode 100644
index 0000000..0b5d8d4
--- /dev/null
+++ b/drivers/power/sec_battery_px.c
@@ -0,0 +1,2148 @@
+/*
+ * sec_battery.c
+ * charger systems for lithium-ion (Li+) batteries
+ *
+ * Copyright (C) 2010 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/kernel.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/wakelock.h>
+#include <linux/android_alarm.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/earlysuspend.h>
+#include <linux/io.h>
+#include <mach/gpio.h>
+#include <linux/power/sec_battery_px.h>
+#include <linux/power/max17042_fuelgauge_px.h>
+#include <linux/mfd/max8997.h>
+
+#include <plat/adc.h>
+#include <mach/usb_switch.h>
+
+#define FAST_POLL 40 /* 40 sec */
+#define SLOW_POLL (30*60) /* 30 min */
+
+/* SIOP */
+#define CHARGING_CURRENT_HIGH_LOW_STANDARD 450
+#define CHARGING_CURRENT_USB 500
+#define SIOP_ACTIVE_CHARGE_CURRENT 450
+#define SIOP_DEACTIVE_CHARGE_CURRENT 1500
+
+enum {
+ SIOP_DEACTIVE = 0,
+ SIOP_ACTIVE,
+};
+
+#if defined(CONFIG_MACH_P2)
+#define P2_CHARGING_FEATURE_02 /* SMB136 + MAX17042, Cable detect by TA_nCon */
+#endif
+
+#if defined(CONFIG_MACH_P4NOTE)
+#define P4_CHARGING_FEATURE_01 /* SMB347 + MAX17042, use TA_nCON */
+#endif
+
+#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE)
+#define P8_CHARGING_FEATURE_01 /* MAX8903 + MAX17042, use TA_nCON */
+#endif
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static enum power_supply_property sec_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static enum power_supply_property sec_power_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+struct battery_info {
+ u32 batt_id; /* Battery ID from ADC */
+ s32 batt_vol; /* Battery voltage from ADC */
+ s32 batt_temp; /* Battery Temperature (C) from ADC */
+ s32 batt_current; /* Battery current from ADC */
+ u32 level; /* formula */
+ u32 charging_source; /* 0: no cable, 1:usb, 2:AC */
+ u32 charging_enabled; /* 0: Disable, 1: Enable */
+ u32 charging_current; /* Charging current */
+ u32 batt_full_count; /* full checked count */
+ u32 batt_health; /* Battery Health (Authority) */
+ u32 batt_is_full; /* 0 : Not full 1: Full */
+ u32 batt_is_recharging; /* 0 : Not recharging 1: Recharging */
+ u32 batt_improper_ta; /* 1: improper ta */
+ u32 abstimer_is_active; /* 0 : Not active 1: Active */
+ u32 siop_activated; /* 0 : Not active 1: Active */
+};
+
+struct battery_data {
+ struct device *dev;
+ struct sec_battery_platform_data *pdata;
+ struct battery_info info;
+ struct power_supply psy_battery;
+ struct power_supply psy_usb;
+ struct power_supply psy_ac;
+ struct workqueue_struct *sec_TA_workqueue;
+ struct work_struct battery_work;
+ struct work_struct cable_work;
+ struct delayed_work TA_work;
+ struct delayed_work fuelgauge_work;
+ struct delayed_work fuelgauge_recovery_work;
+ struct delayed_work fullcharging_work;
+ struct delayed_work full_comp_work;
+ struct alarm alarm;
+ struct mutex work_lock;
+ struct wake_lock vbus_wake_lock;
+ struct wake_lock cable_wake_lock;
+ struct wake_lock work_wake_lock;
+ struct wake_lock fullcharge_wake_lock;
+ struct s3c_adc_client *padc;
+
+#ifdef __TEST_DEVICE_DRIVER__
+ struct wake_lock wake_lock_for_dev;
+#endif /* __TEST_DEVICE_DRIVER__ */
+ enum charger_type current_cable_status;
+ enum charger_type previous_cable_status;
+ int present;
+ unsigned int device_state;
+ unsigned int charging_mode_booting;
+ int sec_battery_initial;
+ int low_batt_boot_flag;
+ u32 full_charge_comp_recharge_info;
+ unsigned long charging_start_time;
+ int connect_irq;
+ int charging_irq;
+ bool slow_poll;
+ ktime_t last_poll;
+ int fg_skip;
+ int fg_skip_cnt;
+ int fg_chk_cnt;
+ int recharging_cnt;
+ int previous_charging_status;
+ int full_check_flag;
+ bool is_first_check;
+ /* 0:Default, 1:Only charger, 2:Only PMIC */
+ int cable_detect_source;
+ int pmic_cable_state;
+ bool is_low_batt_alarm;
+};
+
+struct battery_data *debug_batterydata;
+
+static void sec_cable_charging(struct battery_data *battery);
+static void sec_set_chg_en(struct battery_data *battery, int enable);
+static void sec_cable_changed(struct battery_data *battery);
+static ssize_t sec_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+static ssize_t sec_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+#ifdef __TEST_DEVICE_DRIVER__
+static ssize_t sec_batt_test_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+static ssize_t sec_batt_test_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+static int bat_temp_force_state;
+#endif /* __TEST_DEVICE_DRIVER__ */
+
+static bool check_UV_charging_case(void);
+static void sec_bat_status_update(struct power_supply *bat_ps);
+
+static int get_cached_charging_status(struct battery_data *battery)
+{
+ struct power_supply *psy = power_supply_get_by_name("max8997-charger");
+ union power_supply_propval value;
+ u32 ta_con = 0;
+
+ if (!psy) {
+ pr_err("%s: fail to get max8997-charger ps\n", __func__);
+ return 0;
+ }
+
+ if (psy->get_property) {
+ psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value);
+ if (value.intval == POWER_SUPPLY_STATUS_CHARGING)
+ ta_con = 1;
+ }
+
+ pr_info("PMIC detect : %s\n", ta_con ? "TA connected" :
+ "TA NOT connected");
+ return ta_con;
+}
+
+static int check_ta_conn(struct battery_data *battery)
+{
+ u32 value;
+
+ if (battery->cable_detect_source == 2) {
+ value = get_cached_charging_status(battery);
+ pr_info("Cable detect from PMIC instead of TA_nConnected(%d)\n",
+ value);
+ return value;
+ }
+ value = gpio_get_value(battery->pdata->charger.connect_line);
+
+#if defined(P4_CHARGING_FEATURE_01)
+#if defined(CONFIG_MACH_P4NOTE)
+ value = !value;
+#else
+ /* P4C H/W rev0.2, 0.3, 0.4 : High active */
+ if ((system_rev >= 2) && (system_rev <= 5))
+ value = !value;
+#endif
+#endif
+ pr_debug("Charger detect : %s\n", value ? "TA NOT connected" :
+ "TA connected");
+ return !value;
+}
+
+#ifdef CONFIG_SAMSUNG_LPM_MODE
+static void lpm_mode_check(struct battery_data *battery)
+{
+ battery->charging_mode_booting = \
+ battery->pdata->check_lp_charging_boot();
+ pr_info("%s : charging_mode_booting(%d)\n", __func__,
+ battery->charging_mode_booting);
+}
+#endif
+
+#if defined(P4_CHARGING_FEATURE_01) || defined(P8_CHARGING_FEATURE_01)
+#define CHARGING_CURRENT_HIGH 1500
+#define CHARGING_CURRENT_LOW CHARGING_CURRENT_USB
+static void sec_set_charging(struct battery_data *battery, int charger_type)
+{
+ switch (charger_type) {
+ case CHARGER_AC:
+ gpio_set_value(battery->pdata->charger.currentset_line, 1);
+ battery->info.charging_current = CHARGING_CURRENT_HIGH;
+ break;
+ default:
+ /* For P8 Audio station, the charging current driven by TA
+ path is not as per the required spec hence we drive the
+ input charging current by USB path for Sound Station.*/
+ gpio_set_value(battery->pdata->charger.currentset_line, 0);
+ battery->info.charging_current = CHARGING_CURRENT_LOW;
+ break;
+ }
+ pr_info("%s: Set charging current as %dmA.\n", __func__,
+ battery->info.charging_current);
+}
+#endif
+
+static void sec_battery_alarm(struct alarm *alarm)
+{
+ struct battery_data *battery =
+ container_of(alarm, struct battery_data, alarm);
+
+ pr_debug("%s : sec_battery_alarm.....\n", __func__);
+ wake_lock(&battery->work_wake_lock);
+ schedule_work(&battery->battery_work);
+}
+
+static void sec_program_alarm(struct battery_data *battery, int seconds)
+{
+ ktime_t low_interval = ktime_set(seconds - 10, 0);
+ ktime_t slack = ktime_set(20, 0);
+ ktime_t next;
+
+ pr_debug("%s : sec_program_alarm.....\n", __func__);
+ next = ktime_add(battery->last_poll, low_interval);
+ alarm_start_range(&battery->alarm, next, ktime_add(next, slack));
+}
+
+static
+enum charger_type sec_get_dedicted_charger_type(struct battery_data *battery)
+{
+ /* N.B. If check_ta_conn() is true something valid is
+ connceted to the device for charging.
+ By default this something is considered to be USB.*/
+ enum charger_type result = CHARGER_USB;
+ int avg_vol = 0;
+ int adc_1, adc_2;
+ int vol_1, vol_2;
+ int accessory_line;
+
+ mutex_lock(&battery->work_lock);
+
+ /* ADC check margin (300~500ms) */
+ msleep(150);
+
+ usb_switch_lock();
+ usb_switch_set_path(USB_PATH_ADCCHECK);
+
+#if defined(CONFIG_STMPE811_ADC)
+ vol_1 = stmpe811_get_adc_data(6);
+ vol_2 = stmpe811_get_adc_data(6);
+#else
+ /* ADC values Update Margin */
+ msleep(30);
+
+ adc_1 = s3c_adc_read(battery->padc, 5);
+ adc_2 = s3c_adc_read(battery->padc, 5);
+
+ vol_1 = (adc_1 * 3300) / 4095;
+ vol_2 = (adc_2 * 3300) / 4095;
+#endif
+ avg_vol = (vol_1 + vol_2)/2;
+
+ if ((avg_vol > 800) && (avg_vol < 1800)) {
+#if defined(P4_CHARGING_FEATURE_01)
+ accessory_line = gpio_get_value(
+ battery->pdata->charger.accessory_line);
+ pr_info("%s: accessory line(%d)\n", __func__, accessory_line);
+
+ if (accessory_line == 0) /* HDMI dock cable connected*/
+ result = CHARGER_DOCK;
+ else
+ result = CHARGER_AC;
+#else
+ result = CHARGER_AC; /* TA connected. */
+#endif
+ } else if ((avg_vol > 550) && (avg_vol < 700))
+ result = CHARGER_MISC; /* Samsung Audio Station connected.*/
+ else
+ result = CHARGER_USB;
+
+ usb_switch_clr_path(USB_PATH_ADCCHECK);
+ usb_switch_unlock();
+
+ mutex_unlock(&battery->work_lock);
+
+ pr_info("%s : result(%d), avg_vol(%d)\n", __func__, result, avg_vol);
+ return result;
+}
+
+static void sec_get_cable_status(struct battery_data *battery)
+{
+ if (check_ta_conn(battery)) {
+ battery->current_cable_status =
+ sec_get_dedicted_charger_type(battery);
+ battery->info.batt_full_count = 0;
+ } else {
+ battery->current_cable_status = CHARGER_BATTERY;
+ battery->info.batt_improper_ta = 0;
+ }
+
+ if (battery->pdata->inform_charger_connection)
+ battery->pdata->inform_charger_connection(
+ battery->current_cable_status);
+
+ pr_info("%s: current_status : %d\n",
+ __func__, battery->current_cable_status);
+}
+
+/* Processes when the interrupt line is asserted. */
+#define TA_DISCONNECT_RECHECK_TIME 300
+static void sec_TA_work_handler(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data, TA_work.work);
+ int ta_state = 0;
+
+ /* Prevent unstable VBUS signal from PC */
+ ta_state = check_ta_conn(battery);
+ if (ta_state == 0) {
+ msleep(TA_DISCONNECT_RECHECK_TIME);
+ if (ta_state != check_ta_conn(battery)) {
+ pr_info("%s: unstable ta_state(%d), ignore it.\n",
+ __func__, ta_state);
+ enable_irq(battery->connect_irq);
+ return;
+ }
+ }
+ pr_info("%s: ta_state(%d)\n", __func__, ta_state);
+
+ sec_get_cable_status(battery);
+ sec_cable_changed(battery);
+
+ enable_irq(battery->connect_irq);
+}
+
+static irqreturn_t sec_TA_nCHG_interrupt_handler(int irq, void *arg)
+{
+ struct battery_data *battery = (struct battery_data *)arg;
+ pr_info("%s(%d)\n", __func__,
+ gpio_get_value(battery->pdata->charger.fullcharge_line));
+
+ disable_irq_nosync(irq);
+ cancel_delayed_work(&battery->fullcharging_work);
+ schedule_delayed_work(&battery->fullcharging_work,
+ msecs_to_jiffies(300));
+ wake_lock_timeout(&battery->fullcharge_wake_lock, HZ * 30);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sec_TA_nCon_interrupt_handler(int irq, void *arg)
+{
+ struct battery_data *battery = (struct battery_data *)arg;
+ pr_info("%s(%d)\n", __func__,
+ gpio_get_value(battery->pdata->charger.connect_line));
+
+ disable_irq_nosync(irq);
+ cancel_delayed_work(&battery->TA_work);
+ queue_delayed_work(battery->sec_TA_workqueue, &battery->TA_work, 10);
+ wake_lock_timeout(&battery->cable_wake_lock, HZ * 2);
+
+ return IRQ_HANDLED;
+}
+
+static u32 get_charger_status(struct battery_data *battery)
+{
+ if (check_ta_conn(battery)) {
+ if (battery->current_cable_status == CHARGER_AC)
+ pr_info("Charger Status : AC\n");
+ else if (battery->current_cable_status == CHARGER_DOCK)
+ pr_info("Charger Status : HDMI_DOCK\n");
+ else if (battery->current_cable_status == CHARGER_MISC)
+ pr_info("Charger Status : MISC\n");
+ else
+ pr_info("Charger Status : USB\n");
+ return 1;
+ } else {
+ pr_info("Charger Status : Battery\n");
+ return 0;
+ }
+}
+
+static int is_over_abs_time(struct battery_data *battery)
+{
+ unsigned int total_time;
+ ktime_t ktime;
+ struct timespec cur_time;
+
+ if (!battery->charging_start_time)
+ return 0;
+
+ ktime = alarm_get_elapsed_realtime();
+ cur_time = ktime_to_timespec(ktime);
+
+ if (battery->info.batt_is_recharging)
+ total_time = battery->pdata->recharge_duration;
+ else
+ total_time = battery->pdata->charge_duration;
+
+ if (battery->charging_start_time + total_time < cur_time.tv_sec) {
+ pr_info("Charging time out");
+ return 1;
+ } else
+ return 0;
+}
+
+static int sec_get_bat_level(struct power_supply *bat_ps)
+{
+ struct battery_data *battery = container_of(bat_ps,
+ struct battery_data, psy_battery);
+ 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();
+
+ /* check VFcapacity every five minutes */
+ if (!(battery->fg_chk_cnt++ % 10)) {
+ fg_check_vf_fullcap_range();
+ battery->fg_chk_cnt = 1;
+ }
+
+ fg_soc = get_fuelgauge_value(FG_LEVEL);
+ if (fg_soc < 0) {
+ pr_info("Can't read soc!!!");
+ fg_soc = battery->info.level;
+ }
+
+ if (!battery->pdata->check_jig_status() && \
+ !max17042_chip_data->info.low_batt_comp_flag) {
+ if (((fg_soc+5) < max17042_chip_data->info.prev_repsoc) ||
+ (fg_soc > (max17042_chip_data->info.prev_repsoc+5)))
+ battery->fg_skip = 1;
+ }
+
+ /* skip one time (maximum 30 seconds) because of corruption. */
+ if (battery->fg_skip) {
+ pr_info("%s: skip update until corruption check "
+ "is done (fg_skip_cnt:%d)\n",
+ __func__, ++battery->fg_skip_cnt);
+ fg_soc = battery->info.level;
+ if (recover_flag || battery->fg_skip_cnt > 10) {
+ battery->fg_skip = 0;
+ battery->fg_skip_cnt = 0;
+ }
+ }
+
+ if (battery->low_batt_boot_flag) {
+ fg_soc = 0;
+
+ if (check_ta_conn(battery) && !check_UV_charging_case()) {
+ fg_adjust_capacity();
+ battery->low_batt_boot_flag = 0;
+ }
+
+ if (!check_ta_conn(battery))
+ battery->low_batt_boot_flag = 0;
+ }
+
+ fg_vcell = get_fuelgauge_value(FG_VOLTAGE);
+ if (fg_vcell < 0) {
+ pr_info("Can't read vcell!!!");
+ fg_vcell = battery->info.batt_vol;
+ } else
+ battery->info.batt_vol = fg_vcell;
+
+ fg_current = get_fuelgauge_value(FG_CURRENT);
+ battery->info.batt_current = fg_current;
+ avg_current = get_fuelgauge_value(FG_CURRENT_AVG);
+ fg_vfsoc = get_fuelgauge_value(FG_VF_SOC);
+
+/* P4-Creative does not set full flag by force */
+#if !defined(CONFIG_MACH_P4NOTE)
+ /* Algorithm for reducing time to fully charged (from MAXIM) */
+ if (battery->info.charging_enabled && /* Charging is enabled */
+ !battery->info.batt_is_recharging && /* Not Recharging */
+ ((battery->info.charging_source == CHARGER_AC) ||
+ (battery->info.charging_source == CHARGER_MISC)) &&
+ !battery->is_first_check && /* Skip the first check */
+ (fg_vfsoc > 70 && (fg_current > 20 && fg_current < 250) &&
+ (avg_current > 20 && avg_current < 260))) {
+
+ if (battery->full_check_flag == 2) {
+ pr_info("%s: force fully charged SOC !! (%d)", __func__,
+ battery->full_check_flag);
+ fg_set_full_charged();
+ fg_soc = get_fuelgauge_value(FG_LEVEL);
+ } else if (battery->full_check_flag < 2)
+ pr_info("%s: full_check_flag (%d)", __func__,
+ battery->full_check_flag);
+
+ /* prevent overflow */
+ if (battery->full_check_flag++ > 10000)
+ battery->full_check_flag = 3;
+ } else
+ battery->full_check_flag = 0;
+#endif
+
+ if (battery->info.charging_source == CHARGER_AC &&
+ battery->info.batt_improper_ta == 0) {
+ if (is_over_abs_time(battery)) {
+ battery->info.abstimer_is_active = 1;
+ pr_info("%s: Charging time is over. Active abs timer.",
+ __func__);
+ pr_info(" VCELL(%d), SOC(%d)\n", fg_vcell, fg_soc);
+ sec_set_chg_en(battery, 0);
+ goto __end__;
+ }
+ } else {
+ if (battery->info.abstimer_is_active) {
+ battery->info.abstimer_is_active = 0;
+ pr_info("%s: Inactive abs timer.\n", __func__);
+ }
+ }
+
+ if ((fg_vcell <= battery->pdata->recharge_voltage) ||
+ (fg_vcell <= 4000)) {
+ if (battery->info.batt_is_full
+ || (battery->info.abstimer_is_active
+ && !battery->info.charging_enabled)) {
+ if (++battery->recharging_cnt > 1) {
+ pr_info("%s: Recharging start(%d ? %d).\n",
+ __func__,
+ fg_vcell,
+ battery->pdata->recharge_voltage);
+ battery->info.batt_is_recharging = 1;
+ sec_set_chg_en(battery, 1);
+ battery->recharging_cnt = 0;
+ }
+ } else
+ battery->recharging_cnt = 0;
+ } else
+ battery->recharging_cnt = 0;
+
+ if (fg_soc > 100)
+ fg_soc = 100;
+
+ /* Checks vcell level and tries to compensate SOC if needed.*/
+ /* If jig cable is connected, then skip low batt compensation check. */
+ if (!battery->pdata->check_jig_status() && \
+ !battery->info.charging_enabled)
+ fg_soc = low_batt_compensation(fg_soc, fg_vcell, fg_current);
+
+ /* Compensation for Charging cut off current in P8.
+ * On P8 we increase the charging current to 1.8A
+ * to complete charging in spec time.
+ * This causes the cut-off/ termination current to be high and
+ * hence to set the charging cut -off current to expected value
+ * we control it through ADC channel 6 of AP.
+ * Due to some prob in HW sometimes the CURRENT_MEA goes
+ * below V_TOP_OFF even for very low SOCs hence
+ * check for CURRENT_MEA signal only at
+ * a very later stage of charging process.
+ * For now this code is enabled only for P8-LTE.
+ * Enable it for other P8 Models only if HW is updated.
+ */
+#ifdef CONFIG_MACH_P8LTE
+ /* Charging is done using TA only. */
+ /* TA should be a proper connection. */
+ /* Charging should be Enabled. */
+ /* Should not be in Recharging Path. */
+ /* VCELL > 4.19V. */
+ /* VFSOC > 72. 72 * 1.333 = 95.976 */
+ if ((battery->info.charging_source == CHARGER_AC) &&
+ (battery->info.batt_improper_ta == 0) &&
+ (battery->info.charging_enabled) &&
+ (battery->info.batt_is_recharging == 0) &&
+ (fg_vcell > 4190) &&
+ (fg_vfsoc > 72)) {
+ /* Read ADC Voltage. */
+ if (battery->padc) {
+ int Viset = s3c_adc_read(battery->padc,
+ SEC_CURR_MEA_ADC_CH);
+
+ if (!Viset) {
+ /* Connecting TA after boot shows latency
+ * in ADC update for the first time.
+ * Wait for ~700ms the ADC value to be updated.
+ * This is just a conservative number.
+ */
+ pr_info(
+ "%s: Waiting ADC on Ch6 to be updated\n",
+ __func__);
+ mdelay(700);
+ Viset = s3c_adc_read(battery->padc,
+ SEC_CURR_MEA_ADC_CH);
+ }
+
+ if (Viset < V_TOP_OFF) {
+ if (battery->info.batt_full_count++ <
+ COUNT_TOP_OFF) {
+ pr_info(
+ "%s: Charging current < top-off\n",
+ __func__);
+ } else {
+ /* Disable the charging process. */
+ pr_err(
+ "%s: Charging Disabled: V-topoff(%d)",
+ __func__, V_TOP_OFF);
+ pr_err(" is more than V-Iset(%d)\n",
+ Viset);
+
+ battery->info.batt_full_count = 0;
+ sec_cable_charging(battery);
+ }
+ } else
+ battery->info.batt_full_count = 0;
+ } else {
+ pr_err(
+ "%s: Can't read the ADC regsiter\n",
+ __func__);
+ }
+ }
+#endif
+
+__end__:
+ pr_debug("fg_vcell = %d, fg_soc = %d, is_full = %d, full_count = %d",
+ fg_vcell, fg_soc,
+ battery->info.batt_is_full,
+ battery->info.batt_full_count);
+
+ if (battery->is_first_check)
+ battery->is_first_check = false;
+
+ if (battery->info.batt_is_full &&
+ (battery->info.charging_source != CHARGER_USB))
+ fg_soc = 100;
+
+ return fg_soc;
+}
+
+static int sec_get_bat_vol(struct power_supply *bat_ps)
+{
+ struct battery_data *battery = container_of(bat_ps,
+ struct battery_data, psy_battery);
+ return battery->info.batt_vol;
+}
+
+static bool check_UV_charging_case(void)
+{
+ int fg_vcell = get_fuelgauge_value(FG_VOLTAGE);
+ int fg_current = get_fuelgauge_value(FG_CURRENT);
+ int threshold = 0;
+
+ if (get_fuelgauge_value(FG_BATTERY_TYPE) == SDI_BATTERY_TYPE)
+ threshold = 3300 + ((fg_current * 17) / 100);
+ else if (get_fuelgauge_value(FG_BATTERY_TYPE) == ATL_BATTERY_TYPE)
+ threshold = 3300 + ((fg_current * 13) / 100);
+
+ if (fg_vcell <= threshold)
+ return 1;
+ else
+ return 0;
+}
+
+static void sec_set_time_for_charging(struct battery_data *battery, int mode)
+{
+ if (mode) {
+ ktime_t ktime;
+ struct timespec cur_time;
+
+ ktime = alarm_get_elapsed_realtime();
+ cur_time = ktime_to_timespec(ktime);
+
+ /* record start time for abs timer */
+ battery->charging_start_time = cur_time.tv_sec;
+ } else {
+ /* initialize start time for abs timer */
+ battery->charging_start_time = 0;
+ }
+}
+
+static void disable_internal_charger(void)
+{
+ struct power_supply *internal_psy = \
+ power_supply_get_by_name("max8997-charger");
+ union power_supply_propval value;
+ int ret;
+
+ /* Disable inner charger(MAX8997) */
+ if (internal_psy) {
+ value.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = internal_psy->set_property(internal_psy, \
+ POWER_SUPPLY_PROP_STATUS, &value);
+ if (ret)
+ pr_err("%s: fail to set %s discharging status(%d)\n",
+ __func__, internal_psy->name, ret);
+ }
+}
+static void sec_set_chg_current(struct battery_data *battery, int set_current)
+{
+#if defined(P8_CHARGING_FEATURE_01)
+ if ((set_current > CHARGING_CURRENT_HIGH_LOW_STANDARD) && \
+ (battery->current_cable_status == CHARGER_AC))
+ sec_set_charging(battery, CHARGER_AC);
+ else if (battery->current_cable_status == CHARGER_MISC)
+ sec_set_charging(battery, CHARGER_MISC);
+ else
+ sec_set_charging(battery, CHARGER_USB);
+#else /* P4C H/W rev0.0 does not support yet */
+ if (battery->current_cable_status == CHARGER_AC) {
+ if (battery->pdata->set_charging_current)
+ battery->pdata->set_charging_current((int)set_current);
+
+ if (battery->pdata->get_charging_current)
+ battery->info.charging_current = \
+ battery->pdata->get_charging_current();
+ } else {
+ battery->info.charging_current = CHARGING_CURRENT_USB;
+ }
+#endif
+}
+
+static void sec_set_chg_en(struct battery_data *battery, int enable)
+{
+#if defined(P2_CHARGING_FEATURE_02) || defined(P4_CHARGING_FEATURE_02)
+ /* Disable internal charger(MAX8997) */
+ /* because of using external charger */
+ disable_internal_charger();
+
+ if (battery->pdata->set_charging_state) {
+ if (battery->current_cable_status == CHARGER_AC)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_TA);
+ else if (battery->current_cable_status == CHARGER_MISC)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_STATION);
+ else
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_USB);
+ }
+
+ if ((enable) || (battery->pdata->get_charging_current))
+ battery->info.charging_current = \
+ battery->pdata->get_charging_current();
+
+ sec_set_time_for_charging(battery, enable);
+
+ if (!enable)
+ battery->info.batt_is_recharging = 0;
+
+ pr_info("%s: External charger is %s", __func__, (enable ? "enabled." : \
+ "disabled."));
+#elif defined(P4_CHARGING_FEATURE_01)
+ int charger_enable_line = battery->pdata->charger.enable_line;
+
+ /* Disable internal charger(MAX8997) */
+ /* because of using external charger */
+ disable_internal_charger();
+
+ /* In case of HDMI connecting, set charging current 1.5A */
+#if defined(CONFIG_MACH_P4NOTE)
+ if (battery->pdata->set_charging_state) {
+ if (battery->current_cable_status == CHARGER_AC)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_TA);
+ else if (battery->current_cable_status == CHARGER_MISC)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_STATION);
+ else if (battery->current_cable_status == CHARGER_DOCK)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_DESKDOCK);
+ else
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_USB);
+ }
+
+ if (battery->pdata->get_charging_current)
+ battery->info.charging_current = \
+ battery->pdata->get_charging_current();
+
+ sec_set_time_for_charging(battery, enable);
+
+ if (!enable)
+ battery->info.batt_is_recharging = 0;
+#else
+ if (system_rev >= 2) {
+ if (battery->pdata->set_charging_state) {
+ if (battery->current_cable_status == CHARGER_AC)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_TA);
+ else if (battery->current_cable_status == CHARGER_MISC)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_STATION);
+ else if (battery->current_cable_status == CHARGER_DOCK)
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_DESKDOCK);
+ else
+ battery->pdata->set_charging_state(enable,
+ CABLE_TYPE_USB);
+ }
+
+ if (battery->pdata->get_charging_current)
+ battery->info.charging_current = \
+ battery->pdata->get_charging_current();
+
+ sec_set_time_for_charging(battery, enable);
+
+ if (!enable)
+ battery->info.batt_is_recharging = 0;
+ } else {
+ if (enable) {
+ sec_set_charging(battery,
+ battery->current_cable_status);
+ gpio_set_value(charger_enable_line, 0);
+
+ pr_info("%s: Enabling external charger ", __func__);
+ sec_set_time_for_charging(battery, 1);
+ } else {
+ gpio_set_value(charger_enable_line, 1);
+ pr_info("%s: Disabling external charger ", __func__);
+
+ sec_set_time_for_charging(battery, 0);
+ battery->info.batt_is_recharging = 0;
+ }
+ }
+#endif
+#else
+ int charger_enable_line = battery->pdata->charger.enable_line;
+
+ /* Disable internal charger(MAX8997) */
+ /* because of using external charger */
+ disable_internal_charger();
+
+ if (enable) {
+ sec_set_charging(battery, battery->current_cable_status);
+ gpio_set_value(charger_enable_line, 0);
+
+ pr_info("%s: Enabling the external charger ", __func__);
+ sec_set_time_for_charging(battery, 1);
+ } else {
+ gpio_set_value(charger_enable_line, 1);
+ pr_info("%s: Disabling the external charger ", __func__);
+
+ sec_set_time_for_charging(battery, 0);
+ battery->info.batt_is_recharging = 0;
+ }
+#endif
+ battery->info.charging_enabled = enable;
+}
+
+static void sec_temp_control(struct battery_data *battery, int mode)
+{
+ switch (mode) {
+ case POWER_SUPPLY_HEALTH_GOOD:
+ pr_debug("GOOD");
+ battery->info.batt_health = mode;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ pr_debug("OVERHEAT");
+ battery->info.batt_health = mode;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ pr_debug("FREEZE");
+ battery->info.batt_health = mode;
+ break;
+ default:
+ break;
+ }
+ schedule_work(&battery->cable_work);
+}
+
+static int sec_get_bat_temp(struct power_supply *bat_ps)
+{
+ struct battery_data *battery = container_of(bat_ps,
+ struct battery_data, psy_battery);
+ int health = battery->info.batt_health;
+ int update = 0;
+ int battery_temp;
+ int temp_high_threshold;
+ int temp_high_recovery;
+ int temp_low_threshold;
+ int temp_low_recovery;
+
+ battery_temp = get_fuelgauge_value(FG_TEMPERATURE);
+
+ temp_high_threshold = battery->pdata->temp_high_threshold;
+ temp_high_recovery = battery->pdata->temp_high_recovery;
+ temp_low_threshold = battery->pdata->temp_low_threshold;
+ temp_low_recovery = battery->pdata->temp_low_recovery;
+
+#ifdef __TEST_DEVICE_DRIVER__
+ switch (bat_temp_force_state) {
+ case 0:
+ break;
+ case 1:
+ battery_temp = temp_high_threshold + 1;
+ break;
+ case 2:
+ battery_temp = temp_low_threshold - 1;
+ break;
+ default:
+ break;
+ }
+#endif /* __TEST_DEVICE_DRIVER__ */
+
+ if (battery_temp >= temp_high_threshold) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
+ pr_info("battery overheat(%d>=%d),charging unavailable",
+ battery_temp, temp_high_threshold);
+ sec_temp_control(battery, POWER_SUPPLY_HEALTH_OVERHEAT);
+ update = 1;
+ }
+ } else if (battery_temp <= temp_high_recovery &&
+ battery_temp >= temp_low_recovery) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ health == POWER_SUPPLY_HEALTH_COLD) {
+ pr_info("battery recovery(%d,%d~%d),charging available",
+ battery_temp, temp_low_recovery,
+ temp_high_recovery);
+ sec_temp_control(battery, POWER_SUPPLY_HEALTH_GOOD);
+ update = 1;
+ }
+ } else if (battery_temp <= temp_low_threshold) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
+ pr_info("battery cold(%d <= %d), charging unavailable.",
+ battery_temp, temp_low_threshold);
+ sec_temp_control(battery, POWER_SUPPLY_HEALTH_COLD);
+ update = 1;
+ }
+ }
+
+ if (update)
+ pr_info("sec_get_bat_temp = %d.", battery_temp);
+
+ return battery_temp/100;
+}
+
+static int sec_bat_get_charging_status(struct battery_data *battery)
+{
+ switch (battery->info.charging_source) {
+ case CHARGER_BATTERY:
+ case CHARGER_USB:
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ case CHARGER_AC:
+ case CHARGER_MISC:
+ case CHARGER_DOCK:
+ if (battery->info.batt_is_full ||
+ battery->info.level == 100)
+ return POWER_SUPPLY_STATUS_FULL;
+ else if (battery->info.batt_improper_ta)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ return POWER_SUPPLY_STATUS_CHARGING;
+ case CHARGER_DISCHARGE:
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ default:
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+}
+
+static int sec_bat_set_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct battery_data *battery = container_of(ps,
+ struct battery_data, psy_battery);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ battery->pmic_cable_state = val->intval;
+ pr_info("PMIC cable state: %d\n", battery->pmic_cable_state);
+ if (battery->cable_detect_source == 2) {
+ /* cable is attached or detached.
+ called by USB switch(MUIC) */
+ sec_get_cable_status(battery);
+ sec_cable_changed(battery);
+ }
+ battery->cable_detect_source = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sec_bat_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct battery_data *battery = container_of(bat_ps,
+ struct battery_data, psy_battery);
+
+ pr_debug("psp = %d", psp);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = sec_bat_get_charging_status(battery);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = battery->info.batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = battery->present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = battery->info.level;
+ pr_info("level = %d\n", val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = battery->info.batt_temp;
+ pr_debug("temp = %d\n", val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sec_usb_get_property(struct power_supply *usb_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct battery_data *battery = container_of(usb_ps,
+ struct battery_data, psy_usb);
+
+ enum charger_type charger = battery->info.charging_source;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (charger == CHARGER_USB ? 1 : 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sec_ac_get_property(struct power_supply *ac_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct battery_data *battery = container_of(ac_ps,
+ struct battery_data, psy_ac);
+
+ enum charger_type charger = battery->info.charging_source;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if ((charger == CHARGER_AC) || (charger == CHARGER_MISC)
+ || (charger == CHARGER_DOCK))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define SEC_BATTERY_ATTR(_name)\
+{\
+ .attr = { .name = #_name, .mode = S_IRUGO | (S_IWUSR | S_IWGRP) },\
+ .show = sec_bat_show_property,\
+ .store = sec_bat_store,\
+}
+
+static struct device_attribute sec_battery_attrs[] = {
+ SEC_BATTERY_ATTR(batt_vol),
+ SEC_BATTERY_ATTR(temp),
+#ifdef CONFIG_MACH_SAMSUNG_P5
+ SEC_BATTERY_ATTR(batt_temp_cels),
+#endif
+ SEC_BATTERY_ATTR(batt_charging_source),
+ SEC_BATTERY_ATTR(fg_soc),
+#if defined(CONFIG_MACH_P4NOTE)
+ SEC_BATTERY_ATTR(batt_reset_soc),
+#else
+ SEC_BATTERY_ATTR(reset_soc),
+#endif
+ SEC_BATTERY_ATTR(fg_reset_cap),
+ SEC_BATTERY_ATTR(fg_reg),
+ SEC_BATTERY_ATTR(batt_type),
+ SEC_BATTERY_ATTR(batt_temp_check),
+ SEC_BATTERY_ATTR(batt_full_check),
+ SEC_BATTERY_ATTR(batt_current_now),
+ SEC_BATTERY_ATTR(siop_activated),
+#ifdef CONFIG_SAMSUNG_LPM_MODE
+ SEC_BATTERY_ATTR(batt_lp_charging),
+ SEC_BATTERY_ATTR(voltage_now),
+#endif
+ SEC_BATTERY_ATTR(jig_on),
+ SEC_BATTERY_ATTR(fg_capacity),
+};
+
+enum {
+ BATT_VOL = 0,
+ BATT_TEMP,
+#ifdef CONFIG_MACH_SAMSUNG_P5
+ BATT_TEMP_CELS,
+#endif
+ BATT_CHARGING_SOURCE,
+ BATT_FG_SOC,
+ BATT_RESET_SOC,
+ BATT_RESET_CAP,
+ BATT_FG_REG,
+ BATT_BATT_TYPE,
+ BATT_TEMP_CHECK,
+ BATT_FULL_CHECK,
+ BATT_CURRENT_NOW,
+ SIOP_ACTIVATED,
+#ifdef CONFIG_SAMSUNG_LPM_MODE
+ BATT_LP_CHARGING,
+ VOLTAGE_NOW,
+#endif
+ JIG_ON,
+ FG_CAPACITY,
+};
+
+static int sec_bat_create_attrs(struct device *dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(sec_battery_attrs); i++) {
+ rc = device_create_file(dev, &sec_battery_attrs[i]);
+ if (rc)
+ goto sec_attrs_failed;
+ }
+ return 0;
+
+sec_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sec_battery_attrs[i]);
+ return rc;
+}
+
+static ssize_t sec_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+#ifdef CONFIG_MACH_SAMSUNG_P5
+ s32 temp = 0;
+#endif
+ u8 batt_str[5];
+ const ptrdiff_t off = attr - sec_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ get_fuelgauge_value(FG_VOLTAGE));
+ break;
+ case BATT_TEMP:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->info.batt_temp);
+ break;
+#ifdef CONFIG_MACH_SAMSUNG_P5
+ case BATT_TEMP_CELS:
+ temp = ((debug_batterydata->info.batt_temp) / 10);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ temp);
+ break;
+#endif
+ case BATT_CHARGING_SOURCE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->info.charging_source);
+ break;
+ case BATT_FG_SOC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ get_fuelgauge_value(FG_LEVEL));
+ break;
+ case BATT_BATT_TYPE:
+ if (get_fuelgauge_value(FG_BATTERY_TYPE) == SDI_BATTERY_TYPE)
+ sprintf(batt_str, "SDI");
+ else if (get_fuelgauge_value(FG_BATTERY_TYPE) ==
+ ATL_BATTERY_TYPE)
+ sprintf(batt_str, "ATL");
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%s_%s\n",
+ batt_str, batt_str);
+ break;
+ case BATT_TEMP_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->info.batt_health);
+ break;
+ case BATT_FULL_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->info.batt_is_full);
+ break;
+ case BATT_CURRENT_NOW:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->info.charging_current);
+ break;
+ case SIOP_ACTIVATED:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->info.siop_activated);
+ break;
+#ifdef CONFIG_SAMSUNG_LPM_MODE
+ case BATT_LP_CHARGING:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->charging_mode_booting);
+ break;
+ case VOLTAGE_NOW:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ get_fuelgauge_value(FG_VOLTAGE_NOW));
+ break;
+#endif
+ case JIG_ON:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ debug_batterydata->pdata->check_jig_status());
+ break;
+ case FG_CAPACITY:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "0x%04x 0x%04x 0x%04x 0x%04x\n",
+ get_fuelgauge_capacity(CAPACITY_TYPE_FULL),
+ get_fuelgauge_capacity(CAPACITY_TYPE_MIX),
+ get_fuelgauge_capacity(CAPACITY_TYPE_AV),
+ get_fuelgauge_capacity(CAPACITY_TYPE_REP));
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t sec_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int x = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - sec_battery_attrs;
+
+ switch (off) {
+ case BATT_RESET_SOC:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ if (x == 1) {
+ if (!check_ta_conn(debug_batterydata))
+ fg_reset_soc();
+ }
+ ret = count;
+ }
+ sec_bat_status_update(&debug_batterydata->psy_battery);
+ pr_debug("Reset SOC:%d ", x);
+ break;
+ case BATT_RESET_CAP:
+ if (sscanf(buf, "%d\n", &x) == 1 ||
+ x == 2 || x == 3 || x == 4) {
+ if (x == 1 || x == 2 ||
+ x == 3 || x == 4)
+ fg_reset_capacity();
+ ret = count;
+ }
+ pr_debug("%s: Reset CAP:%d\n", __func__, x);
+ break;
+ case BATT_FG_REG:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ if (x == 1)
+ fg_periodic_read();
+ ret = count;
+ }
+ pr_debug("%s: FG Register:%d\n", __func__, x);
+ break;
+ case BATT_CURRENT_NOW:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ sec_set_chg_current(debug_batterydata, (int)x);
+ ret = count;
+ }
+ pr_debug("%s: BATT_CURRENT_NOW :%d\n", __func__, x);
+ break;
+ case SIOP_ACTIVATED:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ debug_batterydata->info.siop_activated = (int)x;
+ if (debug_batterydata->info.siop_activated ==
+ SIOP_ACTIVE)
+ sec_set_chg_current(debug_batterydata,
+ SIOP_ACTIVE_CHARGE_CURRENT);
+ else
+ sec_set_chg_current(debug_batterydata,
+ SIOP_DEACTIVE_CHARGE_CURRENT);
+ ret = count;
+ }
+ pr_info("%s: SIOP_ACTIVATED :%d\n", __func__, x);
+ break;
+#ifdef CONFIG_SAMSUNG_LPM_MODE
+ case BATT_LP_CHARGING:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ debug_batterydata->charging_mode_booting = x;
+ ret = count;
+ }
+ break;
+#endif
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+#ifdef __TEST_DEVICE_DRIVER__
+#define SEC_TEST_ATTR(_name)\
+{\
+ .attr = { .name = #_name, .mode = S_IRUGO | (S_IWUSR | S_IWGRP) },\
+ .show = sec_batt_test_show_property,\
+ .store = sec_batt_test_store,\
+}
+
+static struct device_attribute sec_batt_test_attrs[] = {
+ SEC_TEST_ATTR(suspend_lock),
+ SEC_TEST_ATTR(control_temp),
+};
+
+enum {
+ SUSPEND_LOCK = 0,
+ CTRL_TEMP,
+};
+
+static int sec_batt_test_create_attrs(struct device *dev)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(sec_batt_test_attrs); i++) {
+ ret = device_create_file(dev, &sec_batt_test_attrs[i]);
+ if (ret)
+ goto sec_attrs_failed;
+ }
+ goto succeed;
+
+sec_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sec_batt_test_attrs[i]);
+succeed:
+ return ret;
+}
+
+static ssize_t sec_batt_test_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+ const ptrdiff_t off = attr - sec_batt_test_attrs;
+
+ switch (off) {
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t sec_batt_test_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int mode = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - sec_batt_test_attrs;
+
+ switch (off) {
+ case SUSPEND_LOCK:
+ if (sscanf(buf, "%d\n", &mode) != 1)
+ break;
+
+ dev_dbg(dev, "%s: suspend lock (%d)\n", __func__, mode);
+ if (mode)
+ wake_lock(&debug_batterydata->wake_lock_for_dev);
+ else
+ wake_lock_timeout(&debug_batterydata->wake_lock_for_dev,
+ HZ / 2);
+ ret = count;
+ break;
+
+ case CTRL_TEMP:
+ if (sscanf(buf, "%d\n", &mode) == 1) {
+ dev_info(dev, "%s: control temp (%d)\n", __func__,
+ mode);
+ bat_temp_force_state = mode;
+ ret = count;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+#endif /* __TEST_DEVICE_DRIVER__ */
+
+int check_usb_status;
+static int sec_cable_status_update(struct battery_data *battery, int status)
+{
+ int ret = 0;
+ enum charger_type source = CHARGER_BATTERY;
+
+ pr_debug("Update cable status ");
+
+ if (!battery->sec_battery_initial)
+ return -EPERM;
+
+ switch (status) {
+ case CHARGER_BATTERY:
+ pr_info("cable NOT PRESENT ");
+ battery->info.charging_source = CHARGER_BATTERY;
+ break;
+ case CHARGER_USB:
+ pr_info("cable USB");
+ battery->info.charging_source = CHARGER_USB;
+ break;
+ case CHARGER_AC:
+ pr_info("cable AC");
+ battery->info.charging_source = CHARGER_AC;
+ break;
+ case CHARGER_DOCK:
+ pr_info("cable DOCK");
+ battery->info.charging_source = CHARGER_DOCK;
+ break;
+ case CHARGER_MISC:
+ pr_info("cable MISC");
+ battery->info.charging_source = CHARGER_AC;
+#if defined(CONFIG_MACH_P8LTE) || defined(CONFIG_MACH_P8)
+ battery->info.charging_source = CHARGER_MISC;
+#endif
+ break;
+ case CHARGER_DISCHARGE:
+ pr_info("Discharge");
+ battery->info.charging_source = CHARGER_DISCHARGE;
+ break;
+ default:
+ pr_info("Not supported status");
+ ret = -EINVAL;
+ }
+ check_usb_status = source = battery->info.charging_source;
+
+ if (source == CHARGER_USB || source == CHARGER_AC || \
+ source == CHARGER_MISC || source == CHARGER_DOCK) {
+ wake_lock(&battery->vbus_wake_lock);
+ } else {
+ /* give userspace some time to see the uevent and update
+ * LED state or whatnot...
+ */
+ if (!get_charger_status(battery)) {
+ if (battery->charging_mode_booting)
+ wake_lock_timeout(&battery->vbus_wake_lock,
+ 5 * HZ);
+ else
+ wake_lock_timeout(&battery->vbus_wake_lock,
+ HZ / 2);
+ }
+ }
+
+ wake_lock(&battery->work_wake_lock);
+ schedule_work(&battery->battery_work);
+
+ pr_debug("call power_supply_changed ");
+
+ return ret;
+}
+
+static void sec_bat_status_update(struct power_supply *bat_ps)
+{
+ struct battery_data *battery = container_of(bat_ps,
+ struct battery_data, psy_battery);
+
+ int old_level, old_temp, old_health, old_is_full;
+
+ if (!battery->sec_battery_initial)
+ return;
+
+ mutex_lock(&battery->work_lock);
+ old_temp = battery->info.batt_temp;
+ old_health = battery->info.batt_health;
+ old_level = battery->info.level;
+ old_is_full = battery->info.batt_is_full;
+
+ battery->info.batt_temp = sec_get_bat_temp(bat_ps);
+ if (!battery->is_low_batt_alarm)
+ battery->info.level = sec_get_bat_level(bat_ps);
+
+ if (!battery->info.charging_enabled &&
+ !battery->info.batt_is_full &&
+ !battery->pdata->check_jig_status()) {
+ if (battery->info.level > old_level)
+ battery->info.level = old_level;
+ }
+
+ battery->info.batt_vol = sec_get_bat_vol(bat_ps);
+
+ if (battery->pdata->get_charging_state)
+ battery->pdata->get_charging_state();
+
+ power_supply_changed(bat_ps);
+ pr_debug("call power_supply_changed");
+
+ pr_info("BAT : soc(%d), vcell(%dmV), curr(%dmA), temp(%d.%d), chg(%d)",
+ battery->info.level,
+ battery->info.batt_vol,
+ battery->info.batt_current,
+ battery->info.batt_temp/10,
+ battery->info.batt_temp%10,
+ battery->info.charging_enabled);
+ pr_info(", full(%d), rechg(%d), lowbat(%d), cable(%d)\n",
+ battery->info.batt_is_full,
+ battery->info.batt_is_recharging,
+ battery->is_low_batt_alarm,
+ battery->current_cable_status);
+
+ mutex_unlock(&battery->work_lock);
+}
+
+static void sec_cable_check_status(struct battery_data *battery)
+{
+ enum charger_type status = 0;
+
+ mutex_lock(&battery->work_lock);
+
+ if (get_charger_status(battery)) {
+ pr_info("%s: Returning to Normal discharge path.\n",
+ __func__);
+ cancel_delayed_work(&battery->fuelgauge_recovery_work);
+ battery->is_low_batt_alarm = false;
+
+ if (battery->info.batt_health != POWER_SUPPLY_HEALTH_GOOD) {
+ pr_info("Unhealth battery state! ");
+ status = CHARGER_DISCHARGE;
+ sec_set_chg_en(battery, 0);
+ goto __end__;
+ }
+
+ status = battery->current_cable_status;
+
+ sec_set_chg_en(battery, 1);
+
+ max17042_chip_data->info.low_batt_comp_flag = 0;
+ reset_low_batt_comp_cnt();
+
+ } else {
+ status = CHARGER_BATTERY;
+ sec_set_chg_en(battery, 0);
+ }
+__end__:
+ sec_cable_status_update(battery, status);
+ mutex_unlock(&battery->work_lock);
+}
+
+static void sec_bat_work(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data, battery_work);
+ unsigned long flags;
+ pr_debug("%s\n", __func__);
+
+ sec_bat_status_update(&battery->psy_battery);
+ battery->last_poll = alarm_get_elapsed_realtime();
+
+ /* prevent suspend before starting the alarm */
+ local_irq_save(flags);
+ wake_unlock(&battery->work_wake_lock);
+ sec_program_alarm(battery, FAST_POLL);
+ local_irq_restore(flags);
+}
+
+static void sec_cable_work(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data, cable_work);
+ pr_debug("%s\n", __func__);
+
+ sec_cable_check_status(battery);
+}
+
+static int sec_bat_suspend(struct device *dev)
+{
+ struct battery_data *battery = dev_get_drvdata(dev);
+ pr_info("%s start\n", __func__);
+
+ if (!get_charger_status(battery)) {
+ sec_program_alarm(battery, SLOW_POLL);
+ battery->slow_poll = 1;
+ }
+
+ pr_info("%s end\n", __func__);
+ return 0;
+}
+
+static void sec_bat_resume(struct device *dev)
+{
+ struct battery_data *battery = dev_get_drvdata(dev);
+ pr_info("%s start\n", __func__);
+
+ irq_set_irq_type(battery->charging_irq
+ , IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING);
+
+ if (battery->slow_poll) {
+ sec_program_alarm(battery, FAST_POLL);
+ battery->slow_poll = 0;
+ }
+
+ wake_lock(&battery->work_wake_lock);
+ schedule_work(&battery->battery_work);
+
+ pr_info("%s end\n", __func__);
+}
+
+static void sec_cable_changed(struct battery_data *battery)
+{
+ pr_debug("charger changed ");
+
+ if (!battery->sec_battery_initial)
+ return;
+
+ if (!battery->charging_mode_booting)
+ battery->info.batt_is_full = 0;
+
+ battery->info.batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ schedule_work(&battery->cable_work);
+
+ /*
+ * Wait a bit before reading ac/usb line status and setting charger,
+ * because ac/usb status readings may lag from irq.
+ */
+
+ battery->last_poll = alarm_get_elapsed_realtime();
+ sec_program_alarm(battery, FAST_POLL);
+}
+
+void sec_cable_charging(struct battery_data *battery)
+{
+ battery->full_charge_comp_recharge_info =
+ battery->info.batt_is_recharging;
+
+ if (!battery->sec_battery_initial)
+ return;
+
+ if (battery->info.charging_enabled &&
+ battery->info.batt_health == POWER_SUPPLY_HEALTH_GOOD) {
+ sec_set_chg_en(battery, 0);
+ battery->info.batt_is_full = 1;
+ /* full charge compensation algorithm by MAXIM */
+ fg_fullcharged_compensation(
+ battery->full_charge_comp_recharge_info, 1);
+
+ cancel_delayed_work(&battery->full_comp_work);
+ schedule_delayed_work(&battery->full_comp_work, 100);
+
+ pr_info("battery is full charged ");
+ }
+
+ wake_lock(&battery->work_wake_lock);
+ schedule_work(&battery->battery_work);
+}
+
+static irqreturn_t low_battery_isr(int irq, void *arg)
+{
+ struct battery_data *battery = (struct battery_data *)arg;
+ pr_debug("%s(%d)", __func__,
+ gpio_get_value(max17042_chip_data->pdata->fuel_alert_line));
+
+ cancel_delayed_work(&battery->fuelgauge_work);
+ schedule_delayed_work(&battery->fuelgauge_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+void fuelgauge_recovery_handler(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data,
+ fuelgauge_recovery_work.work);
+ int current_soc;
+
+ if (battery->info.level > 0) {
+ pr_err("%s: Reduce the Reported SOC by 1 unit, wait for 30s\n",
+ __func__);
+ if (!battery->info.charging_enabled)
+ wake_lock_timeout(&battery->vbus_wake_lock, HZ);
+ current_soc = get_fuelgauge_value(FG_LEVEL);
+ if (current_soc) {
+ pr_info("%s: Returning to Normal discharge path.\n",
+ __func__);
+ pr_info(" Actual SOC(%d) non-zero.\n",
+ current_soc);
+ battery->is_low_batt_alarm = false;
+ return;
+ } else {
+ battery->info.level--;
+ pr_err("%s: New Reduced Reported SOC (%d).\n",
+ __func__, battery->info.level);
+ power_supply_changed(&battery->psy_battery);
+ queue_delayed_work(battery->sec_TA_workqueue,
+ &battery->fuelgauge_recovery_work,
+ msecs_to_jiffies(30000));
+ }
+ } else {
+ if (!get_charger_status(battery)) {
+ pr_err("Set battery level as 0, power off.\n");
+ battery->info.level = 0;
+ wake_lock_timeout(&battery->vbus_wake_lock, HZ);
+ power_supply_changed(&battery->psy_battery);
+ }
+ }
+}
+
+#define STABLE_LOW_BATTERY_DIFF 3
+#define STABLE_LOW_BATTERY_DIFF_LOWBATT 1
+int _low_battery_alarm_(struct battery_data *battery)
+{
+ int overcurrent_limit_in_soc;
+ int current_soc = get_fuelgauge_value(FG_LEVEL);
+
+ if (battery->info.level <= STABLE_LOW_BATTERY_DIFF)
+ overcurrent_limit_in_soc = STABLE_LOW_BATTERY_DIFF_LOWBATT;
+ else
+ overcurrent_limit_in_soc = STABLE_LOW_BATTERY_DIFF;
+
+ if ((battery->info.level - current_soc) > overcurrent_limit_in_soc) {
+ pr_info("%s: Abnormal Current Consumption jump by %d units.\n",
+ __func__, ((battery->info.level - current_soc)));
+ pr_info("Last Reported SOC (%d).\n",
+ battery->info.level);
+
+ battery->is_low_batt_alarm = true;
+
+ if (battery->info.level > 0) {
+ queue_delayed_work(battery->sec_TA_workqueue,
+ &battery->fuelgauge_recovery_work, 0);
+ return 0;
+ }
+ }
+
+ if (!get_charger_status(battery)) {
+ pr_err("Set battery level as 0, power off.\n");
+ battery->info.level = 0;
+ wake_lock_timeout(&battery->vbus_wake_lock, HZ);
+ power_supply_changed(&battery->psy_battery);
+ }
+
+ return 0;
+}
+
+static void fuelgauge_work_handler(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data, fuelgauge_work.work);
+ pr_info("low battery alert!");
+ if (get_fuelgauge_value(FG_CHECK_STATUS))
+ _low_battery_alarm_(battery);
+}
+
+static void full_comp_work_handler(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data, full_comp_work.work);
+ int avg_current = get_fuelgauge_value(FG_CURRENT_AVG);
+
+ if (avg_current >= 25) {
+ cancel_delayed_work(&battery->full_comp_work);
+ schedule_delayed_work(&battery->full_comp_work, 100);
+ } else {
+ pr_info("%s: full charge compensation start (avg_current %d)\n",
+ __func__, avg_current);
+ fg_fullcharged_compensation(
+ battery->full_charge_comp_recharge_info, 0);
+ }
+}
+
+#define BATTERY_FULL_THRESHOLD_SOC 95 /* 95 / 1.333 = 71.27 */
+#if defined(P4_CHARGING_FEATURE_01)
+#define BATTERY_FULL_THRESHOLD_VFSOC 60 /* 60 * 1.333 = 79.98 */
+#else
+#define BATTERY_FULL_THRESHOLD_VFSOC 71 /* 71 * 1.333 = 94.64 */
+#endif
+
+static void fullcharging_work_handler(struct work_struct *work)
+{
+ struct battery_data *battery =
+ container_of(work, struct battery_data, fullcharging_work.work);
+ int check_charger_state = 1;
+ int fg_soc, fg_vfsoc;
+ pr_info("%s : nCHG intr!!, fullcharge_line=%d",
+ __func__,
+ gpio_get_value(battery->pdata->charger.fullcharge_line));
+
+ if (gpio_get_value(battery->pdata->charger.fullcharge_line) == 1) {
+#if defined(P2_CHARGING_FEATURE_02)
+ /* Check charger state */
+ if (battery->pdata->get_charging_state) {
+ if (battery->pdata->get_charging_state() == \
+ POWER_SUPPLY_STATUS_UNKNOWN) {
+ pr_info("Disconnect cable and check charger\n");
+ if (battery->pmic_cable_state == 0) {
+ battery->cable_detect_source = 2;
+ sec_get_cable_status(battery);
+ sec_cable_changed(battery);
+ check_charger_state = 0;
+ }
+ }
+ }
+#endif
+
+#if defined(P4_CHARGING_FEATURE_01)
+ fg_vfsoc = get_fuelgauge_value(FG_VF_SOC);
+ check_charger_state = battery->pdata->get_charger_is_full();
+ /* Check full charged state */
+ if (get_charger_status(battery) != 0
+ && fg_vfsoc > BATTERY_FULL_THRESHOLD_VFSOC
+ && check_charger_state == POWER_SUPPLY_STATUS_FULL) {
+ pr_info("Battery is full\n");
+ sec_cable_charging(battery);
+ }
+#else
+ fg_soc = get_fuelgauge_value(FG_LEVEL);
+ fg_vfsoc = get_fuelgauge_value(FG_VF_SOC);
+ /* Check full charged state */
+ if (get_charger_status(battery) != 0
+ && fg_soc > BATTERY_FULL_THRESHOLD_SOC
+ && fg_vfsoc > BATTERY_FULL_THRESHOLD_VFSOC
+ && check_charger_state == 1) {
+ pr_info("Battery is full\n");
+ sec_cable_charging(battery);
+ }
+#endif
+ } else {
+ pr_info("Charger is working. Cable state will be updated.\n");
+ /* Cable detect from default */
+ battery->cable_detect_source = 0;
+ }
+
+ enable_irq(battery->charging_irq);
+}
+
+static int __devinit sec_bat_probe(struct platform_device *pdev)
+{
+ struct sec_battery_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct battery_data *battery;
+ int ret;
+ unsigned long trigger;
+ int irq_num;
+
+ pr_info("%s : SEC Battery Driver Loading\n", __func__);
+
+ battery = kzalloc(sizeof(*battery), GFP_KERNEL);
+ if (!battery)
+ return -ENOMEM;
+
+ battery->pdata = pdata;
+ if (!battery->pdata) {
+ pr_err("%s : No platform data\n", __func__);
+ ret = -EINVAL;
+ goto err_pdata;
+ }
+
+ battery->pdata->init_charger_gpio();
+
+ platform_set_drvdata(pdev, battery);
+ debug_batterydata = battery;
+
+ battery->present = 1;
+ battery->info.level = 100;
+ battery->info.charging_source = CHARGER_BATTERY;
+ battery->info.batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ battery->info.abstimer_is_active = 0;
+ battery->is_first_check = true;
+ battery->is_low_batt_alarm = false;
+
+ battery->psy_battery.name = "battery";
+ battery->psy_battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->psy_battery.properties = sec_battery_properties;
+ battery->psy_battery.num_properties = \
+ ARRAY_SIZE(sec_battery_properties);
+ battery->psy_battery.get_property = sec_bat_get_property;
+ battery->psy_battery.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_properties;
+ battery->psy_usb.num_properties = ARRAY_SIZE(sec_power_properties);
+ 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_properties;
+ battery->psy_ac.num_properties = ARRAY_SIZE(sec_power_properties);
+ battery->psy_ac.get_property = sec_ac_get_property;
+
+ mutex_init(&battery->work_lock);
+
+ wake_lock_init(&battery->vbus_wake_lock, WAKE_LOCK_SUSPEND,
+ "vbus wake lock");
+ wake_lock_init(&battery->work_wake_lock, WAKE_LOCK_SUSPEND,
+ "batt_work wake lock");
+ wake_lock_init(&battery->cable_wake_lock, WAKE_LOCK_SUSPEND,
+ "temp wake lock");
+ wake_lock_init(&battery->fullcharge_wake_lock, WAKE_LOCK_SUSPEND,
+ "fullcharge wake lock");
+#ifdef __TEST_DEVICE_DRIVER__
+ wake_lock_init(&battery->wake_lock_for_dev, WAKE_LOCK_SUSPEND,
+ "test mode wake lock");
+#endif /* __TEST_DEVICE_DRIVER__ */
+
+ INIT_WORK(&battery->battery_work, sec_bat_work);
+ INIT_WORK(&battery->cable_work, sec_cable_work);
+ INIT_DELAYED_WORK(&battery->fuelgauge_work, fuelgauge_work_handler);
+ INIT_DELAYED_WORK(&battery->fuelgauge_recovery_work,
+ fuelgauge_recovery_handler);
+ INIT_DELAYED_WORK(&battery->fullcharging_work,
+ fullcharging_work_handler);
+ INIT_DELAYED_WORK(&battery->full_comp_work, full_comp_work_handler);
+ INIT_DELAYED_WORK(&battery->TA_work, sec_TA_work_handler);
+
+ battery->sec_TA_workqueue = create_singlethread_workqueue(
+ "sec_TA_workqueue");
+ if (!battery->sec_TA_workqueue) {
+ pr_err("Failed to create single workqueue\n");
+ ret = -ENOMEM;
+ goto err_workqueue_init;
+ }
+
+ battery->padc = s3c_adc_register(pdev, NULL, NULL, 0);
+
+ battery->last_poll = alarm_get_elapsed_realtime();
+ alarm_init(&battery->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
+ sec_battery_alarm);
+
+ ret = power_supply_register(&pdev->dev, &battery->psy_battery);
+ if (ret) {
+ pr_err("Failed to register battery power supply.\n");
+ goto err_battery_psy_register;
+ }
+
+ ret = power_supply_register(&pdev->dev, &battery->psy_usb);
+ if (ret) {
+ pr_err("Failed to register USB power supply.\n");
+ goto err_usb_psy_register;
+ }
+
+ ret = power_supply_register(&pdev->dev, &battery->psy_ac);
+ if (ret) {
+ pr_err("Failed to register AC power supply.\n");
+ goto err_ac_psy_register;
+ }
+
+ /* create sec detail attributes */
+ sec_bat_create_attrs(battery->psy_battery.dev);
+
+#ifdef __TEST_DEVICE_DRIVER__
+ sec_batt_test_create_attrs(battery->psy_ac.dev);
+#endif /* __TEST_DEVICE_DRIVER__ */
+
+ battery->sec_battery_initial = 1;
+ battery->low_batt_boot_flag = 0;
+
+ /* Get initial cable status */
+ sec_get_cable_status(battery);
+ battery->previous_cable_status = battery->current_cable_status;
+ sec_cable_check_status(battery);
+
+ /* TA_nCHG irq */
+ battery->charging_irq = gpio_to_irq(pdata->charger.fullcharge_line);
+ trigger = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+ if (request_threaded_irq
+ (battery->charging_irq, NULL, sec_TA_nCHG_interrupt_handler,
+ trigger, "TA_nCHG intr", battery)) {
+ pr_err("sec_TA_nCHG_interrupt_handler register failed!\n");
+ goto err_charger_irq;
+ }
+
+ /* TA_nConnected irq */
+ battery->connect_irq = gpio_to_irq(pdata->charger.connect_line);
+ trigger = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+ if (request_threaded_irq
+ (battery->connect_irq, NULL, sec_TA_nCon_interrupt_handler, trigger,
+ "TA_nConnected intr", battery)) {
+ pr_err("sec_TA_nCon_interrupt_handler register failed!\n");
+ goto err_charger_irq;
+ }
+
+ if (enable_irq_wake(battery->connect_irq))
+ pr_err("TA_nConnected enable_irq_wake fail!\n");
+
+ if (check_ta_conn(battery) && check_UV_charging_case())
+ battery->low_batt_boot_flag = 1;
+
+ mutex_lock(&battery->work_lock);
+ fg_alert_init();
+ mutex_unlock(&battery->work_lock);
+
+ /* before enable fullcharge interrupt, check fullcharge */
+ if (((battery->info.charging_source == CHARGER_AC) ||
+ (battery->info.charging_source == CHARGER_MISC) ||
+ (battery->info.charging_source == CHARGER_DOCK))
+ && battery->info.charging_enabled
+ && gpio_get_value(pdata->charger.fullcharge_line) == 1)
+ sec_cable_charging(battery);
+
+ /* Request IRQ */
+ irq_num = gpio_to_irq(max17042_chip_data->pdata->fuel_alert_line);
+ if (request_threaded_irq(irq_num, NULL, low_battery_isr,
+ IRQF_TRIGGER_FALLING, "FUEL_ALRT irq",
+ battery))
+ pr_err("Can NOT request irq 'IRQ_FUEL_ALERT' %d ", irq_num);
+
+ if (enable_irq_wake(irq_num))
+ pr_err("FUEL_ALERT enable_irq_wake fail!\n");
+
+#ifdef CONFIG_SAMSUNG_LPM_MODE
+ lpm_mode_check(battery);
+#endif
+
+ return 0;
+
+err_charger_irq:
+ alarm_cancel(&battery->alarm);
+ power_supply_unregister(&battery->psy_ac);
+err_ac_psy_register:
+ power_supply_unregister(&battery->psy_usb);
+err_usb_psy_register:
+ power_supply_unregister(&battery->psy_battery);
+err_battery_psy_register:
+ destroy_workqueue(battery->sec_TA_workqueue);
+err_workqueue_init:
+ wake_lock_destroy(&battery->vbus_wake_lock);
+ wake_lock_destroy(&battery->work_wake_lock);
+ wake_lock_destroy(&battery->cable_wake_lock);
+ wake_lock_destroy(&battery->fullcharge_wake_lock);
+ mutex_destroy(&battery->work_lock);
+err_pdata:
+ kfree(battery);
+
+ return ret;
+}
+
+static int __devexit sec_bat_remove(struct platform_device *pdev)
+{
+ struct battery_data *battery = platform_get_drvdata(pdev);
+
+ free_irq(gpio_to_irq(max17042_chip_data->pdata->fuel_alert_line), NULL);
+ free_irq(gpio_to_irq(battery->pdata->charger.connect_line), NULL);
+
+ alarm_cancel(&battery->alarm);
+ power_supply_unregister(&battery->psy_ac);
+ power_supply_unregister(&battery->psy_usb);
+ power_supply_unregister(&battery->psy_battery);
+
+ destroy_workqueue(battery->sec_TA_workqueue);
+ cancel_delayed_work(&battery->fuelgauge_work);
+ cancel_delayed_work(&battery->fuelgauge_recovery_work);
+ cancel_delayed_work(&battery->fullcharging_work);
+ cancel_delayed_work(&battery->full_comp_work);
+
+ wake_lock_destroy(&battery->vbus_wake_lock);
+ wake_lock_destroy(&battery->work_wake_lock);
+ wake_lock_destroy(&battery->cable_wake_lock);
+ wake_lock_destroy(&battery->fullcharge_wake_lock);
+ mutex_destroy(&battery->work_lock);
+
+ kfree(battery);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sec_battery_pm_ops = {
+ .prepare = sec_bat_suspend,
+ .complete = sec_bat_resume,
+};
+
+static struct platform_driver sec_bat_driver = {
+ .driver = {
+ .name = "sec-battery",
+ .owner = THIS_MODULE,
+ .pm = &sec_battery_pm_ops,
+ },
+ .probe = sec_bat_probe,
+ .remove = __devexit_p(sec_bat_remove),
+};
+
+static int __init sec_bat_init(void)
+{
+ return platform_driver_register(&sec_bat_driver);
+}
+
+static void __exit sec_bat_exit(void)
+{
+ platform_driver_unregister(&sec_bat_driver);
+}
+
+late_initcall(sec_bat_init);
+module_exit(sec_bat_exit);
+
+MODULE_DESCRIPTION("battery driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/power/sec_battery_u1.c b/drivers/power/sec_battery_u1.c
new file mode 100644
index 0000000..a442251
--- /dev/null
+++ b/drivers/power/sec_battery_u1.c
@@ -0,0 +1,3442 @@
+/*
+ * sec_battery.c
+ * Samsung Mobile Battery Driver
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <linux/proc_fs.h>
+#include <linux/android_alarm.h>
+#include <plat/adc.h>
+#include <linux/power/sec_battery_u1.h>
+
+#if defined(CONFIG_TARGET_LOCALE_NA) || defined(CONFIG_TARGET_LOCALE_NAATT)
+#define POLLING_INTERVAL (10 * 1000)
+#else
+#define POLLING_INTERVAL (40 * 1000)
+#endif /* CONFIG_TARGET_LOCALE_NA */
+
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+#define VF_CHECK_INTERVAL (5 * 1000)
+
+#define MAX_VF 1800
+#define MIN_VF 1100
+#define VF_COUNT 1
+#elif defined(CONFIG_MACH_Q1_BD)
+#define VF_CHECK_INTERVAL (5 * 1000)
+#define MAX_VF_ADC 2500
+#define MIN_VF_ADC 1800
+#define ADC_CH_VF 1
+#else
+#define VF_CHECK_INTERVAL (10 * 1000)
+#endif
+#endif
+#define FULL_CHARGING_TIME (6 * 60 * 60 * HZ) /* 6hr */
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define RECHARGING_TIME (2 * 60 * 60 * HZ) /* 2hr */
+#else
+#define RECHARGING_TIME (90 * 60 * HZ) /* 1.5hr */
+#endif
+#define RESETTING_CHG_TIME (10 * 60 * HZ) /* 10Min */
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+#define EVENT_OVER_TIME (10 * 60 * HZ) /* 10Min */
+#endif
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define BAT_USE_TIMER_EXPIRE (10 * 60 * HZ) /* 10 min */
+#endif
+
+#if defined(CONFIG_TARGET_LOCALE_NA) || defined(CONFIG_MACH_Q1_BD)
+#define RECHARGING_VOLTAGE (4130 * 1000) /* 4.13 V */
+#else
+#define RECHARGING_VOLTAGE (4150 * 1000) /* 4.15 V */
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define RECHARGING_CND_COUNT 2
+#endif
+
+#define FG_T_SOC 0
+#define FG_T_VCELL 1
+#define FG_T_TEMPER 2
+#define FG_T_PSOC 3
+#define FG_T_VFOCV 4
+#define FG_T_AVGVCELL 5
+
+#define ADC_SAMPLING_CNT 7
+#if defined(CONFIG_MACH_Q1_BD)
+#define ADC_CH_CHGCURRENT 0
+#else
+#define ADC_CH_CHGCURRENT 1
+#endif
+#define ADC_TOTAL_COUNT 5
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+#define OFFSET_VIDEO_PLAY (0x1 << 0)
+#define OFFSET_MP3_PLAY (0x1 << 1)
+#define OFFSET_VOICE_CALL_2G (0x1 << 2)
+#define OFFSET_VOICE_CALL_3G (0x1 << 3)
+#define OFFSET_DATA_CALL (0x1 << 4)
+#define OFFSET_WIFI (0x1 << 5)
+#define OFFSET_GPS (0x1 << 6)
+#define OFFSET_CAMERA_ON (0x1 << 7)
+#define OFFSET_RECORDING_ON (0x1 << 8)
+#endif
+
+/* put off current during charging*/
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define USE_CALL (0x1 << 0)
+#define USE_VIDEO (0x1 << 1)
+#define USE_MUSIC (0x1 << 2)
+#define USE_BROWSER (0x1 << 3)
+#define USE_HOTSPOT (0x1 << 4)
+#define USE_CAMERA (0x1 << 5)
+#define USE_DATA_CALL (0x1 << 6)
+#define USE_WIMAX (0x1 << 7)
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+#ifdef CONFIG_MACH_U1_NA_SPR_EPIC2_REV00
+#define EVENT_BLOCK_TEMP_ADC 755
+#define HIGH_BLOCK_TEMP_ADC 715
+#define LOW_BLOCK_TEMP_ADC 521
+#define HIGH_RECOVER_TEMP_ADC 714
+#define LOW_RECOVER_TEMP_ADC 522
+
+#define HIGH_BLOCK_TEMP_ADC_LPM 715
+#define LOW_BLOCK_TEMP_ADC_LPM 521
+#define HIGH_RECOVER_TEMP_ADC_LPM 714
+#define LOW_RECOVER_TEMP_ADC_LPM 522
+#else
+#define EVENT_BLOCK_TEMP_ADC 777
+#define HIGH_BLOCK_TEMP_ADC 729
+#define LOW_BLOCK_TEMP_ADC 502
+#define HIGH_RECOVER_TEMP_ADC 699
+#define LOW_RECOVER_TEMP_ADC 516
+
+#define HIGH_BLOCK_TEMP_ADC_LPM 710
+#define LOW_BLOCK_TEMP_ADC_LPM 506
+#define HIGH_RECOVER_TEMP_ADC_LPM 702
+#define LOW_RECOVER_TEMP_ADC_LPM 512
+#endif
+#elif defined(CONFIG_TARGET_LOCALE_NAATT)
+#define EVENT_BLOCK_TEMP 620
+#define HIGH_BLOCK_TEMP 490
+#define LOW_BLOCK_TEMP (-30)
+
+#define EVENT_RECOVER_TEMP 430
+#define HIGH_RECOVER_TEMP 430
+#define LOW_RECOVER_TEMP 0
+#elif defined(CONFIG_MACH_Q1_BD)
+#define HIGH_BLOCK_TEMP 650
+#define LOW_BLOCK_TEMP (-50)
+#define HIGH_RECOVER_TEMP 430
+#define LOW_RECOVER_TEMP 0
+#else
+#define HIGH_BLOCK_TEMP 650
+#define LOW_BLOCK_TEMP (-30)
+#define HIGH_RECOVER_TEMP 430
+#define LOW_RECOVER_TEMP 0
+#endif
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+#define HIGH_DEC_CURR_TEMP 410
+#define HIGH_INC_CURR_TEMP 380
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00)
+#define CURRENT_OF_FULL_CHG 1
+#else
+#define CURRENT_OF_FULL_CHG 350
+#endif /* CONFIG_MACH_U1_NA_SPR_EPIC2_REV00 */
+#elif defined(CONFIG_MACH_Q1_BD)
+#ifdef SEC_BATTERY_1ST_2ND_TOPOFF
+#if 0
+#define CURRENT_1ST_FULL_CHG 1000 /* 360mA */
+#define CURRENT_2ND_FULL_CHG 650 /* 220mA */
+#endif
+#define CURRENT_1ST_FULL_CHG 580 /* 190mA */
+#define CURRENT_2ND_FULL_CHG 540 /* 170mA */
+#else
+#define CURRENT_OF_FULL_CHG 850
+#endif
+#else
+#define CURRENT_OF_FULL_CHG 520
+#endif
+
+#define TEMP_BLOCK_COUNT 3
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+#define BAT_DET_COUNT 0
+#else
+#define BAT_DET_COUNT 1
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define FULL_CHG_COND_COUNT 2
+#else
+#define FULL_CHG_COND_COUNT 3
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define FULL_CHARGE_COND_VOLTAGE (4000 * 1000) /* 4.00 V */
+#else
+#define FULL_CHARGE_COND_VOLTAGE (4150 * 1000) /* 4.15 V */
+#endif
+#define INIT_CHECK_COUNT 4
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+#define SPRINT_SLATE_TEST
+#endif
+
+enum tmu_status_t {
+ TMU_STATUS_NORMAL = 0,
+ TMU_STATUS_TRIPPED,
+ TMU_STATUS_THROTTLED,
+ TMU_STATUS_WARNING,
+};
+
+enum cable_type_t {
+ CABLE_TYPE_NONE = 0,
+ CABLE_TYPE_USB,
+ CABLE_TYPE_AC,
+ CABLE_TYPE_MISC,
+};
+
+enum batt_full_t {
+ BATT_NOT_FULL = 0,
+ BATT_FULL,
+};
+
+enum {
+ BAT_NOT_DETECTED,
+ BAT_DETECTED
+};
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+enum batt_temp_extra {
+ BATT_TEMP_EXT_NONE = 0,
+ BATT_TEMP_EXT_CAMCORDING_NORMAL,
+ BATT_TEMP_EXT_CAMCORDING_HIGH,
+};
+#endif
+
+static ssize_t sec_bat_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static ssize_t sec_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+struct adc_sample {
+ int average_adc;
+ int adc_arr[ADC_TOTAL_COUNT];
+ int index;
+};
+
+struct sec_bat_info {
+ struct device *dev;
+
+ char *fuel_gauge_name;
+ char *charger_name;
+ char *sub_charger_name;
+
+ unsigned int adc_arr_size;
+ struct sec_bat_adc_table_data *adc_table;
+ unsigned int adc_channel;
+ struct adc_sample temper_adc_sample;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ int vf_adc_channel;
+#endif
+
+ struct power_supply psy_bat;
+ struct power_supply psy_usb;
+ struct power_supply psy_ac;
+
+ struct wake_lock vbus_wake_lock;
+ struct wake_lock monitor_wake_lock;
+ struct wake_lock cable_wake_lock;
+
+ enum cable_type_t cable_type;
+ enum batt_full_t batt_full_status;
+
+ unsigned int batt_temp; /* Battery Temperature (C) */
+ int batt_temp_high_cnt;
+ int batt_temp_low_cnt;
+ int batt_temp_recover_cnt;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ enum batt_temp_extra batt_temp_ext;
+ enum batt_temp_extra batt_temp_ext_pre;
+#endif
+ unsigned int batt_health;
+ unsigned int batt_vcell;
+ unsigned int batt_vfocv;
+ unsigned int batt_soc;
+ unsigned int batt_raw_soc;
+ unsigned int polling_interval;
+ int charging_status;
+ int charging_int_full_count;
+ int charging_adc_full_count;
+#ifdef CONFIG_TARGET_LOCALE_NA
+ int recharging_int_threshold_count;
+#endif
+
+ unsigned int batt_temp_adc;
+#ifdef CONFIG_TARGET_LOCALE_NA
+ unsigned int batt_temp_radc;
+#endif
+ unsigned int batt_current_adc;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ int batt_vf_adc;
+ int batt_event_status;
+ unsigned long event_end_time;
+#elif defined(CONFIG_TARGET_LOCALE_NA)
+ int use_call;
+ int use_video;
+ int use_music;
+ int use_browser;
+ int use_hotspot;
+ int use_camera;
+ int use_data_call;
+ int use_wimax;
+ int batt_event_status;
+ unsigned long event_expired_time;
+#endif
+ struct s3c_adc_client *padc;
+
+ struct workqueue_struct *monitor_wqueue;
+ struct work_struct monitor_work;
+ struct work_struct cable_work;
+ struct delayed_work polling_work;
+
+ unsigned long charging_start_time;
+ unsigned long charging_passed_time;
+ unsigned long charging_next_time;
+ unsigned int recharging_status;
+ unsigned int batt_lpm_state;
+#ifdef SPRINT_SLATE_TEST
+ bool slate_test_mode;
+#endif
+
+ struct mutex adclock;
+
+ unsigned int (*get_lpcharging_state) (void);
+ int present;
+ int present_count;
+ bool use_sub_charger;
+
+ int test_value;
+ int initial_check_count;
+ struct proc_dir_entry *entry;
+
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+ unsigned int vf_check_interval;
+ struct delayed_work vf_check_work;
+#endif
+ int batt_tmu_status;
+};
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static enum power_supply_property sec_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+static enum power_supply_property sec_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+static struct sec_bat_info *pchg;
+#endif
+
+struct power_supply *get_power_supply_by_name(char *name)
+{
+ if (!name)
+ return (struct power_supply *)NULL;
+ else
+ return power_supply_get_by_name(name);
+}
+
+static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable);
+
+static int calculate_average_adc(struct sec_bat_info *info,
+ struct adc_sample *sample, int adc)
+{
+ int i, total_adc = 0;
+ int average_adc = sample->average_adc;
+ int index = sample->index;
+
+ if (adc < 0 || adc == 0) {
+ dev_err(info->dev, "%s: invalid adc : %d\n", __func__, adc);
+ return 0;
+ }
+
+ if (!average_adc) {
+ average_adc = adc;
+ for (i = 0; i < ADC_TOTAL_COUNT; i++)
+ sample->adc_arr[i] = adc;
+ } else {
+ sample->index = ++index >= ADC_TOTAL_COUNT ? 0 : index;
+ sample->adc_arr[sample->index] = adc;
+ for (i = 0; i < ADC_TOTAL_COUNT; i++)
+ total_adc += sample->adc_arr[i];
+
+ average_adc = total_adc / ADC_TOTAL_COUNT;
+ }
+
+ sample->average_adc = average_adc;
+ dev_dbg(info->dev, "%s: i(%d) adc=%d, avg_adc=%d\n", __func__,
+ sample->index, adc, average_adc);
+
+ return average_adc;
+}
+
+static int sec_bat_get_fuelgauge_data(struct sec_bat_info *info, int type)
+{
+ struct power_supply *psy
+ = get_power_supply_by_name(info->fuel_gauge_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get fuel gauge ps\n", __func__);
+ return -ENODEV;
+ }
+
+ switch (type) {
+ case FG_T_VCELL:
+ value.intval = 0; /*vcell */
+ psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value);
+ break;
+ case FG_T_VFOCV:
+ value.intval = 1; /*vfocv */
+ psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value);
+ break;
+ case FG_T_AVGVCELL:
+ psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_AVG, &value);
+ break;
+ case FG_T_SOC:
+ value.intval = 0; /*normal soc */
+ psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value);
+ break;
+ case FG_T_PSOC:
+ value.intval = 1; /*raw soc */
+ psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value);
+ break;
+ case FG_T_TEMPER:
+ psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return value.intval;
+}
+
+static int sec_bat_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = info->charging_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = info->batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = info->batt_temp;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ /* val->intval = 1; */
+ val->intval = info->cable_type;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = info->batt_vcell;
+ if (val->intval == -1)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+#ifdef CONFIG_TARGET_LOCALE_NA
+ if (info->charging_status != POWER_SUPPLY_STATUS_FULL
+ && info->batt_soc == 100) {
+ val->intval = 99;
+ break;
+ }
+#endif /*CONFIG_TARGET_LOCALE_NA */
+
+ if (info->charging_status == POWER_SUPPLY_STATUS_FULL) {
+ val->intval = 100;
+ break;
+ }
+ val->intval = info->batt_soc;
+ if (val->intval == -1)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = -1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void sec_bat_handle_charger_topoff(struct sec_bat_info *info)
+{
+#ifdef SEC_BATTERY_1ST_2ND_TOPOFF
+ if (info->charging_status != POWER_SUPPLY_STATUS_FULL) {
+ /* 1st top-off */
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+
+ dev_info(info->dev, "%s: Charging 1st Top-off\n", __func__);
+ } else {
+ /* 2nd top-off, recharging top-off */
+ if (!sec_bat_enable_charging(info, false)) {
+ info->batt_full_status = BATT_FULL;
+ info->recharging_status = false;
+ }
+
+ dev_info(info->dev, "%s: Charging 2nd Top-off\n", __func__);
+ }
+#else
+ if (!sec_bat_enable_charging(info, false)) {
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+ info->batt_full_status = BATT_FULL;
+ info->recharging_status = false;
+
+ dev_info(info->dev, "%s: Charging Top-off\n", __func__);
+ }
+#endif
+}
+
+static int sec_bat_set_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_bat);
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get %s ps\n",
+ __func__, info->charger_name);
+ return -EINVAL;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ dev_info(info->dev, "%s: topoff intr\n", __func__);
+ if (val->intval != POWER_SUPPLY_STATUS_FULL)
+ return -EINVAL;
+
+ if (info->use_sub_charger) {
+#if !defined(CONFIG_MACH_Q1_BD)
+ if (info->cable_type == CABLE_TYPE_USB ||
+ info->cable_type == CABLE_TYPE_MISC)
+#endif
+ sec_bat_handle_charger_topoff(info);
+ break;
+ }
+
+ if (info->batt_full_status == BATT_NOT_FULL) {
+ info->recharging_status = false;
+ info->batt_full_status = BATT_FULL;
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+
+ info->charging_start_time = 0;
+ info->charging_passed_time = 0;
+ info->charging_next_time = 0;
+
+ /* disable charging */
+ value.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ psy->set_property(psy, POWER_SUPPLY_PROP_STATUS,
+ &value);
+ }
+#if 0 /* for reference */
+ if (info->batt_full_status == BATT_NOT_FULL) {
+ info->batt_full_status = BATT_1ST_FULL;
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+ /* TODO: set topoff current 60mA */
+ value.intval = 120;
+ psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL,
+ &value);
+ } else {
+ info->batt_full_status = BATT_2ND_FULL;
+ info->recharging_status = false;
+ /* disable charging */
+ value.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ psy->set_property(psy, POWER_SUPPLY_PROP_STATUS,
+ &value);
+ }
+#endif
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ dev_info(info->dev, "%s: refresh battery data\n", __func__);
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ /* TODO: lowbatt interrupt: called by fuel gauge */
+ dev_info(info->dev, "%s: lowbatt intr\n", __func__);
+ if (val->intval != POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL)
+ return -EINVAL;
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* cable is attached or detached. called by USB switch(MUIC) */
+ dev_info(info->dev, "%s: cable was changed(%d)\n", __func__,
+ val->intval);
+ switch (val->intval) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ info->cable_type = CABLE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ info->cable_type = CABLE_TYPE_AC;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ info->cable_type = CABLE_TYPE_USB;
+ break;
+ case POWER_SUPPLY_TYPE_MISC:
+ info->cable_type = CABLE_TYPE_MISC;
+ break;
+ default:
+ return -EINVAL;
+ }
+ wake_lock(&info->cable_wake_lock);
+ queue_work(info->monitor_wqueue, &info->cable_work);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ /* call by TMU driver
+ * alarm for abnormal temperature increasement
+ */
+ info->batt_tmu_status = val->intval;
+
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ dev_info(info->dev, "%s: TMU status has been changed(%d)\n",
+ __func__, info->batt_tmu_status);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sec_usb_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_usb);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ /* Set enable=1 only if the USB charger is connected */
+ val->intval = (info->cable_type == CABLE_TYPE_USB);
+
+ return 0;
+}
+
+static int sec_ac_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_ac);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ /* Set enable=1 only if the AC charger is connected */
+ val->intval = (info->cable_type == CABLE_TYPE_AC) ||
+ (info->cable_type == CABLE_TYPE_MISC);
+
+ return 0;
+}
+
+static int sec_bat_get_adc_data(struct sec_bat_info *info, int adc_ch)
+{
+ int adc_data;
+ int adc_max = 0;
+ int adc_min = 0;
+ int adc_total = 0;
+ int i;
+ int err_value;
+
+ for (i = 0; i < ADC_SAMPLING_CNT; i++) {
+ adc_data = s3c_adc_read(info->padc, adc_ch);
+ if (adc_data < 0) {
+ dev_err(info->dev, "%s : err(%d) returned, skip read\n",
+ __func__, adc_data);
+ err_value = adc_data;
+ goto err;
+ }
+
+ if (i != 0) {
+ if (adc_data > adc_max)
+ adc_max = adc_data;
+ else if (adc_data < adc_min)
+ adc_min = adc_data;
+ } else {
+ adc_max = adc_data;
+ adc_min = adc_data;
+ }
+ adc_total += adc_data;
+ }
+
+ return (adc_total - adc_max - adc_min) / (ADC_SAMPLING_CNT - 2);
+ err:
+ return err_value;
+}
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+static inline int s3c_read_temper_adc(struct sec_bat_info *info)
+{
+ int adc;
+
+ mutex_lock(&info->adclock);
+ adc = sec_bat_get_adc_data(info, info->adc_channel);
+ mutex_unlock(&info->adclock);
+ if (adc <= 0)
+ adc = info->batt_temp_adc;
+
+ info->batt_temp_adc =
+ calculate_average_adc(info, &info->temper_adc_sample, adc);
+
+ return info->batt_temp_adc;
+}
+#else
+static inline int s3c_read_temper_adc(struct sec_bat_info *info)
+{
+ int adc;
+
+ mutex_lock(&info->adclock);
+ adc = sec_bat_get_adc_data(info, info->adc_channel);
+ mutex_unlock(&info->adclock);
+ if (adc <= 0)
+ adc = info->batt_temp_adc;
+ info->batt_temp_adc = adc;
+
+ return adc;
+}
+#endif
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+static int is_event_end_timer_running(struct sec_bat_info *info)
+{
+ unsigned long passed_time = 0;
+
+ if (info->event_end_time == 0xFFFFFFFF) /*initial value */
+ return false;
+
+ if (jiffies >= info->event_end_time)
+ passed_time = jiffies - info->event_end_time;
+ else
+ passed_time = 0xFFFFFFFF - info->event_end_time + jiffies;
+
+ if (time_after(passed_time, (unsigned long)EVENT_OVER_TIME)) {
+ info->event_end_time = 0xFFFFFFFF;
+ dev_info(info->dev, "%s: Event timer is over 10 min\n",
+ __func__);
+ return false;
+ } else {
+ dev_info(info->dev, "%s: Event timer is running(%u s)\n",
+ __func__, jiffies_to_msecs(passed_time) / 1000);
+ return true;
+ }
+
+ return false;
+}
+
+static int get_recording_state(struct sec_bat_info *info)
+{
+ return info->batt_event_status & OFFSET_RECORDING_ON;
+}
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+static int is_event_end_timer_running(struct sec_bat_info *info)
+{
+ unsigned long passed_time = 0;
+
+ if (info->event_expired_time == 0xFFFFFFFF) /*initial value */
+ return false;
+
+ if (jiffies >= info->event_expired_time)
+ passed_time = jiffies - info->event_expired_time;
+ else
+ passed_time = 0xFFFFFFFF - info->event_expired_time + jiffies;
+
+ if (time_after(passed_time, (unsigned long)BAT_USE_TIMER_EXPIRE)) {
+ info->event_expired_time = 0xFFFFFFFF;
+ dev_info(info->dev, "[SPR_NA] %s: Event timer is over 10 min\n",
+ __func__);
+ return false;
+ } else {
+ dev_info(info->dev,
+ "[SPR_NA] %s: Event timer is running(%u s)\n",
+ __func__, jiffies_to_msecs(passed_time) / 1000);
+ return true;
+ }
+
+ return false;
+}
+
+static unsigned long sec_rescale_temp_adc(struct sec_bat_info *info)
+{
+ int adc_tmp = info->batt_temp_adc;
+ int adc_tmp1 = 0;
+ int adc_tmp2 = 0;
+
+ adc_tmp1 = adc_tmp * 10;
+ adc_tmp2 = (40950 - adc_tmp1);
+ adc_tmp = adc_tmp2 / 50;
+ if ((adc_tmp2 % 10) >= 5)
+ adc_tmp += 1;
+
+ info->batt_temp_radc = adc_tmp;
+
+ dev_dbg(info->dev, "deb***: [battery] bat temper : %d, rescale : %d\n",
+ info->batt_temp_adc, adc_tmp);
+
+ return adc_tmp;
+}
+#endif
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+static int sec_bat_check_temper(struct sec_bat_info *info)
+{
+ struct power_supply *psy
+ = get_power_supply_by_name(info->fuel_gauge_name);
+ union power_supply_propval value;
+ int ret;
+
+ int temp;
+ int temp_adc = s3c_read_temper_adc(info);
+ int temp_radc = sec_rescale_temp_adc(info);
+ int health = info->batt_health;
+ int low = 0;
+ int high = 0;
+ int mid = 0;
+
+ /*calculate_average_adc(info, &info->temper_adc_sample, temp_adc);*/
+
+ if (!info->adc_table || !info->adc_arr_size) {
+ /* using fake temp */
+ temp = 300;
+ info->batt_temp = temp;
+ return temp;
+ }
+ high = info->adc_arr_size - 1;
+
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if (info->adc_table[mid].adc > temp_adc)
+ high = mid - 1;
+ else if (info->adc_table[mid].adc < temp_adc)
+ low = mid + 1;
+ else
+ break;
+ }
+ temp = info->adc_table[mid].temperature;
+
+ info->batt_temp = temp;
+
+ if (info->get_lpcharging_state) {
+ if (info->get_lpcharging_state()) {
+ if (temp_radc >= HIGH_BLOCK_TEMP_ADC_LPM) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_high_cnt <
+ TEMP_BLOCK_COUNT)
+ info->batt_temp_high_cnt++;
+ dev_info(info->dev, "%s: high count = %d\n",
+ __func__, info->batt_temp_high_cnt);
+ } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC_LPM &&
+ temp_radc >= LOW_RECOVER_TEMP_ADC_LPM) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ health == POWER_SUPPLY_HEALTH_COLD) {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+
+ if (info->batt_temp_recover_cnt <
+ TEMP_BLOCK_COUNT)
+ info->batt_temp_recover_cnt++;
+ dev_info(info->dev,
+ "%s: recovery count = %d\n",
+ __func__,
+ info->batt_temp_recover_cnt);
+ }
+ } else if (temp_radc <= LOW_BLOCK_TEMP_ADC_LPM) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_low_cnt <
+ TEMP_BLOCK_COUNT)
+ info->batt_temp_low_cnt++;
+ dev_info(info->dev, "%s: low count = %d\n",
+ __func__, info->batt_temp_low_cnt);
+ } else {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+ }
+ } else {
+ if ((info->batt_event_status)
+ || (is_event_end_timer_running(info))) {
+ dev_info(info->dev,
+ "[NA_SPR] Changed Put off Current",
+ __func__);
+ if (temp_radc >= EVENT_BLOCK_TEMP_ADC) {
+ if (health !=
+ POWER_SUPPLY_HEALTH_OVERHEAT
+ && health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_high_cnt <
+ TEMP_BLOCK_COUNT)
+ info->
+ batt_temp_high_cnt++;
+ dev_info(info->dev,
+ "%s: high count = %d\n",
+ __func__,
+ info->batt_temp_high_cnt);
+ } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC
+ && temp_radc >=
+ LOW_RECOVER_TEMP_ADC) {
+ if (health ==
+ POWER_SUPPLY_HEALTH_OVERHEAT
+ || health ==
+ POWER_SUPPLY_HEALTH_COLD) {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+
+ if (info->
+ batt_temp_recover_cnt <
+ TEMP_BLOCK_COUNT)
+ info->
+ batt_temp_recover_cnt++;
+ dev_info(info->dev,
+ "%s: recovery count = %d\n",
+ __func__,
+ info->
+ batt_temp_recover_cnt);
+ }
+ } else if (temp_radc <= LOW_BLOCK_TEMP_ADC) {
+ if (health != POWER_SUPPLY_HEALTH_COLD
+ && health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_low_cnt <
+ TEMP_BLOCK_COUNT)
+ info->
+ batt_temp_low_cnt++;
+ dev_info(info->dev,
+ "%s: low count = %d\n",
+ __func__,
+ info->batt_temp_low_cnt);
+ } else {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+ }
+ } else {
+ if (temp_radc >= HIGH_BLOCK_TEMP_ADC) {
+ if (health !=
+ POWER_SUPPLY_HEALTH_OVERHEAT
+ && health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_high_cnt <
+ TEMP_BLOCK_COUNT)
+ info->
+ batt_temp_high_cnt++;
+ dev_info(info->dev,
+ "%s: high count = %d\n",
+ __func__,
+ info->batt_temp_high_cnt);
+ } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC
+ && temp_radc >=
+ LOW_RECOVER_TEMP_ADC) {
+ if (health ==
+ POWER_SUPPLY_HEALTH_OVERHEAT
+ || health ==
+ POWER_SUPPLY_HEALTH_COLD) {
+
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+
+ if (info->
+ batt_temp_recover_cnt <
+ TEMP_BLOCK_COUNT)
+ info->
+ batt_temp_recover_cnt++;
+ dev_info(info->dev,
+ "%s: recovery count = %d\n",
+ __func__,
+ info->
+ batt_temp_recover_cnt);
+ }
+ } else if (temp_radc <= LOW_BLOCK_TEMP_ADC) {
+ if (health != POWER_SUPPLY_HEALTH_COLD
+ && health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_low_cnt <
+ TEMP_BLOCK_COUNT)
+ info->
+ batt_temp_low_cnt++;
+ dev_info(info->dev,
+ "%s: low count = %d\n",
+ __func__,
+ info->batt_temp_low_cnt);
+ } else {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+ }
+ }
+ }
+ }
+
+ if (info->cable_type == CABLE_TYPE_NONE) {
+ if (info->batt_health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ info->batt_health == POWER_SUPPLY_HEALTH_COLD) {
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ goto skip;
+ }
+ }
+
+ if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_COLD;
+ else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /*else
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD; */
+
+ /*else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD; */
+ skip:
+ /* Set temperature to fuel gauge */
+ if (info->fuel_gauge_name) {
+ value.intval = info->batt_temp / 10;
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set temperature(%d)\n",
+ __func__, ret);
+ }
+ }
+
+ dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc);
+
+ return temp;
+}
+
+static void sec_bat_get_dcinovp(struct sec_bat_info *info)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get %s ps\n",
+ __func__, info->charger_name);
+ return;
+ }
+
+ if (info->slate_test_mode) {
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ info->cable_type = CABLE_TYPE_NONE;
+ } else {
+ psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value);
+ info->charging_status = value.intval;
+ }
+}
+
+#elif defined(CONFIG_TARGET_LOCALE_NAATT)
+static int sec_bat_check_temper(struct sec_bat_info *info)
+{
+ struct power_supply *psy
+ = get_power_supply_by_name(info->fuel_gauge_name);
+ union power_supply_propval value;
+ int ret;
+
+ int temp;
+ int temp_adc = s3c_read_temper_adc(info);
+ int health = info->batt_health;
+ int low = 0;
+ int high = 0;
+ int mid = 0;
+
+ calculate_average_adc(info, &info->temper_adc_sample, temp_adc);
+
+ if (!info->adc_table || !info->adc_arr_size) {
+ /* using fake temp */
+ temp = 300;
+ info->batt_temp = temp;
+ return temp;
+ }
+ high = info->adc_arr_size - 1;
+
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if (info->adc_table[mid].adc > temp_adc)
+ high = mid - 1;
+ else if (info->adc_table[mid].adc < temp_adc)
+ low = mid + 1;
+ else
+ break;
+ }
+ temp = info->adc_table[mid].temperature;
+
+ info->batt_temp = temp;
+
+ if ((info->batt_event_status) || (is_event_end_timer_running(info))) {
+ /*printk("[%s] event temp is applied\n", __func__); */
+ if (temp >= EVENT_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_high_cnt++;
+ dev_info(info->dev,
+ "%s: event high count = %d(in event)\n",
+ __func__, info->batt_temp_high_cnt);
+ } else if (temp <= EVENT_RECOVER_TEMP
+ && temp >= LOW_RECOVER_TEMP) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT
+ || health == POWER_SUPPLY_HEALTH_COLD) {
+
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+
+ if (info->batt_temp_recover_cnt <
+ TEMP_BLOCK_COUNT)
+ info->batt_temp_recover_cnt++;
+ dev_info(info->dev,
+ "%s: event recovery count = %d\n",
+ __func__, info->batt_temp_recover_cnt);
+ }
+ } else if (temp <= LOW_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_low_cnt++;
+ dev_info(info->dev,
+ "%s: event low count = %d(in event)\n",
+ __func__, info->batt_temp_low_cnt);
+ } else {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+ }
+ } else {
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ info->batt_temp_ext_pre = info->batt_temp_ext;
+ switch (info->batt_temp_ext) {
+ case BATT_TEMP_EXT_NONE:
+ if (get_recording_state(info))
+ info->batt_temp_ext =
+ BATT_TEMP_EXT_CAMCORDING_NORMAL;
+ break;
+ case BATT_TEMP_EXT_CAMCORDING_NORMAL:
+ if (!get_recording_state(info))
+ info->batt_temp_ext = BATT_TEMP_EXT_NONE;
+ else if (temp >= HIGH_DEC_CURR_TEMP)
+ info->batt_temp_ext =
+ BATT_TEMP_EXT_CAMCORDING_HIGH;
+ break;
+ case BATT_TEMP_EXT_CAMCORDING_HIGH:
+ if (!get_recording_state(info))
+ info->batt_temp_ext = BATT_TEMP_EXT_NONE;
+ else if (temp <= HIGH_INC_CURR_TEMP)
+ info->batt_temp_ext =
+ BATT_TEMP_EXT_CAMCORDING_NORMAL;
+ break;
+ default:
+ break;
+ }
+ dev_info(info->dev,
+ "%s: batt_temp_ext_pre=%d, batt_temp_ext=%d\n",
+ __func__, info->batt_temp_ext_pre,
+ info->batt_temp_ext);
+#endif
+ /*printk("[%s] nomal temp is applied\n", __func__); */
+ if (temp >= HIGH_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_high_cnt++;
+ dev_info(info->dev, "%s: high count = %d\n",
+ __func__, info->batt_temp_high_cnt);
+ } else if (temp <= HIGH_RECOVER_TEMP
+ && temp >= LOW_RECOVER_TEMP) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT
+ || health == POWER_SUPPLY_HEALTH_COLD) {
+
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+
+ if (info->batt_temp_recover_cnt <
+ TEMP_BLOCK_COUNT)
+ info->batt_temp_recover_cnt++;
+ dev_info(info->dev, "%s: recovery count = %d\n",
+ __func__, info->batt_temp_recover_cnt);
+ }
+ } else if (temp <= LOW_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_low_cnt++;
+ dev_info(info->dev, "%s: low count = %d\n",
+ __func__, info->batt_temp_low_cnt);
+ } else {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+ }
+ }
+
+ if (info->cable_type == CABLE_TYPE_NONE) {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ }
+
+ if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_COLD;
+ else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /* Set temperature to fuel gauge */
+ if (info->fuel_gauge_name) {
+ value.intval = info->batt_temp / 10;
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set temperature(%d)\n",
+ __func__, ret);
+ }
+ }
+
+ dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc);
+
+ return temp;
+}
+
+#else
+static int sec_bat_check_temper(struct sec_bat_info *info)
+{
+ struct power_supply *psy
+ = get_power_supply_by_name(info->fuel_gauge_name);
+ union power_supply_propval value;
+ int ret;
+
+ int temp;
+ int temp_adc = s3c_read_temper_adc(info);
+ int health = info->batt_health;
+ int low = 0;
+ int high = 0;
+ int mid = 0;
+
+ calculate_average_adc(info, &info->temper_adc_sample, temp_adc);
+
+ if (!info->adc_table || !info->adc_arr_size) {
+ /* using fake temp */
+ temp = 300;
+ info->batt_temp = temp;
+ return temp;
+ }
+ high = info->adc_arr_size - 1;
+
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if (info->adc_table[mid].adc > temp_adc)
+ high = mid - 1;
+ else if (info->adc_table[mid].adc < temp_adc)
+ low = mid + 1;
+ else
+ break;
+ }
+ temp = info->adc_table[mid].temperature;
+
+ info->batt_temp = temp;
+
+ if (temp >= HIGH_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_high_cnt++;
+ dev_info(info->dev, "%s: high count = %d\n",
+ __func__, info->batt_temp_high_cnt);
+ } else if (temp <= HIGH_RECOVER_TEMP && temp >= LOW_RECOVER_TEMP) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ health == POWER_SUPPLY_HEALTH_COLD)
+ if (info->batt_temp_recover_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_recover_cnt++;
+ dev_info(info->dev, "%s: recovery count = %d\n",
+ __func__, info->batt_temp_recover_cnt);
+ } else if (temp <= LOW_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT)
+ info->batt_temp_low_cnt++;
+ dev_info(info->dev, "%s: low count = %d\n",
+ __func__, info->batt_temp_low_cnt);
+ } else {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+ }
+
+ if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_COLD;
+ else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT)
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /* Set temperature to fuel gauge */
+ if (info->fuel_gauge_name) {
+ value.intval = info->batt_temp / 10;
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set temperature(%d)\n",
+ __func__, ret);
+ }
+ }
+
+ dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc);
+
+ return temp;
+}
+#endif
+static void check_chgcurrent(struct sec_bat_info *info)
+{
+ unsigned long chg_current_adc = 0;
+
+ mutex_lock(&info->adclock);
+ chg_current_adc = sec_bat_get_adc_data(info, ADC_CH_CHGCURRENT);
+ mutex_unlock(&info->adclock);
+ if (chg_current_adc < 0)
+ chg_current_adc = info->batt_current_adc;
+ info->batt_current_adc = chg_current_adc;
+
+ dev_dbg(info->dev,
+ "[battery] charging current = %d\n", info->batt_current_adc);
+}
+
+static bool sec_check_chgcurrent(struct sec_bat_info *info)
+{
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ info->batt_current_adc = 0;
+ if ((info->batt_full_status == BATT_FULL) &&
+ (info->recharging_status == false))
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ check_chgcurrent(info);
+
+ if (info->batt_vfocv >= FULL_CHARGE_COND_VOLTAGE / 1000) {
+
+#ifdef SEC_BATTERY_1ST_2ND_TOPOFF
+ if (((info->charging_status ==
+ POWER_SUPPLY_STATUS_CHARGING)
+ && (info->batt_current_adc <=
+ CURRENT_1ST_FULL_CHG))
+ ||
+ ((info->charging_status == POWER_SUPPLY_STATUS_FULL)
+ && (info->batt_current_adc <=
+ CURRENT_2ND_FULL_CHG))) {
+#else
+ if (info->batt_current_adc <= CURRENT_OF_FULL_CHG) {
+#endif
+ /*TA full charged */
+ info->charging_adc_full_count++;
+ if (info->charging_adc_full_count >=
+ FULL_CHG_COND_COUNT) {
+ info->charging_adc_full_count = 0;
+ sec_bat_handle_charger_topoff(info);
+
+ return true;
+ }
+ dev_info(info->dev, "%s : ADC full cnt = %d\n",
+ __func__,
+ info->charging_adc_full_count);
+ } else
+ info->charging_adc_full_count = 0;
+ }
+ break;
+ default:
+ info->charging_adc_full_count = 0;
+ info->batt_current_adc = 0;
+ break;
+ }
+
+ return false;
+}
+
+static void sec_bat_update_info(struct sec_bat_info *info)
+{
+ info->batt_raw_soc = sec_bat_get_fuelgauge_data(info, FG_T_PSOC);
+ info->batt_soc = sec_bat_get_fuelgauge_data(info, FG_T_SOC);
+ info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL);
+ info->batt_vfocv = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV);
+}
+
+static int sec_bat_enable_charging_main(struct sec_bat_info *info, bool enable)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval val_type, val_chg_current, val_topoff;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ info->batt_full_status = BATT_NOT_FULL;
+
+ if (enable) { /* Enable charging */
+ switch (info->cable_type) {
+ case CABLE_TYPE_USB:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ case CABLE_TYPE_AC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 650; /* mA */
+ break;
+ case CABLE_TYPE_MISC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ default:
+ dev_err(info->dev, "%s: Invalid func use\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Set charging current */
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Set topoff current */
+ /* mA: TODO: should change 200->160 */
+ val_topoff.intval = 200;
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL,
+ &val_topoff);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set topoff cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /*Reset charging start time only in initial charging start */
+ if (info->charging_start_time == 0) {
+ info->charging_start_time = jiffies;
+ info->charging_next_time = RESETTING_CHG_TIME;
+ }
+ } else { /* Disable charging */
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ info->charging_start_time = 0;
+ info->charging_passed_time = 0;
+ info->charging_next_time = 0;
+ }
+
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int sec_bat_enable_charging_sub(struct sec_bat_info *info, bool enable)
+{
+ struct power_supply *psy_main =
+ get_power_supply_by_name(info->charger_name);
+ struct power_supply *psy_sub =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval val_type, val_chg_current;
+ int ret;
+
+ if (!psy_main || !psy_sub) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ info->batt_full_status = BATT_NOT_FULL;
+
+ if (enable) { /* Enable charging */
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging"
+ " status-main(%d)\n", __func__, ret);
+ return ret;
+ }
+#if defined(CONFIG_MACH_Q1_BD)
+ /* Set charging current only in charging start time */
+ if (info->charging_start_time == 0) {
+#endif
+ switch (info->cable_type) {
+ case CABLE_TYPE_USB:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ case CABLE_TYPE_AC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ if (info->batt_temp_ext ==
+ BATT_TEMP_EXT_CAMCORDING_HIGH)
+ val_chg_current.intval = 450; /* mA */
+ else
+#endif
+ val_chg_current.intval = 650; /* mA */
+ break;
+ case CABLE_TYPE_MISC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ default:
+ dev_err(info->dev, "%s: Invalid func use\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ /* Set charging current */
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev,
+ "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+#if defined(CONFIG_MACH_Q1_BD)
+ /* Reset charging */
+ } else
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+#endif
+
+ /*Reset charging start time only in initial charging start */
+ if (info->charging_start_time == 0) {
+ info->charging_start_time = jiffies;
+ info->charging_next_time = RESETTING_CHG_TIME;
+ }
+ } else { /* Disable charging */
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging"
+ " status-main(%d)\n", __func__, ret);
+ return ret;
+ }
+ info->charging_start_time = 0;
+ info->charging_passed_time = 0;
+ info->charging_next_time = 0;
+ }
+
+ ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable)
+{
+ if (enable && (info->batt_health != POWER_SUPPLY_HEALTH_GOOD)) {
+ info->charging_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ dev_info(info->dev, "%s: Battery is NOT good!!!\n", __func__);
+ return -EPERM;
+ }
+
+ if (info->use_sub_charger)
+ return sec_bat_enable_charging_sub(info, enable);
+ else
+ return sec_bat_enable_charging_main(info, enable);
+}
+
+static void sec_bat_cable_work(struct work_struct *work)
+{
+ struct sec_bat_info *info = container_of(work, struct sec_bat_info,
+ cable_work);
+
+ switch (info->cable_type) {
+ case CABLE_TYPE_NONE:
+ if (!sec_bat_enable_charging(info, false)) {
+ info->batt_full_status = BATT_NOT_FULL;
+ info->recharging_status = false;
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ info->batt_temp_ext = BATT_TEMP_EXT_NONE;
+ info->batt_temp_ext_pre = BATT_TEMP_EXT_NONE;
+#endif
+ wake_lock_timeout(&info->vbus_wake_lock, HZ * 5);
+ }
+ break;
+ case CABLE_TYPE_USB:
+ case CABLE_TYPE_AC:
+ case CABLE_TYPE_MISC:
+ if (!sec_bat_enable_charging(info, true)) {
+ info->charging_status = POWER_SUPPLY_STATUS_CHARGING;
+ wake_lock(&info->vbus_wake_lock);
+ }
+ break;
+ default:
+ dev_err(info->dev, "%s: Invalid cable type\n", __func__);
+ break;
+ }
+
+ power_supply_changed(&info->psy_ac);
+ power_supply_changed(&info->psy_usb);
+
+ wake_unlock(&info->cable_wake_lock);
+}
+
+static bool sec_bat_charging_time_management(struct sec_bat_info *info)
+{
+ if (info->charging_start_time == 0) {
+ dev_info(info->dev,
+ "%s: charging_start_time has never been used since initializing\n",
+ __func__);
+ return false;
+ }
+
+ if (jiffies >= info->charging_start_time)
+ info->charging_passed_time =
+ jiffies - info->charging_start_time;
+ else
+ info->charging_passed_time =
+ 0xFFFFFFFF - info->charging_start_time + jiffies;
+
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ if (info->recharging_status == true &&
+ time_after(info->charging_passed_time,
+ (unsigned long)RECHARGING_TIME)) {
+ if (!sec_bat_enable_charging(info, false)) {
+ info->recharging_status = false;
+ dev_info(info->dev,
+ "%s: Recharging timer expired\n",
+ __func__);
+ return true;
+ }
+ }
+#ifndef SEC_BATTERY_1ST_2ND_TOPOFF
+ break;
+#endif
+ case POWER_SUPPLY_STATUS_CHARGING:
+#if defined(CONFIG_MACH_Q1_BD)
+ if (info->cable_type != CABLE_TYPE_USB) {
+#endif
+ if (time_after(info->charging_passed_time,
+ (unsigned long)FULL_CHARGING_TIME)) {
+ if (!sec_bat_enable_charging(info, false)) {
+ info->charging_status =
+ POWER_SUPPLY_STATUS_FULL;
+ info->batt_full_status = BATT_FULL;
+ info->recharging_status = false;
+
+ dev_info(info->dev,
+ "%s: Charging timer expired\n",
+ __func__);
+ return true;
+ }
+ }
+#if defined(CONFIG_MACH_Q1_BD)
+ }
+#endif
+ if (time_after(info->charging_passed_time,
+ info->charging_next_time)) {
+ /*reset current in charging status */
+ if (!sec_bat_enable_charging(info, true)) {
+ info->charging_next_time =
+ info->charging_passed_time +
+ RESETTING_CHG_TIME;
+
+ dev_info(info->dev,
+ "%s: Reset charging current\n",
+ __func__);
+ }
+ }
+ break;
+ default:
+ dev_info(info->dev, "%s: Undefine Battery Status\n", __func__);
+ return false;
+ }
+
+ dev_info(info->dev, "Time past : %u secs\n",
+ jiffies_to_msecs(info->charging_passed_time) / 1000);
+
+ return false;
+}
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+static void sec_bat_check_vf_adc(struct sec_bat_info *info)
+{
+ int adc;
+ static int invalid_vf_count;
+
+ if (system_rev == 11)
+ return;
+
+ mutex_lock(&info->adclock);
+ adc = sec_bat_get_adc_data(info, info->vf_adc_channel);
+ mutex_unlock(&info->adclock);
+
+ if (adc <= 0)
+ adc = info->batt_vf_adc;
+
+ info->batt_vf_adc = adc;
+
+ if ((info->batt_vf_adc > MAX_VF || info->batt_vf_adc < MIN_VF)
+ && ((info->cable_type == CABLE_TYPE_AC)
+ || info->cable_type == CABLE_TYPE_USB)) {
+ invalid_vf_count++;
+
+ printk(KERN_DEBUG
+ "[%s] invalid VF is detected... (vf = %d, count = %d)\n",
+ __func__, info->batt_vf_adc, invalid_vf_count);
+
+ if (invalid_vf_count >= VF_COUNT) {
+ printk(KERN_DEBUG
+ "[%s] invalid VF is detected over %d times and now power off.\n",
+ __func__, invalid_vf_count);
+ if (pm_power_off)
+ pm_power_off();
+ }
+ } else {
+ invalid_vf_count = 0;
+ }
+
+ return;
+}
+#endif
+
+static void sec_bat_check_vf(struct sec_bat_info *info)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get %s ps\n",
+ __func__, info->charger_name);
+ return;
+ }
+
+#if defined(CONFIG_MACH_Q1_BD)
+ if (system_rev <= HWREV_FOR_BATTERY) {
+ int adc;
+
+ mutex_lock(&info->adclock);
+ adc = sec_bat_get_adc_data(info, ADC_CH_VF);
+ mutex_unlock(&info->adclock);
+
+ dev_info(info->dev, "%s: adc (%d/%d)\n", __func__, adc,
+ HWREV_FOR_BATTERY);
+
+ if (adc > MAX_VF_ADC || adc < MIN_VF_ADC)
+ value.intval = BAT_NOT_DETECTED;
+ else
+ value.intval = BAT_DETECTED;
+ } else {
+#endif
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n",
+ __func__, ret);
+ return;
+ }
+#if defined(CONFIG_MACH_Q1_BD)
+ }
+#endif
+
+ if (value.intval == BAT_NOT_DETECTED) {
+ if (info->present_count < BAT_DET_COUNT)
+ info->present_count++;
+ else {
+ info->present = BAT_NOT_DETECTED;
+ if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD)
+ info->batt_health =
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ }
+ } else {
+ info->present = BAT_DETECTED;
+ if ((info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) ||
+ (info->batt_health == POWER_SUPPLY_HEALTH_UNKNOWN))
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ info->present_count = 0;
+ }
+
+ dev_info(info->dev, "%s: Battery Health (%d)\n",
+ __func__, info->batt_health);
+ return;
+}
+
+#if (defined(CONFIG_MACH_Q1_BD) && defined(CONFIG_SMB328_CHARGER))
+static void sec_bat_check_ovp(struct sec_bat_info *info)
+{
+ struct power_supply *psy =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval value;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get %s ps\n",
+ __func__, info->sub_charger_name);
+ return;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_HEALTH, &value);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get health(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ if (value.intval == POWER_SUPPLY_HEALTH_OVERVOLTAGE)
+ info->batt_health = value.intval;
+}
+#endif
+
+static bool sec_bat_check_ing_level_trigger(struct sec_bat_info *info)
+{
+ struct power_supply *psy;
+ union power_supply_propval value;
+ int ret;
+
+ if (info->use_sub_charger && info->sub_charger_name) {
+ psy = get_power_supply_by_name(info->sub_charger_name);
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get %s ps\n",
+ __func__, info->sub_charger_name);
+ return false;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n",
+ __func__, ret);
+ return false;
+ }
+
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ if (info->recharging_status == false)
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ switch (value.intval) {
+#ifdef SEC_BATTERY_TOPOFF_BY_CHARGER
+ case POWER_SUPPLY_STATUS_FULL:
+ /* top-off by full charging */
+ if (info->batt_vcell >=
+ FULL_CHARGE_COND_VOLTAGE) {
+ sec_bat_handle_charger_topoff(info);
+ dev_info(info->dev,
+ "%s : top-off by full charging\n",
+ __func__);
+ return true;
+ } else {
+ /*reactivate charging in */
+ /*next monitor work */
+ /*for abnormal */
+ /*full-charged status */
+ info->charging_next_time =
+ info->charging_passed_time + HZ;
+ }
+ break;
+#endif
+#if defined(CONFIG_MACH_Q1_BD)
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ ret =
+ psy->get_property(psy,
+ POWER_SUPPLY_PROP_HEALTH,
+ &value);
+
+ if (ret < 0) {
+ dev_err(info->dev,
+ "%s: fail to get health(%d)\n",
+ __func__, ret);
+ return false;
+ }
+ info->batt_health = value.intval;
+#else
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ if (info->cable_type != CABLE_TYPE_NONE)
+ info->batt_health =
+ POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00)
+ if (info->cable_type == CABLE_TYPE_USB ||
+ info->cable_type == CABLE_TYPE_MISC ||
+ info->cable_type == CABLE_TYPE_AC) {
+#else
+ if (info->cable_type == CABLE_TYPE_USB ||
+ info->cable_type == CABLE_TYPE_MISC) {
+#endif
+ if (info->batt_vcell >=
+ FULL_CHARGE_COND_VOLTAGE) {
+ /* USB full charged */
+ info->charging_int_full_count++;
+ if (info->
+ charging_int_full_count >=
+ FULL_CHG_COND_COUNT) {
+ info->
+ charging_int_full_count
+ = 0;
+ sec_bat_handle_charger_topoff
+ (info);
+ return true;
+ }
+ dev_info(info->dev,
+ "%s : full interrupt cnt = %d\n",
+ __func__,
+ info->
+ charging_int_full_count);
+ } else {
+ info->charging_int_full_count =
+ 0;
+ /*reactivate charging in */
+ /*next monitor work */
+ /*for abnormal */
+ /*full-charged status */
+ info->charging_next_time =
+ info->charging_passed_time +
+ HZ;
+ }
+ }
+ break;
+#endif
+ }
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+#if defined(CONFIG_MACH_Q1_BD)
+ if (info->batt_health ==
+ POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
+ ret =
+ psy->get_property(psy,
+ POWER_SUPPLY_PROP_HEALTH,
+ &value);
+
+ if (ret < 0) {
+ dev_err(info->dev,
+ "%s: fail to get health(%d)\n",
+ __func__, ret);
+ return false;
+ }
+ info->batt_health = value.intval;
+ }
+#else
+ if (info->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ && value.intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+#endif
+ break;
+ default:
+ info->charging_int_full_count = 0;
+ break;
+ }
+ return false;
+ } else {
+ psy = get_power_supply_by_name(info->charger_name);
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get %s ps\n",
+ __func__, info->charger_name);
+ return false;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n",
+ __func__, ret);
+ return false;
+ }
+
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ if (info->recharging_status == false)
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ switch (value.intval) {
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ if (info->cable_type == CABLE_TYPE_USB ||
+ info->cable_type == CABLE_TYPE_MISC) {
+ if (info->batt_vcell >=
+ FULL_CHARGE_COND_VOLTAGE) {
+ /*USB full charged */
+ info->charging_int_full_count++;
+ if (info->
+ charging_int_full_count >=
+ FULL_CHG_COND_COUNT) {
+ info->
+ charging_int_full_count
+ = 0;
+ sec_bat_handle_charger_topoff
+ (info);
+ return true;
+ }
+ dev_info(info->dev,
+ "%s : full interrupt cnt = %d\n",
+ __func__,
+ info->
+ charging_int_full_count);
+ } else {
+ info->charging_int_full_count =
+ 0;
+ /*reactivate charging in */
+ /*next monitor work */
+ /*for abnormal full-charged status */
+ info->charging_next_time =
+ info->charging_passed_time +
+ HZ;
+ }
+ }
+ break;
+ }
+ break;
+ default:
+ info->charging_int_full_count = 0;
+ break;
+ }
+ return false;
+ }
+}
+
+static void sec_bat_monitor_work(struct work_struct *work)
+{
+ struct sec_bat_info *info = container_of(work, struct sec_bat_info,
+ monitor_work);
+
+ sec_bat_check_temper(info);
+#ifndef SEC_BATTERY_INDEPEDENT_VF_CHECK
+ sec_bat_check_vf(info);
+#endif
+ sec_bat_update_info(info);
+#ifdef SPRINT_SLATE_TEST
+ if (info->charging_status != POWER_SUPPLY_STATUS_FULL &&
+ info->batt_health == POWER_SUPPLY_HEALTH_GOOD)
+ if (info->batt_temp_recover_cnt == 0
+ || info->batt_temp_recover_cnt > 3)
+ sec_bat_get_dcinovp(info);
+#endif
+ if (sec_bat_charging_time_management(info))
+ goto full_charged;
+
+ if (sec_bat_check_ing_level_trigger(info))
+ goto full_charged;
+
+#if !defined(CONFIG_MACH_Q1_BD)
+ if (info->cable_type == CABLE_TYPE_AC)
+#endif
+ if (sec_check_chgcurrent(info))
+ goto full_charged;
+
+ if (info->cable_type == CABLE_TYPE_NONE &&
+ info->charging_status == POWER_SUPPLY_STATUS_FULL) {
+ dev_err(info->dev, "invalid full state\n");
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (info->test_value) {
+ switch (info->test_value) {
+ case 1: /*full status */
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case 2: /*low temperature */
+ info->batt_health = POWER_SUPPLY_HEALTH_COLD;
+ break;
+ case 3: /*high temperature */
+ info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case 4: /*over voltage */
+ info->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ case 5: /*abnormal battery */
+ info->batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case 6: /*tmu tripped */
+ info->batt_tmu_status = TMU_STATUS_TRIPPED;
+ break;
+ }
+ }
+
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+ if (info->batt_vcell < RECHARGING_VOLTAGE &&
+ info->recharging_status == false) {
+ info->recharging_int_threshold_count++;
+ if (info->recharging_int_threshold_count
+ >= RECHARGING_CND_COUNT) {
+ if (!sec_bat_enable_charging(info, true)) {
+ info->recharging_int_threshold_count =
+ 0;
+ info->recharging_status = true;
+
+ dev_info(info->dev,
+ "%s: Start Recharging, Vcell = %d\n",
+ __func__, info->batt_vcell);
+ }
+ }
+ } else {
+ info->recharging_int_threshold_count = 0;
+ }
+#else
+ if (info->batt_vcell < RECHARGING_VOLTAGE &&
+ info->recharging_status == false) {
+ if (!sec_bat_enable_charging(info, true)) {
+ info->recharging_status = true;
+
+ dev_info(info->dev,
+ "%s: Start Recharging, Vcell = %d\n",
+ __func__, info->batt_vcell);
+ }
+ }
+#endif
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ switch (info->batt_health) {
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ case POWER_SUPPLY_HEALTH_GOOD:
+ if (info->batt_temp_ext_pre != info->batt_temp_ext) {
+ sec_bat_enable_charging(info, false);
+
+ if (!sec_bat_enable_charging(info, true)) {
+ dev_info(info->dev,
+ "%s: Changed charging current\n",
+ __func__);
+ }
+ }
+ break;
+#endif
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ case POWER_SUPPLY_HEALTH_COLD:
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ case POWER_SUPPLY_HEALTH_DEAD:
+ case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+ if (!sec_bat_enable_charging(info, false)) {
+ info->charging_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ info->recharging_status = false;
+
+ dev_info(info->dev, "%s: Not charging\n",
+ __func__);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ dev_dbg(info->dev, "%s: Discharging\n", __func__);
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) {
+ dev_info(info->dev, "%s: recover health state\n",
+ __func__);
+ if (info->cable_type != CABLE_TYPE_NONE) {
+ if (!sec_bat_enable_charging(info, true))
+ info->charging_status =
+ POWER_SUPPLY_STATUS_CHARGING;
+ } else
+ info->charging_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ default:
+ dev_info(info->dev, "%s: Undefined Battery Status\n", __func__);
+ return;
+ }
+
+ full_charged:
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ dev_info(info->dev,
+ "soc(%d), vfocv(%d), vcell(%d), temp(%d), charging(%d), health(%d), vf(%d)\n",
+ info->batt_soc, info->batt_vfocv, info->batt_vcell / 1000,
+ info->batt_temp / 10, info->charging_status, info->batt_health,
+ info->batt_vf_adc);
+#else
+ dev_info(info->dev,
+ "soc(%d), vfocv(%d), vcell(%d), temp(%d), charging(%d), health(%d), chg_adc(%d)\n",
+ info->batt_soc, info->batt_vfocv, info->batt_vcell / 1000,
+ info->batt_temp / 10, info->charging_status,
+ info->batt_health, info->batt_current_adc);
+#endif
+
+ power_supply_changed(&info->psy_bat);
+
+ wake_unlock(&info->monitor_wake_lock);
+
+ return;
+}
+
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+static void sec_bat_vf_check_work(struct work_struct *work)
+{
+ struct sec_bat_info *info;
+ info = container_of(work, struct sec_bat_info, vf_check_work.work);
+
+ sec_bat_check_vf(info);
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ sec_bat_check_vf_adc(info);
+#endif
+
+#if (defined(CONFIG_MACH_Q1_BD) && defined(CONFIG_SMB328_CHARGER))
+ sec_bat_check_ovp(info);
+
+ if ((info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) ||
+ (info->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE)) {
+ dev_info(info->dev,
+ "%s: Battery Disconnected or OVP activated\n",
+ __func__);
+#else
+ if (info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
+ dev_info(info->dev, "%s: Battery Disconnected\n", __func__);
+#endif
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+ }
+
+ schedule_delayed_work(&info->vf_check_work,
+ msecs_to_jiffies(VF_CHECK_INTERVAL));
+}
+#endif
+
+static void sec_bat_polling_work(struct work_struct *work)
+{
+ struct sec_bat_info *info;
+ info = container_of(work, struct sec_bat_info, polling_work.work);
+
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ if (info->initial_check_count) {
+ schedule_delayed_work(&info->polling_work, HZ);
+ info->initial_check_count--;
+ } else
+ schedule_delayed_work(&info->polling_work,
+ msecs_to_jiffies(info->polling_interval));
+}
+
+#define SEC_BATTERY_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, \
+ .mode = 0664 }, \
+ .show = sec_bat_show_property, \
+ .store = sec_bat_store, \
+}
+
+static struct device_attribute sec_battery_attrs[] = {
+ SEC_BATTERY_ATTR(batt_vol),
+ SEC_BATTERY_ATTR(batt_soc),
+ SEC_BATTERY_ATTR(batt_vfocv),
+ SEC_BATTERY_ATTR(batt_temp),
+ SEC_BATTERY_ATTR(batt_temp_adc),
+#ifdef CONFIG_TARGET_LOCALE_NA
+ SEC_BATTERY_ATTR(batt_temp_radc),
+#endif
+ SEC_BATTERY_ATTR(batt_temp_adc_avg),
+ SEC_BATTERY_ATTR(charging_source),
+ SEC_BATTERY_ATTR(batt_lp_charging),
+ SEC_BATTERY_ATTR(video),
+ SEC_BATTERY_ATTR(mp3),
+ SEC_BATTERY_ATTR(batt_type),
+ SEC_BATTERY_ATTR(batt_full_check),
+ SEC_BATTERY_ATTR(batt_temp_check),
+ SEC_BATTERY_ATTR(batt_temp_adc_spec),
+ SEC_BATTERY_ATTR(batt_test_value),
+ SEC_BATTERY_ATTR(batt_current_now),
+ SEC_BATTERY_ATTR(batt_current_adc),
+ SEC_BATTERY_ATTR(siop_activated),
+ SEC_BATTERY_ATTR(system_rev),
+#ifdef CONFIG_TARGET_LOCALE_NA
+ SEC_BATTERY_ATTR(fg_soc),
+#else
+ SEC_BATTERY_ATTR(fg_psoc),
+#endif /* CONFIG_TARGET_LOCALE_NA */
+ SEC_BATTERY_ATTR(batt_lpm_state),
+ SEC_BATTERY_ATTR(batt_tmu_status),
+#ifdef SPRINT_SLATE_TEST
+ SEC_BATTERY_ATTR(slate_test_mode),
+#endif
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ SEC_BATTERY_ATTR(batt_v_f_adc),
+ SEC_BATTERY_ATTR(batt_voice_call_2g),
+ SEC_BATTERY_ATTR(batt_voice_call_3g),
+ SEC_BATTERY_ATTR(batt_data_call),
+ SEC_BATTERY_ATTR(wifi),
+ SEC_BATTERY_ATTR(batt_gps),
+ SEC_BATTERY_ATTR(camera),
+ SEC_BATTERY_ATTR(recording),
+#elif defined(CONFIG_TARGET_LOCALE_NA)
+ SEC_BATTERY_ATTR(call),
+ /*SEC_BATTERY_ATTR(video),*/
+ /*SEC_BATTERY_ATTR(music),*/
+ SEC_BATTERY_ATTR(browser),
+ SEC_BATTERY_ATTR(hotspot),
+ SEC_BATTERY_ATTR(camera),
+ SEC_BATTERY_ATTR(data_call),
+ SEC_BATTERY_ATTR(wimax),
+#endif
+};
+
+enum {
+ BATT_VOL = 0,
+ BATT_SOC,
+ BATT_VFOCV,
+ BATT_TEMP,
+ BATT_TEMP_ADC,
+#ifdef CONFIG_TARGET_LOCALE_NA
+ BATT_TEMP_RADC,
+#endif
+ BATT_TEMP_ADC_AVG,
+ CHARGING_SOURCE,
+ BATT_LP_CHARGING,
+ BATT_VIDEO,
+ BATT_MP3,
+ BATT_TYPE,
+ BATT_FULL_CHECK,
+ BATT_TEMP_CHECK,
+ BATT_TEMP_ADC_SPEC,
+ BATT_TEST_VALUE,
+ BATT_CURRENT_NOW,
+ BATT_CURRENT_ADC,
+ BATT_SIOP_ACTIVATED,
+ BATT_SYSTEM_REV,
+ BATT_FG_PSOC,
+ BATT_LPM_STATE,
+ BATT_TMU_STATUS,
+#ifdef SPRINT_SLATE_TEST
+ SLATE_TEST_MODE,
+#endif
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ BATT_V_F_ADC,
+ BATT_VOICE_CALL_2G,
+ BATT_VOICE_CALL_3G,
+ BATT_DATA_CALL,
+ BATT_WIFI,
+ BATT_GPS,
+ BATT_CAMERA,
+ BATT_RECORDING,
+#endif
+#if defined(CONFIG_TARGET_LOCALE_NA)
+ BATT_CALL,
+ BATT_BROWSER,
+ BATT_HOTSOPT,
+ BATT_CAMERA,
+ BATT_DATA_CALL,
+ BATT_WIMAX,
+#endif
+};
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+static void sec_bat_check_event_status(struct sec_bat_info *info, int mode,
+ int offset)
+{
+ int is_event_running = 0;
+
+ if ((info->batt_event_status != 0)
+ /*&& (info->cable_type != CABLE_TYPE_NONE) */
+ )
+ is_event_running = 1;
+
+ if (mode) {
+ if (!(info->batt_event_status & offset))
+ info->batt_event_status |= offset;
+ } else {
+ if (info->batt_event_status & offset)
+ info->batt_event_status &= ~offset;
+ }
+
+ printk(KERN_DEBUG "[%s] current batt_event_status = 0x%x\n", __func__,
+ info->batt_event_status);
+
+ if ((info->batt_event_status == 0) && (is_event_running == 1))
+ info->event_end_time = jiffies;
+
+ return;
+}
+#elif defined(CONFIG_TARGET_LOCALE_NA)
+static void sec_bat_check_event_status(struct sec_bat_info *info, int mode,
+ int offset)
+{
+ int is_event_running = 0;
+
+ if ((info->batt_event_status != 0)
+ /*&& (info->cable_type != CABLE_TYPE_NONE) */
+ )
+ is_event_running = 1;
+
+ if (mode) {
+ if (!(info->batt_event_status & offset))
+ info->batt_event_status |= offset;
+ } else {
+ if (info->batt_event_status & offset)
+ info->batt_event_status &= ~offset;
+ }
+
+ printk(KERN_DEBUG "[%s] current batt_event_status = 0x%x\n", __func__,
+ info->batt_event_status);
+
+ if ((info->batt_event_status == 0) && (is_event_running == 1))
+ info->event_expired_time = jiffies;
+
+ return;
+}
+
+int sec_bat_use_wimax(int onoff)
+{
+ struct sec_bat_info *info = pchg;
+ sec_bat_check_event_status(info, onoff, USE_WIMAX);
+}
+EXPORT_SYMBOL(sec_bat_use_wimax);
+#endif
+
+static ssize_t sec_bat_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev->parent);
+ int i = 0, val;
+ const ptrdiff_t off = attr - sec_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_AVGVCELL);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_SOC:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_SOC);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_VFOCV:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_TEMP:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->batt_temp);
+ break;
+ case BATT_TEMP_ADC:
+ val = s3c_read_temper_adc(info);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+#ifdef CONFIG_TARGET_LOCALE_NA
+ case BATT_TEMP_RADC:
+ val = s3c_read_temper_adc(info);
+ val = sec_rescale_temp_adc(info);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_temp_radc);
+ break;
+#endif
+ case BATT_TEMP_ADC_AVG:
+ val = info->temper_adc_sample.average_adc;
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case CHARGING_SOURCE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->cable_type);
+ break;
+ case BATT_LP_CHARGING:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->get_lpcharging_state());
+ break;
+ case BATT_VIDEO:
+ /* TODO */
+#ifdef CONFIG_TARGET_LOCALE_NA
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_video);
+#else
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+#endif
+ break;
+ case BATT_MP3:
+ /* TODO */
+#ifdef CONFIG_TARGET_LOCALE_NA
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_music);
+#else
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+#endif
+ break;
+ case BATT_TYPE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "SDI_SDI");
+ break;
+ case BATT_FULL_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ (info->charging_status ==
+ POWER_SUPPLY_STATUS_FULL)
+ ? 1 : 0);
+ break;
+ case BATT_TEMP_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->batt_health);
+ break;
+ case BATT_TEMP_ADC_SPEC:
+#ifdef CONFIG_TARGET_LOCALE_NA
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "(HIGH: %d - %d, LOW: %d - %d)\n",
+ HIGH_BLOCK_TEMP_ADC, HIGH_RECOVER_TEMP_ADC,
+ LOW_BLOCK_TEMP_ADC, LOW_RECOVER_TEMP_ADC);
+#else
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "(HIGH: %d - %d, LOW: %d - %d)\n",
+ HIGH_BLOCK_TEMP / 10, HIGH_RECOVER_TEMP / 10,
+ LOW_BLOCK_TEMP / 10, LOW_RECOVER_TEMP / 10);
+#endif
+ break;
+ case BATT_TEST_VALUE:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "0-normal, 1-full, 2-low, 3-high, 4-over, 5-cf, 6-trip (%d)\n",
+ info->test_value);
+ break;
+ case BATT_CURRENT_NOW:
+ case BATT_SIOP_ACTIVATED:
+ {
+ struct power_supply *psy =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ pr_err("%s: fail to get battery ps\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ /* initialized as NOT charging */
+ value.intval = 0;
+
+ if ((info->charging_status ==
+ POWER_SUPPLY_STATUS_CHARGING)
+ || (info->charging_status ==
+ POWER_SUPPLY_STATUS_FULL))
+ psy->get_property(psy,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &value);
+
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ value.intval);
+ }
+ break;
+ case BATT_CURRENT_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_current_adc);
+ break;
+ case BATT_SYSTEM_REV:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", system_rev);
+ break;
+ case BATT_FG_PSOC:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_PSOC);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_LPM_STATE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_lpm_state);
+ break;
+ case BATT_TMU_STATUS:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_tmu_status);
+ break;
+#ifdef SPRINT_SLATE_TEST
+ case SLATE_TEST_MODE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->slate_test_mode);
+ break;
+#endif
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ case BATT_V_F_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_vf_adc);
+ break;
+ case BATT_VOICE_CALL_2G:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_VOICE_CALL_3G:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_DATA_CALL:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_WIFI:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_GPS:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_CAMERA:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_RECORDING:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+#endif
+#ifdef CONFIG_TARGET_LOCALE_NA
+ case BATT_CALL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_call);
+ break;
+ case BATT_BROWSER:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->use_browser);
+ break;
+ case BATT_HOTSOPT:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->use_hotspot);
+ break;
+ case BATT_CAMERA:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->use_camera);
+ break;
+ case BATT_DATA_CALL:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->use_data_call);
+ break;
+ case BATT_WIMAX:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_wimax);
+ break;
+#endif
+ default:
+ i = -EINVAL;
+ break;
+ }
+
+ return i;
+}
+
+static ssize_t sec_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0, x = 0;
+ const ptrdiff_t off = attr - sec_battery_attrs;
+ struct sec_bat_info *info = dev_get_drvdata(dev->parent);
+
+ switch (off) {
+ case BATT_CURRENT_NOW:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ ret = count;
+ if ((info->charging_status ==
+ POWER_SUPPLY_STATUS_CHARGING)
+ || (info->charging_status ==
+ POWER_SUPPLY_STATUS_FULL)) {
+ struct power_supply *psy =
+ get_power_supply_by_name(info->
+ sub_charger_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ pr_err("%s: fail to get battery ps\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ value.intval = x; /* charging current */
+ psy->set_property(psy,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &value);
+
+ /* reflect changing current */
+ value.intval = true;
+ psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE,
+ &value);
+ } else
+ pr_err("%s: NOT charging status\n", __func__);
+ }
+ break;
+ case BATT_SIOP_ACTIVATED:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ ret = count;
+ if ((info->charging_status ==
+ POWER_SUPPLY_STATUS_CHARGING) ||
+ (info->charging_status ==
+ POWER_SUPPLY_STATUS_FULL)) {
+ struct power_supply *psy =
+ get_power_supply_by_name(info->
+ sub_charger_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ pr_err("%s: fail to get battery ps\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ if (x) {
+ /* charging current for SIOP */
+ value.intval = 450;
+ } else {
+ switch (info->cable_type) {
+ case CABLE_TYPE_USB:
+ case CABLE_TYPE_MISC:
+ value.intval = 450; /* mA */
+ break;
+ case CABLE_TYPE_AC:
+ value.intval = 650; /* mA */
+ break;
+ default:
+ dev_err(info->dev,
+ "%s: Invalid func use\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+ psy->set_property(psy,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &value);
+
+ /* reflect changing current */
+ value.intval = true;
+ psy->set_property(psy,
+ POWER_SUPPLY_PROP_ONLINE,
+ &value);
+ } else
+ pr_err("%s: NOT charging status\n", __func__);
+ }
+ break;
+ case BATT_VIDEO:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: video(%d)\n", __func__, x);
+ ret = count;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ sec_bat_check_event_status(info, x, OFFSET_VIDEO_PLAY);
+#elif defined(CONFIG_TARGET_LOCALE_NA)
+ info->use_video = x;
+ sec_bat_check_event_status(info, x, USE_VIDEO);
+#endif
+ }
+ break;
+ case BATT_MP3:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: mp3(%d)\n", __func__, x);
+ ret = count;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ sec_bat_check_event_status(info, x, OFFSET_MP3_PLAY);
+#elif defined(CONFIG_TARGET_LOCALE_NA)
+ info->use_music = x;
+ sec_bat_check_event_status(info, x, USE_MUSIC);
+#endif
+ }
+ break;
+ case BATT_TEST_VALUE:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->test_value = x;
+ printk(KERN_DEBUG "%s : test case : %d\n", __func__,
+ info->test_value);
+ ret = count;
+ }
+ break;
+ case BATT_LPM_STATE:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->batt_lpm_state = x;
+ ret = count;
+ }
+ break;
+#ifdef SPRINT_SLATE_TEST
+ case SLATE_TEST_MODE:
+ if (strncmp(buf, "1", 1) == 0) {
+ info->slate_test_mode = true;
+ ret = count;
+ } else {
+ info->slate_test_mode = false;
+ ret = count;
+ }
+ break;
+#endif
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ case BATT_VOICE_CALL_2G:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: call 2G(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x,
+ OFFSET_VOICE_CALL_2G);
+ }
+ break;
+ case BATT_VOICE_CALL_3G:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: call 3G(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x,
+ OFFSET_VOICE_CALL_3G);
+ }
+ break;
+ case BATT_DATA_CALL:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: data call(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, OFFSET_DATA_CALL);
+ }
+ break;
+ case BATT_WIFI:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: wifi(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, OFFSET_WIFI);
+ }
+ break;
+ case BATT_GPS:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: gps(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, OFFSET_GPS);
+ }
+ break;
+ case BATT_CAMERA:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: camera(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, OFFSET_CAMERA_ON);
+ }
+ break;
+ case BATT_RECORDING:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: recording(%d)\n", __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x,
+ OFFSET_RECORDING_ON);
+ }
+ break;
+#endif
+#ifdef CONFIG_TARGET_LOCALE_NA
+ case BATT_CALL:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->use_call = x;
+ dev_info(info->dev, "[NA_SPR]%s: call (%d)\n", __func__,
+ x);
+ ret = count;
+ sec_bat_check_event_status(info, x, USE_CALL);
+ }
+ break;
+ case BATT_BROWSER:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->use_browser = x;
+ dev_info(info->dev, "[NA_SPR]%s: BROWSER(%d)\n",
+ __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, USE_BROWSER);
+ }
+ break;
+ case BATT_HOTSOPT:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->use_hotspot = x;
+ dev_info(info->dev, "[NA_SPR]%s: HOTSPOT(%d)\n",
+ __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, USE_HOTSPOT);
+ }
+ break;
+ case BATT_CAMERA:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "[NA_SPR]%s: CAMERA(%d)\n",
+ __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, USE_CAMERA);
+ }
+ break;
+ case BATT_DATA_CALL:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->use_data_call = x;
+ dev_info(info->dev, "[NA_SPR]%s: DATA CALL(%d)\n",
+ __func__, x);
+ ret = count;
+ sec_bat_check_event_status(info, x, USE_DATA_CALL);
+ }
+ break;
+ case BATT_WIMAX:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->use_wimax = x;
+ dev_info(info->dev, "[NA_SPR]%s: WIMAX(%d)\n", __func__,
+ x);
+ ret = count;
+ sec_bat_check_event_status(info, x, USE_WIMAX);
+ }
+ break;
+#endif
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int sec_bat_create_attrs(struct device *dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(sec_battery_attrs); i++) {
+ rc = device_create_file(dev, &sec_battery_attrs[i]);
+ if (rc)
+ goto sec_attrs_failed;
+ }
+ goto succeed;
+
+ sec_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sec_battery_attrs[i]);
+ succeed:
+ return rc;
+}
+
+static int sec_bat_is_charging(struct sec_bat_info *info)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ return value.intval;
+}
+
+static int sec_bat_read_proc(char *buf, char **start,
+ off_t offset, int count, int *eof, void *data)
+{
+ struct sec_bat_info *info = data;
+ struct timespec cur_time;
+ ktime_t ktime;
+ int len = 0;
+
+ ktime = alarm_get_elapsed_realtime();
+ cur_time = ktime_to_timespec(ktime);
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+ len = sprintf(buf,
+ "%lu, %u, %u, %u, %u, %u, %d, %d, %d, %u, %u, %u, %u, %u, %u, %u, %d, %lu\n",
+ cur_time.tv_sec,
+ info->batt_raw_soc,
+ info->batt_soc,
+ info->batt_vfocv,
+ info->batt_vcell,
+ info->batt_current_adc,
+ info->batt_full_status,
+ info->charging_int_full_count,
+ info->charging_adc_full_count,
+ info->recharging_status,
+ info->batt_temp_adc,
+ info->batt_temp_radc,
+ info->batt_health,
+ info->charging_status,
+ info->batt_tmu_status,
+ info->present,
+ info->cable_type, info->charging_passed_time);
+
+ return len;
+}
+#else
+ len = sprintf(buf,
+ "%lu, %u, %u, %u, %u, %u, %d, %d, %d, %u, %u, %u, %u, %u, %u, %d, %lu\n",
+ cur_time.tv_sec,
+ info->batt_raw_soc,
+ info->batt_soc,
+ info->batt_vfocv,
+ info->batt_vcell,
+ info->batt_current_adc,
+ info->batt_full_status,
+ info->charging_int_full_count,
+ info->charging_adc_full_count,
+ info->recharging_status,
+ info->batt_temp_adc,
+ info->batt_health,
+ info->charging_status,
+ info->batt_tmu_status,
+ info->present,
+ info->cable_type, info->charging_passed_time);
+
+ return len;
+}
+#endif
+
+static __devinit int sec_bat_probe(struct platform_device *pdev)
+{
+ struct sec_bat_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct sec_bat_info *info;
+ struct power_supply *psy;
+ union power_supply_propval value;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "%s: SEC Battery Driver Loading\n", __func__);
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, info);
+
+ info->dev = &pdev->dev;
+ if (!pdata->fuel_gauge_name || !pdata->charger_name) {
+ dev_err(info->dev, "%s: no fuel gauge or charger name\n",
+ __func__);
+ goto err_kfree;
+ }
+ info->fuel_gauge_name = pdata->fuel_gauge_name;
+ info->charger_name = pdata->charger_name;
+ info->sub_charger_name = pdata->sub_charger_name;
+#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00)
+ if (system_rev <= HWREV_FOR_BATTERY) {
+#else
+ if (system_rev >= HWREV_FOR_BATTERY) {
+#endif
+ dev_info(&pdev->dev, "%s: use sub-charger (%s)\n",
+ __func__, info->sub_charger_name);
+ info->adc_arr_size = pdata->adc_sub_arr_size;
+ info->adc_table = pdata->adc_sub_table;
+ info->adc_channel = pdata->adc_sub_channel;
+ info->use_sub_charger = true;
+ } else {
+ dev_info(&pdev->dev, "%s: use main charger (%s)\n",
+ __func__, info->charger_name);
+ info->adc_arr_size = pdata->adc_arr_size;
+ info->adc_table = pdata->adc_table;
+ info->adc_channel = pdata->adc_channel;
+ info->use_sub_charger = false;
+ }
+ info->get_lpcharging_state = pdata->get_lpcharging_state;
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ info->vf_adc_channel = pdata->adc_vf_channel;
+#endif
+
+ info->psy_bat.name = "battery",
+ info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ info->psy_bat.properties = sec_battery_props,
+ info->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props),
+ info->psy_bat.get_property = sec_bat_get_property,
+ info->psy_bat.set_property = sec_bat_set_property,
+ info->psy_usb.name = "usb",
+ info->psy_usb.type = POWER_SUPPLY_TYPE_USB,
+ info->psy_usb.supplied_to = supply_list,
+ info->psy_usb.num_supplicants = ARRAY_SIZE(supply_list),
+ info->psy_usb.properties = sec_power_props,
+ info->psy_usb.num_properties = ARRAY_SIZE(sec_power_props),
+ info->psy_usb.get_property = sec_usb_get_property,
+ info->psy_ac.name = "ac",
+ info->psy_ac.type = POWER_SUPPLY_TYPE_MAINS,
+ info->psy_ac.supplied_to = supply_list,
+ info->psy_ac.num_supplicants = ARRAY_SIZE(supply_list),
+ info->psy_ac.properties = sec_power_props,
+ info->psy_ac.num_properties = ARRAY_SIZE(sec_power_props),
+ info->psy_ac.get_property = sec_ac_get_property;
+
+ wake_lock_init(&info->vbus_wake_lock, WAKE_LOCK_SUSPEND,
+ "vbus_present");
+ wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND,
+ "sec-battery-monitor");
+ wake_lock_init(&info->cable_wake_lock, WAKE_LOCK_SUSPEND,
+ "sec-battery-cable");
+
+ psy = get_power_supply_by_name(info->charger_name);
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+ info->present = value.intval;
+
+ if (info->present == BAT_NOT_DETECTED)
+ info->batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+#if defined(CONFIG_MACH_Q1_BD)
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+#else
+ info->charging_status = sec_bat_is_charging(info);
+ if (info->charging_status < 0)
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+#endif
+
+ info->present_count = 0;
+ info->charging_int_full_count = 0;
+ info->charging_adc_full_count = 0;
+#ifdef CONFIG_TARGET_LOCALE_NA
+ info->recharging_int_threshold_count = 0;
+#endif
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ info->batt_temp_recover_cnt = 0;
+
+ info->initial_check_count = INIT_CHECK_COUNT;
+
+ mutex_init(&info->adclock);
+
+ info->padc = s3c_adc_register(pdev, NULL, NULL, 0);
+ info->charging_start_time = 0;
+
+#if defined(CONFIG_TARGET_LOCALE_NAATT)
+ info->batt_vf_adc = 0;
+ info->batt_event_status = 0;
+ info->event_end_time = 0xFFFFFFFF;
+#elif defined(CONFIG_TARGET_LOCALE_NA)
+ info->use_call = 0;
+ info->use_video = 0;
+ info->use_music = 0;
+ info->use_browser = 0;
+ info->use_hotspot = 0;
+ info->use_camera = 0;
+ info->use_data_call = 0;
+ info->use_wimax = 0;
+ info->batt_event_status = 0;
+ info->event_expired_time = 0xFFFFFFFF;
+#endif
+ if (info->get_lpcharging_state) {
+ if (info->get_lpcharging_state())
+ info->polling_interval = POLLING_INTERVAL / 4;
+ else
+ info->polling_interval = POLLING_INTERVAL;
+ }
+
+ info->test_value = 0;
+
+ info->batt_tmu_status = TMU_STATUS_NORMAL;
+
+ /* init power supplier framework */
+ ret = power_supply_register(&pdev->dev, &info->psy_bat);
+ if (ret) {
+ dev_err(info->dev, "%s: failed to register psy_bat\n",
+ __func__);
+ goto err_wake_lock;
+ }
+
+ ret = power_supply_register(&pdev->dev, &info->psy_usb);
+ if (ret) {
+ dev_err(info->dev, "%s: failed to register psy_usb\n",
+ __func__);
+ goto err_supply_unreg_bat;
+ }
+
+ ret = power_supply_register(&pdev->dev, &info->psy_ac);
+ if (ret) {
+ dev_err(info->dev, "%s: failed to register psy_ac\n", __func__);
+ goto err_supply_unreg_usb;
+ }
+
+ /* create sec detail attributes */
+ sec_bat_create_attrs(info->psy_bat.dev);
+
+ info->entry = create_proc_entry("batt_info_proc", S_IRUGO, NULL);
+ if (!info->entry)
+ dev_err(info->dev, "%s: failed to create proc_entry\n",
+ __func__);
+ else {
+ info->entry->read_proc = sec_bat_read_proc;
+ info->entry->data = (struct sec_bat_info *)info;
+ }
+
+ info->monitor_wqueue = create_freezable_workqueue(dev_name(&pdev->dev));
+ if (!info->monitor_wqueue) {
+ dev_err(info->dev, "%s: fail to create workqueue\n", __func__);
+ goto err_supply_unreg_ac;
+ }
+
+ INIT_WORK(&info->monitor_work, sec_bat_monitor_work);
+ INIT_WORK(&info->cable_work, sec_bat_cable_work);
+
+ INIT_DELAYED_WORK_DEFERRABLE(&info->polling_work, sec_bat_polling_work);
+ schedule_delayed_work(&info->polling_work, 0);
+
+#ifdef CONFIG_TARGET_LOCALE_NA
+ pchg = info; /* using pointer - for wimax */
+#endif
+
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+ INIT_DELAYED_WORK_DEFERRABLE(&info->vf_check_work,
+ sec_bat_vf_check_work);
+ schedule_delayed_work(&info->vf_check_work, 0);
+#endif
+
+#if defined(CONFIG_MACH_Q1_BD)
+ if (pdata->initial_check)
+ pdata->initial_check();
+#endif
+
+ dev_info(info->dev, "%s: SEC Battery Driver Loaded\n", __func__);
+ return 0;
+
+ err_supply_unreg_ac:
+ power_supply_unregister(&info->psy_ac);
+ err_supply_unreg_usb:
+ power_supply_unregister(&info->psy_usb);
+ err_supply_unreg_bat:
+ power_supply_unregister(&info->psy_bat);
+ err_wake_lock:
+ wake_lock_destroy(&info->vbus_wake_lock);
+ wake_lock_destroy(&info->monitor_wake_lock);
+ wake_lock_destroy(&info->cable_wake_lock);
+ s3c_adc_release(info->padc);
+ mutex_destroy(&info->adclock);
+ err_kfree:
+ kfree(info);
+
+ return ret;
+}
+
+static int __devexit sec_bat_remove(struct platform_device *pdev)
+{
+ struct sec_bat_info *info = platform_get_drvdata(pdev);
+
+ remove_proc_entry("batt_info_proc", NULL);
+
+ flush_workqueue(info->monitor_wqueue);
+ destroy_workqueue(info->monitor_wqueue);
+
+ cancel_delayed_work(&info->polling_work);
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+ cancel_delayed_work(&info->vf_check_work);
+#endif
+
+ power_supply_unregister(&info->psy_bat);
+ power_supply_unregister(&info->psy_usb);
+ power_supply_unregister(&info->psy_ac);
+
+ wake_lock_destroy(&info->vbus_wake_lock);
+ wake_lock_destroy(&info->monitor_wake_lock);
+ wake_lock_destroy(&info->cable_wake_lock);
+ mutex_destroy(&info->adclock);
+
+ s3c_adc_release(info->padc);
+
+ kfree(info);
+
+ return 0;
+}
+
+static int sec_bat_suspend(struct device *dev)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev);
+
+ cancel_work_sync(&info->monitor_work);
+ cancel_delayed_work(&info->polling_work);
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+ cancel_delayed_work(&info->vf_check_work);
+#endif
+
+ return 0;
+}
+
+static int sec_bat_resume(struct device *dev)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev);
+
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ schedule_delayed_work(&info->polling_work,
+ msecs_to_jiffies(info->polling_interval));
+
+#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK
+ schedule_delayed_work(&info->vf_check_work,
+ msecs_to_jiffies(VF_CHECK_INTERVAL));
+#endif
+
+ return 0;
+}
+
+#if defined(CONFIG_TARGET_LOCALE_NA)
+static void sec_bat_shutdown(struct device *dev)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev);
+ struct power_supply *psy_main, *psy_sub;
+ union power_supply_propval val_type;
+ int ret;
+
+ if (!info->use_sub_charger)
+ return;
+
+ psy_main = get_power_supply_by_name(info->charger_name);
+ psy_sub = get_power_supply_by_name(info->sub_charger_name);
+
+ if (!psy_main || !psy_sub) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return;
+ }
+
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ ret =
+ psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret =
+ psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return;
+ }
+}
+#elif defined(CONFIG_MACH_Q1_BD)
+static void sec_bat_shutdown(struct device *dev)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev);
+#if defined(CONFIG_SMB328_CHARGER)
+ struct power_supply *psy_sub =
+ get_power_supply_by_name("smb328-charger");
+ union power_supply_propval value;
+ int ret;
+
+ if (!psy_sub) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return;
+ }
+
+ /* only for OTG */
+ value.intval = false;
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &value);
+ if (ret) {
+ dev_info(info->dev, "%s: fail to set OTG (%d)\n",
+ __func__, ret);
+ return;
+ }
+#endif
+}
+#else
+#define sec_bat_shutdown NULL
+#endif
+
+static const struct dev_pm_ops sec_bat_pm_ops = {
+ .suspend = sec_bat_suspend,
+ .resume = sec_bat_resume,
+};
+
+static struct platform_driver sec_bat_driver = {
+ .driver = {
+ .name = "sec-battery",
+ .owner = THIS_MODULE,
+ .pm = &sec_bat_pm_ops,
+ .shutdown = sec_bat_shutdown,
+ },
+ .probe = sec_bat_probe,
+ .remove = __devexit_p(sec_bat_remove),
+};
+
+static int __init sec_bat_init(void)
+{
+ return platform_driver_register(&sec_bat_driver);
+}
+
+static void __exit sec_bat_exit(void)
+{
+ platform_driver_unregister(&sec_bat_driver);
+}
+
+late_initcall(sec_bat_init);
+module_exit(sec_bat_exit);
+
+MODULE_DESCRIPTION("SEC battery driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_AUTHOR("<joshua.chang@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sec_battery_u1_kor.c b/drivers/power/sec_battery_u1_kor.c
new file mode 100644
index 0000000..147072e
--- /dev/null
+++ b/drivers/power/sec_battery_u1_kor.c
@@ -0,0 +1,2299 @@
+/*
+ * sec_battery.c
+ * Samsung Mobile Battery Driver
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ *
+ * <ms925.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <linux/proc_fs.h>
+#include <linux/android_alarm.h>
+#include <plat/adc.h>
+#include <linux/power/sec_battery_u1.h>
+
+#define POLLING_INTERVAL (40 * 1000)
+#define MEASURE_DSG_INTERVAL (20 * 1000)
+#define MEASURE_CHG_INTERVAL (3 * 1000)
+#define FULL_CHARGING_TIME (6 * 60 * 60 * HZ) /* 6hr */
+#define RECHARGING_TIME (2 * 60 * 60 * HZ) /* 2hr */
+#define RESETTING_CHG_TIME (10 * 60 * HZ) /* 10Min */
+#define RECHARGING_VOLTAGE (4130 * 1000) /* 4.13 V */
+#if defined(CONFIG_MACH_U1_KOR_LGT)
+#define HIGH_BLOCK_TEMP_ADC 395
+#define HIGH_RECOVER_TEMP_ADC 345
+#define LOW_BLOCK_TEMP_ADC 253
+#define LOW_RECOVER_TEMP_ADC 258
+#else
+#define HIGH_BLOCK_TEMP_ADC 390
+#define HIGH_RECOVER_TEMP_ADC 348
+#define LOW_BLOCK_TEMP_ADC 247
+#define LOW_RECOVER_TEMP_ADC 256
+#endif
+
+#define FG_T_SOC 0
+#define FG_T_VCELL 1
+#define FG_T_TEMPER 2
+#define FG_T_PSOC 3
+#define FG_T_VFOCV 4
+#define FG_T_AVGVCELL 5
+
+#define ADC_SAMPLING_CNT 6
+#define ADC_CH_CHGCURRENT_MAIN 0
+#define ADC_CH_CHGCURRENT_SUB 1
+#define ADC_CH_TEMPERATURE_MAIN 7
+#define ADC_CH_TEMPERATURE_SUB 6 /* 6 is near AP side */
+/* #define ADC_TOTAL_COUNT 5 */
+
+#define CURRENT_OF_FULL_CHG_MAIN 300
+#define CURRENT_OF_FULL_CHG_SUB 500
+/* all count duration = (count - 1) * poll interval */
+#define RE_CHG_COND_COUNT 4
+#define RE_CHG_MIN_COUNT 2
+#define TEMP_BLOCK_COUNT 3
+#define BAT_DET_COUNT 2
+#define FULL_CHG_COND_COUNT 2
+#define OVP_COND_COUNT 2
+#define USB_FULL_COND_COUNT 3
+#define USB_FULL_COND_VOLTAGE 4180000
+#define FULL_CHARGE_COND_VOLTAGE 4100000
+#define INIT_CHECK_COUNT 4
+#define CALL_EXCEPTION_VOLTAGE_UP 3400000
+#define CALL_EXCEPTION_VOLTAGE_DN 3200000
+
+/* for backup
+extern int get_proximity_activation_state(void);
+extern int get_proximity_approach_state(void);
+*/
+
+enum tmu_status_t {
+ TMU_STATUS_NORMAL = 0,
+ TMU_STATUS_TRIPPED,
+ TMU_STATUS_THROTTLED,
+ TMU_STATUS_WARNING,
+};
+
+enum cable_type_t {
+ CABLE_TYPE_NONE = 0,
+ CABLE_TYPE_USB,
+ CABLE_TYPE_AC,
+ CABLE_TYPE_MISC,
+};
+
+enum batt_full_t {
+ BATT_NOT_FULL = 0,
+ BATT_FULL,
+};
+
+enum {
+ BAT_NOT_DETECTED,
+ BAT_DETECTED
+};
+
+static ssize_t sec_bat_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static ssize_t sec_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+struct battest_info {
+ int rechg_count;
+ int full_count;
+ int full_count_sub;
+ int test_value;
+ int test_esuspend;
+ bool is_rechg_state;
+};
+
+/*
+struct adc_sample {
+ int average_adc;
+ int adc_arr[ADC_TOTAL_COUNT];
+ int index;
+};
+*/
+
+struct sec_bat_info {
+ struct device *dev;
+
+ char *fuel_gauge_name;
+ char *charger_name;
+ char *sub_charger_name;
+
+ unsigned int adc_arr_size;
+ struct sec_bat_adc_table_data *adc_table;
+ /*
+ unsigned int adc_channel;
+ struct adc_sample temper_adc_sample;
+ */
+
+ struct power_supply psy_bat;
+ struct power_supply psy_usb;
+ struct power_supply psy_ac;
+
+ struct wake_lock vbus_wake_lock;
+ struct wake_lock monitor_wake_lock;
+ struct wake_lock cable_wake_lock;
+ struct wake_lock test_wake_lock;
+ struct wake_lock measure_wake_lock;
+
+ enum cable_type_t cable_type;
+ enum batt_full_t batt_full_status;
+
+ unsigned int batt_temp; /* Battery Temperature (C) */
+ int batt_temp_high_cnt;
+ int batt_temp_low_cnt;
+ unsigned int batt_health;
+ unsigned int batt_vcell;
+ unsigned int batt_vfocv;
+ unsigned int batt_soc;
+ unsigned int batt_raw_soc;
+ unsigned int batt_presoc;
+ unsigned int polling_interval;
+ unsigned int measure_interval;
+ int charging_status;
+ /* int charging_full_count; */
+
+ unsigned int batt_temp_adc;
+ unsigned int batt_temp_adc_sub;
+ unsigned int batt_temp_radc;
+ unsigned int batt_temp_radc_sub;
+ unsigned int batt_current_adc;
+ unsigned int present;
+ struct battest_info test_info;
+
+ struct s3c_adc_client *padc;
+
+ struct workqueue_struct *monitor_wqueue;
+ struct work_struct monitor_work;
+ struct delayed_work cable_work;
+ struct delayed_work polling_work;
+ struct delayed_work measure_work;
+
+ unsigned long charging_start_time;
+ unsigned long charging_passed_time;
+ unsigned long next_check_time;
+ unsigned int recharging_status;
+ unsigned int batt_lpm_state;
+ unsigned int sub_chg_status;
+ unsigned int voice_call_state;
+ unsigned int is_call_except;
+ unsigned int charging_set_current;
+
+ struct mutex adclock;
+
+ unsigned int (*get_lpcharging_state) (void);
+ bool charging_enabled;
+ bool use_sub_charger;
+ bool sub_chg_ovp;
+ int initial_check_count;
+ struct proc_dir_entry *entry;
+
+ int batt_tmu_status;
+};
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static enum power_supply_property sec_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+};
+
+static enum power_supply_property sec_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+struct power_supply *get_power_supply_by_name(char *name)
+{
+ if (!name)
+ return (struct power_supply *)NULL;
+ else
+ return power_supply_get_by_name(name);
+}
+
+#if 0
+static int calculate_average_adc(struct sec_bat_info *info,
+ struct adc_sample *sample, int adc)
+{
+ int i, total_adc = 0;
+ int average_adc = sample->average_adc;
+ int index = sample->index;
+
+ if (adc < 0 || adc == 0) {
+ dev_err(info->dev, "%s: invalid adc : %d\n", __func__, adc);
+ return 0;
+ }
+
+ if (!average_adc) {
+ average_adc = adc;
+ for (i = 0; i < ADC_TOTAL_COUNT; i++)
+ sample->adc_arr[i] = adc;
+ } else {
+ sample->index = ++index >= ADC_TOTAL_COUNT ? 0 : index;
+ sample->adc_arr[sample->index] = adc;
+ for (i = 0; i < ADC_TOTAL_COUNT; i++)
+ total_adc += sample->adc_arr[i];
+
+ average_adc = total_adc / ADC_TOTAL_COUNT;
+ }
+
+ sample->average_adc = average_adc;
+ dev_dbg(info->dev, "%s: i(%d) adc=%d, avg_adc=%d\n", __func__,
+ sample->index, adc, average_adc);
+
+ return average_adc;
+}
+#endif
+
+static int sec_bat_check_vf(struct sec_bat_info *info)
+{
+ int health = info->batt_health;
+
+ if (info->present == 0) {
+ if (info->test_info.test_value == 999) {
+ pr_info("test case : %d\n", info->test_info.test_value);
+ health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ } else
+ health = POWER_SUPPLY_HEALTH_DEAD;
+ } else {
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ /* update health */
+ if (health != info->batt_health) {
+ if (health == POWER_SUPPLY_HEALTH_UNKNOWN ||
+ health == POWER_SUPPLY_HEALTH_DEAD){
+ info->batt_health = health;
+ pr_info("vf error update\n");
+ } else if (info->batt_health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ info->batt_health != POWER_SUPPLY_HEALTH_COLD &&
+ health == POWER_SUPPLY_HEALTH_GOOD) {
+ info->batt_health = health;
+ pr_info("recovery form vf error\n");
+ }
+ }
+
+ return 0;
+}
+
+static int sec_bat_check_detbat(struct sec_bat_info *info)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+ static int cnt;
+ int vf_state = BAT_DETECTED;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+
+ if ((info->cable_type != CABLE_TYPE_NONE) &&
+ (value.intval == BAT_NOT_DETECTED)) {
+ if (cnt <= BAT_DET_COUNT)
+ cnt++;
+ if (cnt >= BAT_DET_COUNT)
+ vf_state = BAT_NOT_DETECTED;
+ else
+ vf_state = BAT_DETECTED;
+ } else {
+ vf_state = BAT_DETECTED;
+ cnt = 0;
+ }
+
+ if (info->present == 1 &&
+ vf_state == BAT_NOT_DETECTED) {
+ pr_info("detbat state(->%d) changed\n", vf_state);
+ info->present = 0;
+ cancel_work_sync(&info->monitor_work);
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+ } else if (info->present == 0 &&
+ vf_state == BAT_DETECTED) {
+ pr_info("detbat state(->%d) changed\n", vf_state);
+ info->present = 1;
+ cancel_work_sync(&info->monitor_work);
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+ }
+
+ return value.intval;
+}
+
+static int sec_bat_get_fuelgauge_data(struct sec_bat_info *info, int type)
+{
+ struct power_supply *psy
+ = get_power_supply_by_name(info->fuel_gauge_name);
+ union power_supply_propval value;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get fuel gauge ps\n", __func__);
+ return -ENODEV;
+ }
+
+ switch (type) {
+ case FG_T_VCELL:
+ value.intval = 0; /*vcell */
+ psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value);
+ break;
+ case FG_T_VFOCV:
+ value.intval = 1; /*vfocv */
+ psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value);
+ break;
+ case FG_T_AVGVCELL:
+ psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_AVG, &value);
+ break;
+ case FG_T_SOC:
+ value.intval = 0; /*normal soc */
+ psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value);
+ break;
+ case FG_T_PSOC:
+ value.intval = 1; /*raw soc */
+ psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value);
+ break;
+ case FG_T_TEMPER:
+ psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return value.intval;
+}
+
+static int sec_bat_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (info->test_info.test_value == 999) {
+ pr_info("batt test case : %d\n",
+ info->test_info.test_value);
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ } else
+ val->intval = info->charging_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = info->batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = info->present;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = info->batt_temp;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ /* val->intval = 1; */
+ val->intval = info->cable_type;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = info->batt_vcell;
+ if (val->intval == -1)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (info->charging_status == POWER_SUPPLY_STATUS_FULL) {
+ val->intval = 100;
+ break;
+ }
+ val->intval = info->batt_soc;
+ if (val->intval == -1)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sec_bat_handle_sub_charger_topoff(struct sec_bat_info *info)
+{
+ struct power_supply *psy_sub =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval value;
+ int ret = 0;
+
+ if (!psy_sub) {
+ dev_err(info->dev, "%s: fail to get sub charger ps\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ if (info->batt_full_status == BATT_NOT_FULL) {
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+ info->batt_full_status = BATT_FULL;
+ info->recharging_status = false;
+ info->charging_passed_time = 0;
+ info->charging_start_time = 0;
+ /* disable charging */
+ value.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS,
+ &value);
+ info->charging_enabled = false;
+ }
+ return ret;
+}
+
+static int sec_bat_set_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_bat);
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ dev_info(info->dev, "%s: topoff intr\n", __func__);
+ if (val->intval != POWER_SUPPLY_STATUS_FULL)
+ return -EINVAL;
+
+ if (info->use_sub_charger) {
+ if (info->cable_type == CABLE_TYPE_USB ||
+ info->cable_type == CABLE_TYPE_MISC)
+ sec_bat_handle_sub_charger_topoff(info);
+ break;
+ }
+
+ if (info->batt_full_status == BATT_NOT_FULL) {
+ info->recharging_status = false;
+ info->batt_full_status = BATT_FULL;
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+ /* disable charging */
+ value.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ psy->set_property(psy, POWER_SUPPLY_PROP_STATUS,
+ &value);
+ info->charging_enabled = false;
+ }
+#if 0 /* for reference */
+ if (info->batt_full_status == BATT_NOT_FULL) {
+ info->batt_full_status = BATT_1ST_FULL;
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+ /* TODO: set topoff current 60mA */
+ value.intval = 120;
+ psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL,
+ &value);
+ } else {
+ info->batt_full_status = BATT_2ND_FULL;
+ info->recharging_status = false;
+ /* disable charging */
+ value.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ psy->set_property(psy, POWER_SUPPLY_PROP_STATUS,
+ &value);
+ }
+#endif
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ dev_info(info->dev, "%s: refresh battery data\n", __func__);
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ /* TODO: lowbatt interrupt: called by fuel gauge */
+ dev_info(info->dev, "%s: lowbatt intr\n", __func__);
+ if (val->intval != POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL)
+ return -EINVAL;
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* cable is attached or detached. called by USB switch(MUIC) */
+ dev_info(info->dev, "%s: cable was changed(%d)\n", __func__,
+ val->intval);
+ switch (val->intval) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ info->cable_type = CABLE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ info->cable_type = CABLE_TYPE_AC;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ info->cable_type = CABLE_TYPE_USB;
+ break;
+ case POWER_SUPPLY_TYPE_MISC:
+ info->cable_type = CABLE_TYPE_MISC;
+ break;
+ default:
+ return -EINVAL;
+ }
+ wake_lock(&info->cable_wake_lock);
+ /* TODO : fix delay time */
+ queue_delayed_work(info->monitor_wqueue, &info->cable_work, 0);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ /* call by TMU driver
+ * alarm for abnormal temperature increasement
+ */
+ info->batt_tmu_status = val->intval;
+
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ dev_info(info->dev, "%s: TMU status has been changed(%d)\n",
+ __func__, info->batt_tmu_status);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sec_usb_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_usb);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ /* Set enable=1 only if the USB charger is connected */
+ val->intval = (info->cable_type == CABLE_TYPE_USB);
+
+ return 0;
+}
+
+static int sec_ac_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sec_bat_info *info = container_of(ps, struct sec_bat_info,
+ psy_ac);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ /* Set enable=1 only if the AC charger is connected */
+ val->intval = (info->cable_type == CABLE_TYPE_AC) ||
+ (info->cable_type == CABLE_TYPE_MISC);
+
+ return 0;
+}
+
+static int sec_bat_get_adc_data(struct sec_bat_info *info, int adc_ch)
+{
+ int adc_data;
+ int adc_max = 0;
+ int adc_min = 0;
+ int adc_total = 0;
+ int i;
+ int err_value;
+
+ for (i = 0; i < ADC_SAMPLING_CNT; i++) {
+ adc_data = s3c_adc_read(info->padc, adc_ch);
+
+ if (adc_data < 0) {
+ pr_err("err(%d) returned, skip adc read\n", adc_data);
+ err_value = adc_data;
+ goto err;
+ }
+
+ if (i != 0) {
+ if (adc_data > adc_max)
+ adc_max = adc_data;
+ else if (adc_data < adc_min)
+ adc_min = adc_data;
+ } else {
+ adc_max = adc_data;
+ adc_min = adc_data;
+ }
+ adc_total += adc_data;
+ }
+
+ return (adc_total - adc_max - adc_min) / (ADC_SAMPLING_CNT - 2);
+err:
+ return err_value;
+}
+
+static inline int s3c_read_temper_adc(struct sec_bat_info *info)
+{
+ int adc;
+
+ mutex_lock(&info->adclock);
+ adc = sec_bat_get_adc_data(info, ADC_CH_TEMPERATURE_MAIN);
+ mutex_unlock(&info->adclock);
+ if (adc <= 0)
+ adc = info->batt_temp_adc;
+ info->batt_temp_adc = adc;
+
+ return adc;
+}
+
+static inline int s3c_read_temper_adc_sub(struct sec_bat_info *info)
+{
+ int adc;
+ int adc_tmp1 = 0;
+ int adc_tmp2 = 0;
+ unsigned int temp_radc_sub = 0;
+
+ mutex_lock(&info->adclock);
+ adc = sec_bat_get_adc_data(info, ADC_CH_TEMPERATURE_SUB);
+ mutex_unlock(&info->adclock);
+ if (adc <= 0)
+ adc = info->batt_temp_adc_sub;
+ info->batt_temp_adc_sub = adc;
+
+ temp_radc_sub = info->batt_temp_adc_sub;
+ adc_tmp1 = temp_radc_sub * 10;
+ adc_tmp2 = (40950 - adc_tmp1);
+ temp_radc_sub = adc_tmp2 / 100;
+ if ((adc_tmp2 % 10) >= 5)
+ temp_radc_sub += 1;
+
+ info->batt_temp_radc_sub = temp_radc_sub;
+ /* pr_info("[battery] lcd temper : %d, rescale : %d\n",
+ info->batt_temp_adc_sub, temp_radc_sub); */
+
+ return adc;
+}
+
+static unsigned long sec_rescale_temp_adc(struct sec_bat_info *info)
+{
+ int adc_tmp = info->batt_temp_adc;
+ int adc_tmp1 = 0;
+ int adc_tmp2 = 0;
+
+ adc_tmp1 = adc_tmp * 10;
+ adc_tmp2 = (40950 - adc_tmp1);
+ adc_tmp = adc_tmp2 / 100;
+ if ((adc_tmp2 % 10) >= 5)
+ adc_tmp += 1;
+
+ info->batt_temp_radc = adc_tmp;
+
+ /* pr_info("[battery] bat temper : %d, rescale : %d\n",
+ info->batt_temp_adc, adc_tmp); */
+
+ return adc_tmp;
+}
+
+static int sec_bat_check_temper(struct sec_bat_info *info)
+{
+ struct power_supply *psy
+ = get_power_supply_by_name(info->fuel_gauge_name);
+ union power_supply_propval value;
+ int ret;
+ int temp;
+ int low = 0;
+ int high = 0;
+ int mid = 0;
+
+ if (!info->adc_table || !info->adc_arr_size) {
+ /* using fake temp */
+ temp = 300;
+ info->batt_temp = temp;
+ goto skip_tupdate;
+ }
+
+ high = info->adc_arr_size - 1;
+
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if (info->adc_table[mid].adc > info->batt_temp_adc)
+ high = mid - 1;
+ else if (info->adc_table[mid].adc < info->batt_temp_adc)
+ low = mid + 1;
+ else
+ break;
+ }
+ temp = info->adc_table[mid].temperature;
+ /* pr_info("%s : temperature form adc table : %d\n", __func__, temp); */
+ info->batt_temp = temp;
+
+skip_tupdate:
+ /* Set temperature to fuel gauge */
+ if (info->fuel_gauge_name) {
+ value.intval = info->batt_temp / 10;
+ /* value.intval = temp / 10; */
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set temperature(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int sec_bat_check_temper_adc(struct sec_bat_info *info)
+{
+ int temp_adc = s3c_read_temper_adc(info);
+ int rescale_adc = 0;
+ int health = info->batt_health;
+
+ rescale_adc = sec_rescale_temp_adc(info);
+
+ if (info->test_info.test_value == 1) {
+ pr_info("test case : %d\n", info->test_info.test_value);
+ rescale_adc = HIGH_BLOCK_TEMP_ADC + 1;
+ if (info->cable_type == CABLE_TYPE_NONE)
+ rescale_adc = HIGH_RECOVER_TEMP_ADC - 1;
+ info->batt_temp_radc = rescale_adc;
+ }
+
+ if (info->cable_type == CABLE_TYPE_NONE ||
+ info->test_info.test_value == 999) {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ goto skip_hupdate;
+ }
+
+ if (rescale_adc >= HIGH_BLOCK_TEMP_ADC) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT)
+ if (info->batt_temp_high_cnt <= TEMP_BLOCK_COUNT)
+ info->batt_temp_high_cnt++;
+ } else if (rescale_adc <= HIGH_RECOVER_TEMP_ADC &&
+ rescale_adc >= LOW_RECOVER_TEMP_ADC) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ health == POWER_SUPPLY_HEALTH_COLD) {
+ info->batt_temp_high_cnt = 0;
+ info->batt_temp_low_cnt = 0;
+ }
+ } else if (rescale_adc <= LOW_BLOCK_TEMP_ADC) {
+ if (health != POWER_SUPPLY_HEALTH_COLD)
+ if (info->batt_temp_low_cnt <= TEMP_BLOCK_COUNT)
+ info->batt_temp_low_cnt++;
+ }
+
+ if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT)
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT)
+ health = POWER_SUPPLY_HEALTH_COLD;
+ else
+ health = POWER_SUPPLY_HEALTH_GOOD;
+
+skip_hupdate:
+ if (info->batt_health != POWER_SUPPLY_HEALTH_UNKNOWN &&
+ info->batt_health != POWER_SUPPLY_HEALTH_DEAD &&
+ health != info->batt_health) {
+ info->batt_health = health;
+ cancel_work_sync(&info->monitor_work);
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+ }
+
+ return 0;
+}
+
+
+
+static void check_chgcurrent_sub(struct sec_bat_info *info)
+{
+ int chg_current_adc = 0;
+
+ mutex_lock(&info->adclock);
+ chg_current_adc = sec_bat_get_adc_data(info, ADC_CH_CHGCURRENT_SUB);
+ mutex_unlock(&info->adclock);
+ if (chg_current_adc < 0)
+ chg_current_adc = info->batt_current_adc;
+ info->batt_current_adc = chg_current_adc;
+
+ dev_dbg(info->dev,
+ "[battery] charging current = %d\n", info->batt_current_adc);
+}
+
+/* only for sub-charger */
+static void sec_check_chgcurrent(struct sec_bat_info *info)
+{
+ static int cnt;
+
+ if (info->charging_enabled && info->cable_type == CABLE_TYPE_AC) {
+ check_chgcurrent_sub(info);
+ /* AGAIN_FEATURE */
+ if (info->batt_current_adc <= CURRENT_OF_FULL_CHG_SUB)
+ check_chgcurrent_sub(info);
+
+ /* if (info->test_info.test_value == 2)
+ {
+ pr_info("batt test case : %d\n",
+ info->test_info.test_value);
+ info->batt_current_adc = CURRENT_OF_FULL_CHG_SUB - 1;
+ cnt = FULL_CHG_COND_COUNT;
+ } else */
+ if (info->test_info.test_value == 3) {
+ pr_info("batt test case : %d\n",
+ info->test_info.test_value);
+ info->batt_current_adc = CURRENT_OF_FULL_CHG_SUB + 1;
+ cnt = 0;
+ }
+
+ if (info->batt_vcell >= FULL_CHARGE_COND_VOLTAGE) {
+ if (info->batt_current_adc <= CURRENT_OF_FULL_CHG_SUB) {
+ cnt++;
+ pr_info("full state? %d, %d\n",
+ info->batt_current_adc, cnt);
+ if (cnt >= FULL_CHG_COND_COUNT) {
+ pr_info("full state!! %d/%d\n",
+ cnt, FULL_CHG_COND_COUNT);
+ sec_bat_handle_sub_charger_topoff(info);
+ cnt = 0;
+ }
+ }
+ } else
+ cnt = 0;
+ } else {
+ cnt = 0;
+ info->batt_current_adc = 0;
+ }
+ info->test_info.full_count = cnt;
+}
+
+
+static int sec_check_recharging(struct sec_bat_info *info)
+{
+ static int cnt;
+
+ if (info->batt_vcell > RECHARGING_VOLTAGE) {
+ cnt = 0;
+ return 0;
+ } else {
+ /* AGAIN_FEATURE */
+ info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL);
+ if (info->batt_vcell <= RECHARGING_VOLTAGE) {
+ cnt++;
+ if (cnt >= RE_CHG_COND_COUNT) {
+ cnt = 0;
+ info->test_info.is_rechg_state = true;
+ return 1;
+ } else if (cnt >= RE_CHG_MIN_COUNT &&
+ info->batt_vcell <= FULL_CHARGE_COND_VOLTAGE) {
+ cnt = 0;
+ info->test_info.is_rechg_state = true;
+ return 1;
+ } else
+ return 0;
+ } else {
+ cnt = 0;
+ return 0;
+ }
+ }
+ info->test_info.rechg_count = cnt;
+}
+
+static void sec_bat_update_info(struct sec_bat_info *info)
+{
+ info->batt_presoc = info->batt_soc;
+ info->batt_raw_soc = sec_bat_get_fuelgauge_data(info, FG_T_PSOC);
+ info->batt_soc = sec_bat_get_fuelgauge_data(info, FG_T_SOC);
+ info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL);
+ info->batt_vfocv = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV);
+ /* info->batt_temp = sec_bat_get_fuelgauge_data(info, FG_T_TEMPER); */
+ /* pr_info("temperature form FG : %d\n", info->batt_temp); */
+}
+
+static int sec_bat_enable_charging_main(struct sec_bat_info *info, bool enable)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval val_type, val_chg_current, val_topoff;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ info->batt_full_status = BATT_NOT_FULL;
+
+ if (enable) { /* Enable charging */
+ switch (info->cable_type) {
+ case CABLE_TYPE_USB:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ case CABLE_TYPE_AC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 650; /* mA */
+ break;
+ case CABLE_TYPE_MISC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ default:
+ dev_err(info->dev, "%s: Invalid func use\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Set charging current */
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Set topoff current */
+ /* mA */
+ val_topoff.intval = 200;
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL,
+ &val_topoff);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set topoff cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ info->charging_start_time = jiffies;
+ } else { /* Disable charging */
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ info->charging_passed_time = 0;
+ info->charging_start_time = 0;
+ }
+
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ info->charging_enabled = enable;
+
+ return 0;
+}
+
+static int sec_bat_enable_charging_sub(struct sec_bat_info *info, bool enable)
+{
+ struct power_supply *psy_main =
+ get_power_supply_by_name(info->charger_name);
+ struct power_supply *psy_sub =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval val_type, val_chg_current;
+ int ret;
+
+ if (!psy_main || !psy_sub) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ info->batt_full_status = BATT_NOT_FULL;
+
+ if (enable) { /* Enable charging */
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging"
+ " status-main(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ switch (info->cable_type) {
+ case CABLE_TYPE_USB:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ case CABLE_TYPE_AC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 650; /* mA */
+ break;
+ case CABLE_TYPE_MISC:
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+ break;
+ default:
+ dev_err(info->dev, "%s: Invalid func use\n", __func__);
+ return -EINVAL;
+ }
+
+ info->charging_set_current = val_chg_current.intval;
+
+ /* Set charging current */
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ info->charging_start_time = jiffies;
+ info->next_check_time =
+ info->charging_start_time + RESETTING_CHG_TIME;
+ } else { /* Disable charging */
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging"
+ " status-main(%d)\n", __func__, ret);
+ return ret;
+ }
+ info->next_check_time = 0;
+ info->charging_passed_time = 0;
+ info->charging_set_current = 0;
+ info->charging_start_time = 0;
+ }
+
+ ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ info->charging_enabled = enable;
+
+ return 0;
+}
+
+static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable)
+{
+ if (info->use_sub_charger)
+ return sec_bat_enable_charging_sub(info, enable);
+ else
+ return sec_bat_enable_charging_main(info, enable);
+}
+
+static void sec_bat_cable_work(struct work_struct *work)
+{
+ struct sec_bat_info *info = container_of(work, struct sec_bat_info,
+ cable_work.work);
+
+ switch (info->cable_type) {
+ case CABLE_TYPE_NONE:
+ /* TODO : check DCIN state again*/
+ info->sub_chg_ovp = false;
+ info->batt_full_status = BATT_NOT_FULL;
+ info->recharging_status = false;
+ info->test_info.is_rechg_state = false;
+ info->charging_start_time = 0;
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ sec_bat_enable_charging(info, false);
+ wake_lock_timeout(&info->vbus_wake_lock, HZ * 5);
+ cancel_delayed_work(&info->measure_work);
+ info->measure_interval = MEASURE_DSG_INTERVAL;
+ wake_lock(&info->measure_wake_lock);
+ queue_delayed_work(info->monitor_wqueue,
+ &info->measure_work, 0);
+ /* schedule_delayed_work(&info->measure_work, 0); */
+ break;
+ case CABLE_TYPE_USB:
+ case CABLE_TYPE_AC:
+ case CABLE_TYPE_MISC:
+ /* TODO : check DCIN state again*/
+ info->charging_status = POWER_SUPPLY_STATUS_CHARGING;
+ sec_bat_enable_charging(info, true);
+ wake_lock(&info->vbus_wake_lock);
+ cancel_delayed_work(&info->measure_work);
+ info->measure_interval = MEASURE_CHG_INTERVAL;
+ wake_lock(&info->measure_wake_lock);
+ queue_delayed_work(info->monitor_wqueue,
+ &info->measure_work, 0);
+ /* schedule_delayed_work(&info->measure_work, 0); */
+ break;
+ default:
+ dev_err(info->dev, "%s: Invalid cable type\n", __func__);
+ break;
+ }
+
+ power_supply_changed(&info->psy_ac);
+ power_supply_changed(&info->psy_usb);
+ /* TBD */
+ /*
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+ */
+
+ wake_unlock(&info->cable_wake_lock);
+}
+
+static void sec_bat_charging_time_management(struct sec_bat_info *info)
+{
+ unsigned long charging_time;
+
+ if (info->charging_start_time == 0) {
+ dev_dbg(info->dev, "%s: charging_start_time has never"
+ " been used since initializing\n", __func__);
+ return;
+ }
+
+ if (jiffies >= info->charging_start_time)
+ charging_time = jiffies - info->charging_start_time;
+ else
+ charging_time = 0xFFFFFFFF - info->charging_start_time
+ + jiffies;
+
+ info->charging_passed_time = charging_time;
+
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ if (time_after(charging_time, (unsigned long)RECHARGING_TIME) &&
+ info->recharging_status == true) {
+ sec_bat_enable_charging(info, false);
+ info->recharging_status = false;
+ dev_info(info->dev, "%s: Recharging timer expired\n",
+ __func__);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ if (time_after(charging_time,
+ (unsigned long)FULL_CHARGING_TIME)) {
+ sec_bat_enable_charging(info, false);
+ info->charging_status = POWER_SUPPLY_STATUS_FULL;
+
+ dev_info(info->dev, "%s: Charging timer expired\n",
+ __func__);
+ }
+ break;
+ default:
+ dev_info(info->dev, "%s: Undefine Battery Status\n", __func__);
+ return;
+ }
+
+ dev_dbg(info->dev, "Time past : %u secs\n",
+ jiffies_to_msecs(charging_time) / 1000);
+
+ return;
+}
+
+
+static void sec_bat_check_ing_level_trigger(struct sec_bat_info *info)
+{
+ struct power_supply *psy_sub =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval value;
+ union power_supply_propval val_type, val_chg_current;
+ static int full_cnt;
+ static int ovp_cnt;
+ int ret;
+
+ ret = psy_sub->get_property(psy_sub, POWER_SUPPLY_PROP_STATUS, &value);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ info->sub_chg_status = value.intval;
+
+ if (info->charging_enabled) {
+ switch (value.intval) {
+ case POWER_SUPPLY_STATUS_DISCHARGING: /* overvoltage */
+ ovp_cnt++;
+ if (ovp_cnt >= OVP_COND_COUNT) {
+ pr_info("over/under voltage detected!! (%d)\n",
+ ovp_cnt);
+ info->sub_chg_ovp = true;
+ info->batt_full_status = BATT_NOT_FULL;
+ info->recharging_status = false;
+ info->test_info.is_rechg_state = false;
+ info->charging_start_time = 0;
+ info->charging_status =
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ sec_bat_enable_charging(info, false);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING: /* full */
+ if (info->cable_type == CABLE_TYPE_USB ||
+ info->cable_type == CABLE_TYPE_MISC) {
+ pr_info("usb full cnt = %d\n", full_cnt);
+ full_cnt++;
+ if (full_cnt >= USB_FULL_COND_COUNT &&
+ info->batt_vcell >=
+ USB_FULL_COND_VOLTAGE) {
+ full_cnt = 0;
+ sec_bat_handle_sub_charger_topoff(info);
+ }
+ } else {
+ full_cnt = 0;
+ }
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING: /* charging */
+ dev_dbg(info->dev, "%s : passed_time(%lu) > next_time(%lu)\n",
+ __func__, info->charging_passed_time,
+ info->next_check_time);
+ if (time_after(info->charging_passed_time,
+ info->next_check_time)) {
+ info->next_check_time =
+ info->charging_passed_time +
+ RESETTING_CHG_TIME;
+ if (info->is_call_except) {
+ pr_info("call exception case, skip resetting\n");
+ full_cnt = 0;
+ ovp_cnt = 0;
+ break;
+ }
+
+ if (info->cable_type == CABLE_TYPE_AC) {
+ val_type.intval =
+ POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 650; /* mA */
+
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ }
+
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ }
+
+ dev_info(info->dev, "%s: reset charging current\n",
+ __func__);
+ }
+ }
+ full_cnt = 0;
+ ovp_cnt = 0;
+ break;
+ default:
+ full_cnt = 0;
+ ovp_cnt = 0;
+ }
+ } else {
+ if (info->charging_status == POWER_SUPPLY_STATUS_DISCHARGING &&
+ info->sub_chg_status ==
+ POWER_SUPPLY_STATUS_NOT_CHARGING &&
+ info->sub_chg_ovp == true) {
+ pr_info("recover from ovp, charge again!!\n");
+ info->sub_chg_ovp = false;
+ info->charging_status = POWER_SUPPLY_STATUS_CHARGING;
+ sec_bat_enable_charging(info, true);
+ }
+ full_cnt = 0;
+ ovp_cnt = 0;
+ }
+}
+
+static int sec_main_charger_disable(struct sec_bat_info *info)
+{
+ struct power_supply *psy_main =
+ get_power_supply_by_name(info->charger_name);
+ union power_supply_propval val_type;
+ int ret;
+
+ val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging"
+ " status-main(%d)\n", __func__, ret);
+ }
+
+ return ret;
+}
+
+static int sec_main_charger_default(struct sec_bat_info *info)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval val_type, val_chg_current;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450; /* mA */
+
+ /* Set charging current */
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+#if 0 /* for backup */
+static void sec_batt_check_call_exception_test(struct sec_bat_info *info)
+{
+ struct power_supply *psy_sub =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval val_type, val_chg_current;
+ int proximity_activation = 0;
+ int proximity_approach = 0;
+ int ret;
+
+ if (info->cable_type != CABLE_TYPE_AC) {
+ info->is_call_except = 0;
+ return;
+ }
+
+ if (!info->charging_enabled) {
+ if (info->is_call_except)
+ info->is_call_except = 0;
+ return;
+ }
+
+ if (!info->is_call_except)
+ info->is_call_except = 1;
+ else
+ info->is_call_except = 0;
+
+ val_type.intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (info->charging_set_current == 650 &&
+ info->is_call_except == 1) {
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450;
+ info->charging_set_current = val_chg_current.intval;
+ pr_info("call exception enable!\n");
+ } else if (info->charging_set_current == 450 &&
+ info->is_call_except == 0) {
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 650;
+ info->charging_set_current = val_chg_current.intval;
+ pr_info("call exception disable!\n");
+ }
+
+ if (val_type.intval == POWER_SUPPLY_STATUS_CHARGING) {
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ }
+
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ }
+
+ dev_info(info->dev, "%s: reset charging current\n",
+ __func__);
+ }
+}
+#endif
+
+#if 0 /* for backup */
+static void sec_batt_check_call_exception(struct sec_bat_info *info)
+{
+ struct power_supply *psy_sub =
+ get_power_supply_by_name(info->sub_charger_name);
+ union power_supply_propval val_type, val_chg_current;
+ int proximity_activation = 0;
+ int proximity_approach = 0;
+ int ret;
+
+ if (info->cable_type != CABLE_TYPE_AC) {
+ info->is_call_except = 0;
+ return;
+ }
+
+ if (!info->charging_enabled) {
+ if (info->is_call_except)
+ info->is_call_except = 0;
+ return;
+ }
+
+ proximity_activation = get_proximity_activation_state();
+ proximity_approach = get_proximity_approach_state();
+
+ if (info->voice_call_state &&
+ proximity_activation &&
+ proximity_approach) {
+ /* info->is_call_except = 1; */
+ info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL);
+ if (info->batt_vcell >= CALL_EXCEPTION_VOLTAGE_DN &&
+ info->batt_vcell < CALL_EXCEPTION_VOLTAGE_UP &&
+ info->is_call_except == 0) {
+ info->is_call_except = 0;
+ } else if (info->batt_vcell < CALL_EXCEPTION_VOLTAGE_DN) {
+ info->is_call_except = 0;
+ } else {
+ info->is_call_except = 1;
+ }
+ } else {
+ info->is_call_except = 0;
+ }
+
+ val_type.intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (info->charging_set_current == 650 &&
+ info->is_call_except == 1) {
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 450;
+ info->charging_set_current = val_chg_current.intval;
+ pr_info("call exception enable! (%d, %d, %d)\n",
+ info->voice_call_state, proximity_activation,
+ proximity_approach);
+ } else if (info->charging_set_current == 450 &&
+ info->is_call_except == 0) {
+ val_type.intval = POWER_SUPPLY_STATUS_CHARGING;
+ val_chg_current.intval = 650;
+ info->charging_set_current = val_chg_current.intval;
+ pr_info("call exception disable! (%d, %d, %d)\n",
+ info->voice_call_state, proximity_activation,
+ proximity_approach);
+ }
+
+ if (val_type.intval == POWER_SUPPLY_STATUS_CHARGING) {
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val_chg_current);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging cur(%d)\n",
+ __func__, ret);
+ }
+
+ ret = psy_sub->set_property(psy_sub,
+ POWER_SUPPLY_PROP_STATUS,
+ &val_type);
+ if (ret) {
+ dev_err(info->dev, "%s: fail to set charging status(%d)\n",
+ __func__, ret);
+ }
+
+ dev_info(info->dev, "%s: reset charging current\n",
+ __func__);
+ }
+}
+#endif
+
+static void sec_bat_monitor_work(struct work_struct *work)
+{
+ struct sec_bat_info *info = container_of(work, struct sec_bat_info,
+ monitor_work);
+
+ wake_lock(&info->monitor_wake_lock);
+
+ sec_bat_charging_time_management(info);
+
+ sec_bat_update_info(info);
+ sec_bat_check_temper(info);
+ sec_bat_check_vf(info);
+
+ if (info->use_sub_charger) {
+ sec_bat_check_ing_level_trigger(info);
+ sec_check_chgcurrent(info);
+
+ /*if (info->sub_chg_status == POWER_SUPPLY_STATUS_CHARGING &&
+ info->test_info.test_value == 2) {
+ pr_info("batt test case : %d\n",
+ info->test_info.test_value);
+ sec_bat_handle_sub_charger_topoff(info);
+ }*/
+ }
+
+ if (info->test_info.test_value == 6)
+ info->batt_tmu_status = TMU_STATUS_WARNING;
+
+ switch (info->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ if (sec_check_recharging(info) &&
+ info->recharging_status == false) {
+ info->recharging_status = true;
+ sec_bat_enable_charging(info, true);
+
+ dev_info(info->dev,
+ "%s: Start Recharging, Vcell = %d\n", __func__,
+ info->batt_vcell);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ if (info->batt_health == POWER_SUPPLY_HEALTH_OVERHEAT
+ || info->batt_health == POWER_SUPPLY_HEALTH_COLD) {
+ sec_bat_enable_charging(info, false);
+ info->charging_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ info->test_info.is_rechg_state = false;
+
+ dev_info(info->dev, "%s: Not charging\n", __func__);
+ } else if (info->batt_health == POWER_SUPPLY_HEALTH_DEAD) {
+ sec_bat_enable_charging(info, false);
+ info->charging_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ info->test_info.is_rechg_state = false;
+ dev_info(info->dev, "%s: Not charging (VF err!)\n",
+ __func__);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ dev_dbg(info->dev, "%s: Discharging\n", __func__);
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) {
+ dev_info(info->dev, "%s: recover health state\n",
+ __func__);
+ if (info->cable_type != CABLE_TYPE_NONE) {
+ sec_bat_enable_charging(info, true);
+ info->charging_status
+ = POWER_SUPPLY_STATUS_CHARGING;
+ } else
+ info->charging_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ default:
+ dev_info(info->dev, "%s: Undefined Battery Status\n", __func__);
+ return;
+ }
+
+ if (info->batt_soc != info->batt_presoc)
+ pr_info("[fg] p:%d, s1:%d, s2:%d, v:%d, t:%d\n",
+ info->batt_raw_soc,
+ info->batt_soc, info->batt_presoc,
+ info->batt_vcell, info->batt_temp_radc);
+
+ power_supply_changed(&info->psy_bat);
+
+ wake_unlock(&info->monitor_wake_lock);
+
+ return;
+}
+
+static void sec_bat_polling_work(struct work_struct *work)
+{
+ struct sec_bat_info *info;
+ info = container_of(work, struct sec_bat_info, polling_work.work);
+
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ if (info->initial_check_count) {
+ schedule_delayed_work(&info->polling_work, HZ);
+ info->initial_check_count--;
+ } else
+ schedule_delayed_work(&info->polling_work,
+ msecs_to_jiffies(info->polling_interval));
+}
+
+static void sec_bat_measure_work(struct work_struct *work)
+{
+ struct sec_bat_info *info;
+ info = container_of(work, struct sec_bat_info, measure_work.work);
+
+ wake_lock(&info->measure_wake_lock);
+ sec_bat_check_temper_adc(info);
+ if (sec_bat_check_detbat(info) == BAT_NOT_DETECTED
+ && info->present == 1) {
+ msleep(100);
+ sec_bat_check_detbat(info); /* AGAIN_FEATURE */
+ }
+ /* TBD */
+ /*
+ sec_batt_check_call_exception(info);
+ sec_batt_check_call_exception_test(info); */ /* for test */
+
+ if (info->initial_check_count) {
+ queue_delayed_work(info->monitor_wqueue, &info->measure_work,
+ HZ);
+ } else {
+ queue_delayed_work(info->monitor_wqueue, &info->measure_work,
+ msecs_to_jiffies(info->measure_interval));
+ }
+ /*
+ schedule_delayed_work(&info->measure_work,
+ msecs_to_jiffies(info->measure_interval));
+ */
+ wake_unlock(&info->measure_wake_lock);
+}
+
+#define SEC_BATTERY_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, \
+ .mode = 0664 }, \
+ .show = sec_bat_show_property, \
+ .store = sec_bat_store, \
+}
+
+static struct device_attribute sec_battery_attrs[] = {
+ SEC_BATTERY_ATTR(batt_vol),
+ SEC_BATTERY_ATTR(batt_soc),
+ SEC_BATTERY_ATTR(batt_vfocv),
+ SEC_BATTERY_ATTR(batt_temp),
+ SEC_BATTERY_ATTR(batt_temp_adc),
+ SEC_BATTERY_ATTR(batt_temp_adc_avg),
+ SEC_BATTERY_ATTR(batt_temp_adc_sub),
+ SEC_BATTERY_ATTR(charging_source),
+ SEC_BATTERY_ATTR(batt_lp_charging),
+ SEC_BATTERY_ATTR(video),
+ SEC_BATTERY_ATTR(mp3),
+ SEC_BATTERY_ATTR(batt_type),
+ SEC_BATTERY_ATTR(batt_full_check),
+ SEC_BATTERY_ATTR(batt_temp_check),
+ SEC_BATTERY_ATTR(batt_temp_adc_spec),
+ SEC_BATTERY_ATTR(batt_test_value),
+ SEC_BATTERY_ATTR(batt_temp_radc),
+ SEC_BATTERY_ATTR(batt_current_adc),
+ SEC_BATTERY_ATTR(batt_esus_test),
+ SEC_BATTERY_ATTR(system_rev),
+ SEC_BATTERY_ATTR(fg_psoc),
+ SEC_BATTERY_ATTR(batt_lpm_state),
+ SEC_BATTERY_ATTR(sub_chg_state),
+ SEC_BATTERY_ATTR(lpm_reboot_event),
+ SEC_BATTERY_ATTR(talk_wcdma),
+ SEC_BATTERY_ATTR(talk_gsm),
+ SEC_BATTERY_ATTR(batt_tmu_status),
+};
+
+enum {
+ BATT_VOL = 0,
+ BATT_SOC,
+ BATT_VFOCV,
+ BATT_TEMP,
+ BATT_TEMP_ADC,
+ BATT_TEMP_ADC_AVG,
+ BATT_TEMP_ADC_SUB,
+ CHARGING_SOURCE,
+ BATT_LP_CHARGING,
+ BATT_VIDEO,
+ BATT_MP3,
+ BATT_TYPE,
+ BATT_FULL_CHECK,
+ BATT_TEMP_CHECK,
+ BATT_TEMP_ADC_SPEC,
+ BATT_TEST_VALUE,
+ BATT_TEMP_RADC,
+ BATT_CURRENT_ADC,
+ BATT_ESUS_TEST,
+ BATT_SYSTEM_REV,
+ BATT_FG_PSOC,
+ BATT_LPM_STATE,
+ BATT_SUB_CHG_STATE,
+ LPM_REBOOT_EVENT,
+ BATT_WCDMA_CALL,
+ BATT_GSM_CALL,
+ BATT_TMU_STATUS,
+};
+
+static ssize_t sec_bat_show_property(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev->parent);
+ int i = 0, val;
+ const ptrdiff_t off = attr - sec_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_AVGVCELL);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_SOC:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_SOC);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_VFOCV:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_TEMP:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_temp);
+ break;
+ case BATT_TEMP_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_temp_adc);
+ break;
+ case BATT_TEMP_ADC_AVG:
+ /*
+ val = info->temper_adc_sample.average_adc;
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ */
+ break;
+ case BATT_TEMP_ADC_SUB:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_temp_radc_sub);
+ break;
+ case CHARGING_SOURCE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->cable_type);
+ break;
+ case BATT_LP_CHARGING:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->get_lpcharging_state());
+ break;
+ case BATT_VIDEO:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_MP3:
+ /* TODO */
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0);
+ break;
+ case BATT_TYPE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "SDI_SDI");
+ break;
+ case BATT_FULL_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ (info->charging_status ==
+ POWER_SUPPLY_STATUS_FULL) ? 1 : 0);
+ break;
+ case BATT_TEMP_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->batt_health);
+ break;
+ case BATT_TEMP_ADC_SPEC:
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "(HIGH: %d - %d, LOW: %d - %d)\n",
+ HIGH_BLOCK_TEMP_ADC, HIGH_RECOVER_TEMP_ADC,
+ LOW_BLOCK_TEMP_ADC, LOW_RECOVER_TEMP_ADC);
+ break;
+ case BATT_TEST_VALUE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->test_info.test_value);
+ break;
+ case BATT_TEMP_RADC:
+ val = s3c_read_temper_adc(info);
+ val = sec_rescale_temp_adc(info);
+ i += scnprintf(buf + i, PAGE_SIZE - i,
+ "%d\n", info->batt_temp_radc);
+ break;
+ case BATT_CURRENT_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_current_adc);
+ break;
+ case BATT_ESUS_TEST:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->test_info.test_esuspend);
+ break;
+ case BATT_SYSTEM_REV:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", system_rev);
+ break;
+ case BATT_FG_PSOC:
+ val = sec_bat_get_fuelgauge_data(info, FG_T_PSOC);
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val);
+ break;
+ case BATT_LPM_STATE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_lpm_state);
+ break;
+ case BATT_SUB_CHG_STATE:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->sub_chg_status);
+ break;
+ case BATT_TMU_STATUS:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ info->batt_tmu_status);
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t sec_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0, x = 0;
+ const ptrdiff_t off = attr - sec_battery_attrs;
+ struct sec_bat_info *info = dev_get_drvdata(dev->parent);
+
+ switch (off) {
+ case BATT_VIDEO:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: video(%d)\n", __func__, x);
+ ret = count;
+ }
+ break;
+ case BATT_MP3:
+ /* TODO */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ dev_info(info->dev, "%s: mp3(%d)\n", __func__, x);
+ ret = count;
+ }
+ break;
+ case BATT_ESUS_TEST: /* early_suspend test */
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ if (x == 0) {
+ info->test_info.test_esuspend = 0;
+ wake_lock_timeout(&info->test_wake_lock,
+ 5 * HZ);
+ cancel_delayed_work(&info->measure_work);
+ info->measure_interval = MEASURE_DSG_INTERVAL;
+ wake_lock(&info->measure_wake_lock);
+ queue_delayed_work(info->monitor_wqueue,
+ &info->measure_work, 0);
+ /* schedule_delayed_work(&info->measure_work,
+ 0); */
+ } else {
+ info->test_info.test_esuspend = 1;
+ wake_lock(&info->test_wake_lock);
+ cancel_delayed_work(&info->measure_work);
+ info->measure_interval = MEASURE_CHG_INTERVAL;
+ wake_lock(&info->measure_wake_lock);
+ queue_delayed_work(info->monitor_wqueue,
+ &info->measure_work, 0);
+ /* schedule_delayed_work(&info->measure_work,
+ 0); */
+ }
+ ret = count;
+ }
+ break;
+ case BATT_TEST_VALUE:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ if (x == 0)
+ info->test_info.test_value = 0;
+ else if (x == 1)
+ /* for temp warning event */
+ info->test_info.test_value = 1;
+ else if (x == 2)
+ /* for full event */
+ /* info->test_info.test_value = 2; */
+ /* disable full test interface. */
+ info->test_info.test_value = 0;
+ else if (x == 3)
+ /* for abs time event */
+ info->test_info.test_value = 3;
+ else if (x == 999) {
+ /* for pop-up disable */
+ info->test_info.test_value = 999;
+ if ((info->batt_health ==
+ POWER_SUPPLY_HEALTH_OVERHEAT) ||
+ (info->batt_health ==
+ POWER_SUPPLY_HEALTH_COLD)) {
+ info->batt_health =
+ POWER_SUPPLY_HEALTH_GOOD;
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue,
+ &info->monitor_work);
+ }
+ } else if (x == 6)
+ /* for tmu test */
+ info->test_info.test_value = 6;
+ else
+ info->test_info.test_value = 0;
+ pr_info("batt test case : %d\n",
+ info->test_info.test_value);
+ ret = count;
+ }
+ break;
+ case BATT_LPM_STATE:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->batt_lpm_state = x;
+ ret = count;
+ }
+ break;
+ case LPM_REBOOT_EVENT:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ /* disable sub-charger and enable main-charger */
+ pr_info("lpm reboot event is trigered\n");
+ if (info->use_sub_charger) {
+ sec_bat_enable_charging(info, false);
+ sec_main_charger_default(info);
+ }
+ ret = count;
+ }
+ break;
+ case BATT_WCDMA_CALL:
+ case BATT_GSM_CALL:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ info->voice_call_state = x;
+ pr_info("voice call = %d, %d\n",
+ x, info->voice_call_state);
+ }
+ 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 sec_attrs_failed;
+ }
+ goto succeed;
+
+sec_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sec_battery_attrs[i]);
+succeed:
+ return rc;
+}
+
+static int sec_bat_is_charging(struct sec_bat_info *info)
+{
+ struct power_supply *psy = get_power_supply_by_name(info->charger_name);
+ union power_supply_propval value;
+ int ret;
+
+ if (!psy) {
+ dev_err(info->dev, "%s: fail to get charger ps\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to get status(%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ return value.intval;
+}
+
+static int sec_bat_read_proc(char *buf, char **start,
+ off_t offset, int count, int *eof, void *data)
+{
+ struct sec_bat_info *info = data;
+ struct timespec cur_time;
+ ktime_t ktime;
+ int len = 0;
+
+ ktime = alarm_get_elapsed_realtime();
+ cur_time = ktime_to_timespec(ktime);
+
+ len = sprintf(buf, "%lu %u %u %u %u "
+ "%u %d %d %d %d "
+ "%d %u %u %u %u "
+ "%u %u %d %d %lu\n",
+ cur_time.tv_sec,
+ info->batt_raw_soc,
+ info->batt_soc,
+ info->batt_vcell,
+ info->batt_current_adc,
+ info->charging_enabled,
+ info->batt_full_status,
+ info->test_info.full_count,
+ info->test_info.full_count_sub,
+ info->test_info.rechg_count,
+ info->test_info.is_rechg_state,
+ info->recharging_status,
+ info->batt_temp_radc,
+ info->batt_temp_radc_sub,
+ info->batt_health,
+ info->charging_status,
+ info->present,
+ info->cable_type,
+ info->batt_tmu_status,
+ info->charging_passed_time);
+ return len;
+}
+
+static __devinit int sec_bat_probe(struct platform_device *pdev)
+{
+ struct sec_bat_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct sec_bat_info *info;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "%s: SEC Battery Driver Loading\n", __func__);
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, info);
+
+ info->dev = &pdev->dev;
+ if (!pdata->fuel_gauge_name || !pdata->charger_name) {
+ dev_err(info->dev, "%s: no fuel gauge or charger name\n",
+ __func__);
+ goto err_kfree;
+ }
+ info->fuel_gauge_name = pdata->fuel_gauge_name;
+ info->charger_name = pdata->charger_name;
+ info->adc_arr_size = pdata->adc_arr_size;
+ info->adc_table = pdata->adc_table;
+
+ info->get_lpcharging_state = pdata->get_lpcharging_state;
+
+ if (pdata->sub_charger_name) {
+ info->sub_charger_name = pdata->sub_charger_name;
+ if (system_rev >= HWREV_FOR_BATTERY)
+ info->use_sub_charger = true;
+ }
+
+ info->psy_bat.name = "battery",
+ info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ info->psy_bat.properties = sec_battery_props,
+ info->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props),
+ info->psy_bat.get_property = sec_bat_get_property,
+ info->psy_bat.set_property = sec_bat_set_property,
+ info->psy_usb.name = "usb",
+ info->psy_usb.type = POWER_SUPPLY_TYPE_USB,
+ info->psy_usb.supplied_to = supply_list,
+ info->psy_usb.num_supplicants = ARRAY_SIZE(supply_list),
+ info->psy_usb.properties = sec_power_props,
+ info->psy_usb.num_properties = ARRAY_SIZE(sec_power_props),
+ info->psy_usb.get_property = sec_usb_get_property,
+ info->psy_ac.name = "ac",
+ info->psy_ac.type = POWER_SUPPLY_TYPE_MAINS,
+ info->psy_ac.supplied_to = supply_list,
+ info->psy_ac.num_supplicants = ARRAY_SIZE(supply_list),
+ info->psy_ac.properties = sec_power_props,
+ info->psy_ac.num_properties = ARRAY_SIZE(sec_power_props),
+ info->psy_ac.get_property = sec_ac_get_property;
+
+ wake_lock_init(&info->vbus_wake_lock, WAKE_LOCK_SUSPEND,
+ "vbus_present");
+ wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND,
+ "sec-battery-monitor");
+ wake_lock_init(&info->cable_wake_lock, WAKE_LOCK_SUSPEND,
+ "sec-battery-cable");
+ wake_lock_init(&info->test_wake_lock, WAKE_LOCK_SUSPEND,
+ "bat_esus_test");
+ wake_lock_init(&info->measure_wake_lock, WAKE_LOCK_SUSPEND,
+ "sec-battery-measure");
+
+ info->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ info->charging_status = sec_bat_is_charging(info);
+ if (info->charging_status < 0)
+ info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (info->charging_status == POWER_SUPPLY_STATUS_CHARGING)
+ info->cable_type = CABLE_TYPE_USB; /* default */
+ info->batt_soc = 100;
+ info->recharging_status = false;
+ info->present = 1;
+ info->initial_check_count = INIT_CHECK_COUNT;
+
+ mutex_init(&info->adclock);
+
+ info->padc = s3c_adc_register(pdev, NULL, NULL, 0);
+ info->charging_start_time = 0;
+ info->is_call_except = 0;
+
+ if (info->get_lpcharging_state) {
+ if (info->get_lpcharging_state())
+ info->polling_interval = POLLING_INTERVAL / 4;
+ else
+ info->polling_interval = POLLING_INTERVAL;
+ }
+
+ info->batt_tmu_status = TMU_STATUS_NORMAL;
+
+ if (info->charging_status == POWER_SUPPLY_STATUS_CHARGING)
+ info->measure_interval = MEASURE_CHG_INTERVAL;
+ else
+ info->measure_interval = MEASURE_DSG_INTERVAL;
+
+ /* init power supplier framework */
+ ret = power_supply_register(&pdev->dev, &info->psy_bat);
+ if (ret) {
+ dev_err(info->dev, "%s: failed to register psy_bat\n",
+ __func__);
+ goto err_wake_lock;
+ }
+
+ ret = power_supply_register(&pdev->dev, &info->psy_usb);
+ if (ret) {
+ dev_err(info->dev, "%s: failed to register psy_usb\n",
+ __func__);
+ goto err_supply_unreg_bat;
+ }
+
+ ret = power_supply_register(&pdev->dev, &info->psy_ac);
+ if (ret) {
+ dev_err(info->dev, "%s: failed to register psy_ac\n", __func__);
+ goto err_supply_unreg_usb;
+ }
+
+ /* create sec detail attributes */
+ sec_bat_create_attrs(info->psy_bat.dev);
+
+ info->entry = create_proc_entry("batt_info_proc", S_IRUGO, NULL);
+ if (!info->entry)
+ dev_err(info->dev, "%s: failed to create proc_entry\n",
+ __func__);
+ else {
+ info->entry->read_proc = sec_bat_read_proc;
+ info->entry->data = (struct sec_bat_info *)info;
+ }
+
+ info->monitor_wqueue =
+ create_freezable_workqueue(dev_name(&pdev->dev));
+ if (!info->monitor_wqueue) {
+ dev_err(info->dev, "%s: fail to create workqueue\n", __func__);
+ goto err_supply_unreg_ac;
+ }
+
+ if (info->charging_status == POWER_SUPPLY_STATUS_DISCHARGING)
+ sec_main_charger_disable(info);
+
+ INIT_WORK(&info->monitor_work, sec_bat_monitor_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->cable_work, sec_bat_cable_work);
+
+ INIT_DELAYED_WORK_DEFERRABLE(&info->polling_work, sec_bat_polling_work);
+ schedule_delayed_work(&info->polling_work, 0);
+
+ INIT_DELAYED_WORK_DEFERRABLE(&info->measure_work, sec_bat_measure_work);
+ schedule_delayed_work(&info->measure_work, 0);
+
+ return 0;
+
+err_supply_unreg_ac:
+ power_supply_unregister(&info->psy_ac);
+err_supply_unreg_usb:
+ power_supply_unregister(&info->psy_usb);
+err_supply_unreg_bat:
+ power_supply_unregister(&info->psy_bat);
+err_wake_lock:
+ wake_lock_destroy(&info->vbus_wake_lock);
+ wake_lock_destroy(&info->monitor_wake_lock);
+ wake_lock_destroy(&info->cable_wake_lock);
+ wake_lock_destroy(&info->test_wake_lock);
+ wake_lock_destroy(&info->measure_wake_lock);
+ mutex_destroy(&info->adclock);
+err_kfree:
+ kfree(info);
+
+ return ret;
+}
+
+static int __devexit sec_bat_remove(struct platform_device *pdev)
+{
+ struct sec_bat_info *info = platform_get_drvdata(pdev);
+
+ remove_proc_entry("batt_info_proc", NULL);
+
+ flush_workqueue(info->monitor_wqueue);
+ destroy_workqueue(info->monitor_wqueue);
+
+ cancel_delayed_work(&info->cable_work);
+ cancel_delayed_work(&info->polling_work);
+ cancel_delayed_work(&info->measure_work);
+
+ power_supply_unregister(&info->psy_bat);
+ power_supply_unregister(&info->psy_usb);
+ power_supply_unregister(&info->psy_ac);
+
+ wake_lock_destroy(&info->vbus_wake_lock);
+ wake_lock_destroy(&info->monitor_wake_lock);
+ wake_lock_destroy(&info->cable_wake_lock);
+ wake_lock_destroy(&info->test_wake_lock);
+ wake_lock_destroy(&info->measure_wake_lock);
+ mutex_destroy(&info->adclock);
+
+ s3c_adc_release(info->padc);
+
+ kfree(info);
+
+ return 0;
+}
+
+static int sec_bat_suspend(struct device *dev)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev);
+
+ cancel_work_sync(&info->monitor_work);
+ cancel_delayed_work(&info->cable_work);
+ cancel_delayed_work(&info->polling_work);
+ cancel_delayed_work(&info->measure_work);
+
+ return 0;
+}
+
+static int sec_bat_resume(struct device *dev)
+{
+ struct sec_bat_info *info = dev_get_drvdata(dev);
+
+ wake_lock(&info->monitor_wake_lock);
+ queue_work(info->monitor_wqueue, &info->monitor_work);
+
+ schedule_delayed_work(&info->polling_work,
+ msecs_to_jiffies(info->polling_interval));
+ schedule_delayed_work(&info->measure_work,
+ msecs_to_jiffies(info->measure_interval));
+
+ return 0;
+}
+
+#define sec_bat_shutdown NULL
+
+static const struct dev_pm_ops sec_bat_pm_ops = {
+ .suspend = sec_bat_suspend,
+ .resume = sec_bat_resume,
+};
+
+static struct platform_driver sec_bat_driver = {
+ .driver = {
+ .name = "sec-battery",
+ .owner = THIS_MODULE,
+ .pm = &sec_bat_pm_ops,
+ .shutdown = sec_bat_shutdown,
+ },
+ .probe = sec_bat_probe,
+ .remove = __devexit_p(sec_bat_remove),
+};
+
+static int __init sec_bat_init(void)
+{
+ return platform_driver_register(&sec_bat_driver);
+}
+
+static void __exit sec_bat_exit(void)
+{
+ platform_driver_unregister(&sec_bat_driver);
+}
+
+late_initcall(sec_bat_init);
+module_exit(sec_bat_exit);
+
+MODULE_DESCRIPTION("SEC battery driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_AUTHOR("<joshua.chang@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/smb136_charger.c b/drivers/power/smb136_charger.c
new file mode 100644
index 0000000..10afcc7
--- /dev/null
+++ b/drivers/power/smb136_charger.c
@@ -0,0 +1,465 @@
+/*
+ * smb136_charger.c
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Ikkeun Kim <iks.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+
+#include <linux/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/power/sec_battery_px.h>
+#include <linux/power/smb136_charger.h>
+#include <linux/mfd/max8997.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <plat/gpio-cfg.h>
+#include <mach/gpio-p2.h>
+
+
+/* Slave address */
+#define SMB136_SLAVE_ADDR 0x9A
+
+/* SMB136 Registers. */
+#define SMB_ChargeCurrent 0x00
+#define SMB_InputCurrentLimit 0x01
+#define SMB_FloatVoltage 0x02
+#define SMB_ControlA 0x03
+#define SMB_ControlB 0x04
+#define SMB_PinControl 0x05
+#define SMB_OTGControl 0x06
+#define SMB_Fault 0x07
+#define SMB_Temperature 0x08
+#define SMB_SafetyTimer 0x09
+#define SMB_VSYS 0x0A
+#define SMB_I2CAddr 0x0B
+
+#define SMB_IRQreset 0x30
+#define SMB_CommandA 0x31
+#define SMB_StatusA 0x32
+#define SMB_StatusB 0x33
+#define SMB_StatusC 0x34
+#define SMB_StatusD 0x35
+#define SMB_StatusE 0x36
+#define SMB_StatusF 0x37
+#define SMB_StatusG 0x38
+#define SMB_StatusH 0x39
+#define SMB_DeviceID 0x3B
+#define SMB_CommandB 0x3C
+
+/* SMB_StatusC register bit. */
+#define SMB_USB 1
+#define SMB_CHARGER 0
+#define Compelete 1
+#define Busy 0
+#define InputCurrent275 0xE
+#define InputCurrent500 0xF
+#define InputCurrent700 0x0
+#define InputCurrent800 0x1
+#define InputCurrent900 0x2
+#define InputCurrent1000 0x3
+#define InputCurrent1100 0x4
+#define InputCurrent1200 0x5
+#define InputCurrent1300 0x6
+#define InputCurrent1400 0x7
+
+/* SIOP */
+#define CHARGING_CURRENT_HIGH_LOW_STANDARD 450
+
+struct smb136_chg_data {
+ struct i2c_client *client;
+ struct smb_charger_data *pdata;
+ struct smb_charger_callbacks *callbacks;
+};
+
+static struct smb136_chg_data *smb136_chg;
+static int charger_i2c_init;
+
+
+static int smb136_i2c_read(struct i2c_client *client, u8 reg, u8 *data)
+{
+ int ret = 0;
+
+ if (!client)
+ return -ENODEV;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0)
+ return -EIO;
+
+ *data = ret & 0xff;
+ return 0;
+}
+
+static int smb136_i2c_write(struct i2c_client *client, u8 reg, u8 data)
+{
+ if (!client)
+ return -ENODEV;
+
+ return i2c_smbus_write_byte_data(client, reg, data);
+}
+
+static void smb136_test_read(void)
+{
+ struct smb136_chg_data *chg = smb136_chg;
+ u8 data = 0;
+ u32 addr = 0;
+
+ if (!charger_i2c_init) {
+ pr_info("%s : smb136 charger IC i2c is not initialized!!\n"
+ , __func__);
+ return ;
+ }
+
+ for (addr = 0; addr < 0x0c; addr++) {
+ smb136_i2c_read(chg->client, addr, &data);
+ pr_info("SMB136 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+
+ for (addr = 0x31; addr < 0x3D; addr++) {
+ smb136_i2c_read(chg->client, addr, &data);
+ pr_info("SMB136 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+}
+
+static int smb136_get_charging_state(void)
+{
+ struct smb136_chg_data *chg = smb136_chg;
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ u8 command_a_data, statue_e_data, statue_h_data;
+
+ smb136_i2c_read(chg->client, SMB_CommandA, &command_a_data);
+ smb136_i2c_read(chg->client, SMB_StatusE, &statue_e_data);
+ smb136_i2c_read(chg->client, SMB_StatusH, &statue_h_data);
+
+ if (statue_h_data & 0x01) {
+ pr_debug("%s: POWER_SUPPLY_STATUS_CHARGING\n", __func__);
+ return POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ if (!(statue_e_data & 0x06)) {
+ pr_debug("%s: POWER_SUPPLY_STATUS_DISCHARGING\n"
+ , __func__);
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ } else if ((statue_e_data & 0xc0)) {
+ pr_debug("%s: POWER_SUPPLY_STATUS_FULL\n", __func__);
+ return POWER_SUPPLY_STATUS_FULL;
+ }
+ }
+
+ pr_info("%s: POWER_SUPPLY_STATUS_UNKNOWN\n", __func__);
+ pr_info("%s: 0x31h(0x%02x), 0x36h(0x%02x), 0x39h(0x%02x)\n",
+ __func__, command_a_data, statue_e_data, statue_h_data);
+ return (int)status;
+}
+
+static void smb136_set_charging_state(int en, int cable_status)
+{
+ struct smb136_chg_data *chg = smb136_chg;
+ u8 data = 0;
+
+ if (!charger_i2c_init) {
+ pr_info("%s : smb136 charger IC i2c is not initialized!!\n"
+ , __func__);
+ return;
+ }
+
+ pr_info("%s : enable(%d), cable_status(%d)\n"
+ , __func__, en, cable_status);
+
+ if (en) { /* enable */
+ /* 2. Change USB5/1/HC Control from Pin to I2C */
+ smb136_i2c_write(chg->client, SMB_PinControl, 0x8);
+ udelay(10);
+
+ /* 1. USB 100mA Mode, USB5/1 Current Levels */
+ /* Prevent in-rush current */
+ data = 0x80;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* 3. Set charge current to 100mA */
+ /* Prevent in-rush current */
+ data = 0x14;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+
+ /* 4. Disable Automatic Input Current Limit */
+ data = 0xe6;
+ smb136_i2c_write(chg->client, SMB_InputCurrentLimit, data);
+ udelay(10);
+
+ /* 4. Automatic Recharge Disabed */
+ data = 0x8c;
+ smb136_i2c_write(chg->client, SMB_ControlA, data);
+ udelay(10);
+
+ /* 5. Safty timer Disabled */
+ data = 0x28;
+ smb136_i2c_write(chg->client, SMB_ControlB, data);
+ udelay(10);
+
+ /* 6. Disable USB D+/D- Detection */
+ data = 0x28;
+ smb136_i2c_write(chg->client, SMB_OTGControl, data);
+ udelay(10);
+
+ /* 7. Set Output Polarity for STAT */
+ data = 0xCA;
+ smb136_i2c_write(chg->client, SMB_FloatVoltage, data);
+ udelay(10);
+
+ /* 9. Re-load Enable */
+ data = 0x4b;
+ smb136_i2c_write(chg->client, SMB_SafetyTimer, data);
+ udelay(10);
+
+ /* Enable charge */
+ gpio_set_value(chg->pdata->enable, 0);
+
+ switch (cable_status) {
+ case CABLE_TYPE_TA:
+ /* HC mode */
+ data = 0x8c;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* Set charge current to 1500mA */
+ data = 0xf4;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ break;
+ case CABLE_TYPE_STATION:
+ /* HC mode */
+ data = 0x8c;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* Set charge current to 750mA */
+ data = 0x54;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+
+ /* Disable Automatic Input Current Limit,
+ * USBIN Current Limit to 700mA */
+ data = 0x06;
+ smb136_i2c_write(chg->client,
+ SMB_InputCurrentLimit, data);
+ udelay(10);
+ break;
+ case CABLE_TYPE_USB:
+ default:
+ /* Prevent in-rush current */
+ msleep(100);
+
+ /* USBIN 500mA mode */
+ data = 0x88;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* Set charge current to 500mA */
+ data = 0x14;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ break;
+ }
+ } else {
+ gpio_set_value(chg->pdata->enable, 1);
+
+ /* USB 100mA Mode, USB5/1 Current Levels */
+ /* Prevent in-rush current */
+ data = 0x80;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* Set charge current to 100mA */
+ /* Prevent in-rush current */
+ data = 0x14;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ }
+
+ return;
+}
+
+static int smb136_get_charging_current(void)
+{
+ struct smb136_chg_data *chg = smb136_chg;
+ u8 data = 0;
+ int get_current = 0;
+
+ smb136_i2c_read(chg->client, SMB_ChargeCurrent, &data);
+ switch (data >> 5) {
+ case 0:
+ get_current = 500; break;
+ case 1:
+ get_current = 650; break;
+ case 2:
+ get_current = 750; break;
+ case 3:
+ get_current = 850; break;
+ case 4:
+ get_current = 950; break;
+ case 5:
+ get_current = 1100; break;
+ case 6:
+ get_current = 1300; break;
+ case 7:
+ get_current = 1500; break;
+ default:
+ get_current = 500;
+ break;
+ }
+ pr_debug("%s: Get charging current as %dmA.\n", __func__, get_current);
+ return get_current;
+}
+
+static void smb136_set_charging_current(int set_current)
+{
+ struct smb136_chg_data *chg = smb136_chg;
+ u8 data = 0;
+
+ if (set_current > 450) {
+ /* HC mode */
+ data = 0x8c;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* Set charge current to 1500mA */
+ data = 0xf4;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ } else {
+ /* USBIN 500mA mode */
+ data = 0x88;
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* Set charge current to 500mA */
+ data = 0x14;
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ }
+ pr_debug("%s: Set charging current as %dmA.\n", __func__, set_current);
+}
+
+static int smb136_i2c_probe
+(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct smb136_chg_data *chg;
+ int ret = 0;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ pr_info("%s : SMB136 Charger Driver Loading\n", __func__);
+
+ chg = kzalloc(sizeof(struct smb136_chg_data), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->callbacks = kzalloc(sizeof(struct smb_charger_callbacks)
+ , GFP_KERNEL);
+ if (!chg->callbacks) {
+ pr_err("%s : No callbacks\n", __func__);
+ ret = -ENOMEM;
+ goto err_callbacks;
+ }
+
+ chg->client = client;
+ if (!chg->client) {
+ pr_err("%s : No client\n", __func__);
+ ret = -EINVAL;
+ goto err_client;
+ }
+
+ chg->pdata = client->dev.platform_data;
+ if (!chg->pdata) {
+ pr_err("%s : No platform data supplied\n", __func__);
+ ret = -EINVAL;
+ goto err_pdata;
+ }
+
+ i2c_set_clientdata(client, chg);
+ smb136_chg = chg;
+ pr_info("register callback functions!\n");
+
+ chg->callbacks->set_charging_state = smb136_set_charging_state;
+ chg->callbacks->get_charging_state = smb136_get_charging_state;
+ chg->callbacks->set_charging_current = smb136_set_charging_current;
+ chg->callbacks->get_charging_current = smb136_get_charging_current;
+ if (chg->pdata && chg->pdata->register_callbacks)
+ chg->pdata->register_callbacks(chg->callbacks);
+
+ charger_i2c_init = 1;
+ pr_info("Smb136 charger attach success!!!\n");
+
+ smb136_test_read();
+
+ return 0;
+
+err_pdata:
+err_client:
+err_callbacks:
+ kfree(chg);
+ return ret;
+}
+
+static int __devexit smb136_remove(struct i2c_client *client)
+{
+ struct smb136_chg_data *chg = i2c_get_clientdata(client);
+
+ if (chg->pdata && chg->pdata->unregister_callbacks)
+ chg->pdata->unregister_callbacks();
+
+ kfree(chg);
+ return 0;
+}
+
+static const struct i2c_device_id smb136_id[] = {
+ { "smb136-charger", 0 },
+ { }
+};
+
+
+static struct i2c_driver smb136_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "smb136-charger",
+ },
+ .id_table = smb136_id,
+ .probe = smb136_i2c_probe,
+ .remove = __devexit_p(smb136_remove),
+ .command = NULL,
+};
+
+
+MODULE_DEVICE_TABLE(i2c, smb136_id);
+
+static int __init smb136_init(void)
+{
+ return i2c_add_driver(&smb136_i2c_driver);
+}
+
+static void __exit smb136_exit(void)
+{
+ i2c_del_driver(&smb136_i2c_driver);
+}
+
+module_init(smb136_init);
+module_exit(smb136_exit);
+
+MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>");
+MODULE_DESCRIPTION("smb136 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/smb136_charger_q1.c b/drivers/power/smb136_charger_q1.c
new file mode 100644
index 0000000..da545e0
--- /dev/null
+++ b/drivers/power/smb136_charger_q1.c
@@ -0,0 +1,531 @@
+/*
+ * smb136_charger.c
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Ikkeun Kim <iks.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/power/smb136_charger_q1.h>
+#define DEBUG
+
+enum cable_type_t {
+ CABLE_TYPE_NONE = 0,
+ CABLE_TYPE_USB,
+ CABLE_TYPE_AC,
+ CABLE_TYPE_MISC,
+};
+
+static int smb136_i2c_read(struct i2c_client *client, u8 reg, u8 *data)
+{
+ int ret = 0;
+
+ if (!client)
+ return -ENODEV;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0)
+ return -EIO;
+
+ *data = ret & 0xff;
+ return 0;
+}
+
+static int smb136_i2c_write(struct i2c_client *client, u8 reg, u8 data)
+{
+ if (!client)
+ return -ENODEV;
+
+ return i2c_smbus_write_byte_data(client, reg, data);
+}
+
+static void smb136_test_read(struct i2c_client *client)
+{
+ struct smb136_chip *chg = i2c_get_clientdata(client);
+ u8 data = 0;
+ u32 addr = 0;
+
+ for (addr = 0; addr < 0x0c; addr++) {
+ smb136_i2c_read(chg->client, addr, &data);
+ dev_info(&client->dev,
+ "SMB136 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+
+ for (addr = 0x31; addr < 0x3D; addr++) {
+ smb136_i2c_read(chg->client, addr, &data);
+ dev_info(&client->dev,
+ "SMB136 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+}
+
+static int smb136_get_charging_status(struct i2c_client *client)
+{
+ struct smb136_chip *chg = i2c_get_clientdata(client);
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ u8 data1 = 0;
+ u8 data2 = 0;
+
+ smb136_i2c_read(chg->client, 0x36, &data1);
+ smb136_i2c_read(chg->client, 0x39, &data2);
+ dev_info(&client->dev, "%s : 0x36h(0x%02x), 0x39h(0x%02x)\n",
+ __func__, data1, data2);
+
+ if (data2 & 0x01)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else {
+ if ((data1 & 0x08) == 0x08) {
+ /* if error bit check,
+ ignore the status of charger-ic */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else if ((data1 & 0xc0) == 0xc0) {
+ /* At least one charge cycle terminated,
+ Charge current < Termination Current */
+ status = POWER_SUPPLY_STATUS_FULL;
+ }
+ }
+
+ return (int)status;
+}
+
+static int smb136_charging(struct i2c_client *client)
+{
+ struct smb136_chip *chg = i2c_get_clientdata(client);
+ u8 data = 0;
+ int gpio = 0;
+
+ dev_info(&client->dev, "%s : enable(%d), cable(%d)\n",
+ __func__, chg->is_enable, chg->cable_type);
+
+ if (chg->is_enable) {
+ switch (chg->cable_type) {
+ case CABLE_TYPE_AC:
+ /* 1. HC mode */
+ data = 0x8c;
+
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* 2. Change USB5/1/HC Control from Pin to I2C */
+ /* 2. EN pin control - active low */
+ smb136_i2c_write(chg->client, SMB_PinControl, 0x8);
+ udelay(10);
+
+ smb136_i2c_write(chg->client, SMB_CommandA, 0x8c);
+ udelay(10);
+
+ /* 3. Set charge current to 950mA,
+ termination current to 150mA */
+ data = 0x94;
+
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ break;
+ case CABLE_TYPE_USB:
+ default:
+ /* 1. USBIN 500mA mode */
+ data = 0x88;
+
+ smb136_i2c_write(chg->client, SMB_CommandA, data);
+ udelay(10);
+
+ /* 2. Change USB5/1/HC Control from Pin to I2C */
+ /* 2. EN pin control - active low */
+ smb136_i2c_write(chg->client, SMB_PinControl, 0x8);
+ udelay(10);
+
+ smb136_i2c_write(chg->client, SMB_CommandA, 0x88);
+ udelay(10);
+
+ /* 3. Set charge current to 500mA,
+ termination current to 150mA */
+ data = 0x14;
+
+ smb136_i2c_write(chg->client, SMB_ChargeCurrent, data);
+ udelay(10);
+ break;
+ }
+
+ /* 3. Enable Automatic Input Current Limit to 1000mA
+ (threshold 4.25V) */
+ data = 0x60;
+ smb136_i2c_write(chg->client, SMB_InputCurrentLimit, data);
+ udelay(10);
+
+ /* 4. Automatic Recharge Disabed */
+ data = 0x8c;
+ smb136_i2c_write(chg->client, SMB_ControlA, data);
+ udelay(10);
+
+ /* 5. Safety timer Disabled */
+ data = 0x28;
+ smb136_i2c_write(chg->client, SMB_ControlB, data);
+ udelay(10);
+
+ /* 6. Disable USB D+/D- Detection */
+ data = 0x28;
+ smb136_i2c_write(chg->client, SMB_OTGControl, data);
+ udelay(10);
+
+ /* 7. Set Output Polarity for STAT */
+ /* 7. Set float voltage to 4.2V */
+ data = 0x4a;
+ smb136_i2c_write(chg->client, SMB_FloatVoltage, data);
+ udelay(10);
+
+ /* 8. Re-load Enable */
+ data = 0x4b;
+ smb136_i2c_write(chg->client, SMB_SafetyTimer, data);
+ udelay(10);
+ } else {
+ /* do nothing... */
+ }
+
+ /* CHG_EN pin control - active low */
+ gpio = gpio_request(chg->pdata->gpio_chg_en, "CHG_EN");
+ if (!gpio) {
+ gpio_direction_output(chg->pdata->gpio_chg_en,
+ !(chg->is_enable));
+ dev_info(&client->dev, "gpio(CHG_EN)is %d\n",
+ gpio_get_value(chg->pdata->gpio_chg_en));
+ gpio_free(chg->pdata->gpio_chg_en);
+ } else
+ dev_err(&client->dev,
+ "faile to request gpio(CHG_EN)\n");
+
+ /* smb136_test_read(client); */
+
+ return 0;
+}
+
+static int smb136_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb136_chip *chip = container_of(psy,
+ struct smb136_chip,
+ charger);
+ u8 data;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = smb136_get_charging_status(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = chip->cable_type;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->is_enable;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ smb136_i2c_read(chip->client, SMB_ChargeCurrent, &data);
+ switch (data >> 5) {
+ case 0:
+ val->intval = 500;
+ break;
+ case 1:
+ val->intval = 650;
+ break;
+ case 2:
+ val->intval = 750;
+ break;
+ case 3:
+ val->intval = 850;
+ break;
+ case 4:
+ val->intval = 950;
+ break;
+ case 5:
+ val->intval = 1100;
+ break;
+ case 6:
+ val->intval = 1300;
+ break;
+ case 7:
+ val->intval = 1500;
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_info(&chip->client->dev, "%s: smb136_get_property (%d,%d)\n",
+ __func__, psp, val->intval);
+
+ return 0;
+}
+
+static int smb136_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb136_chip *chip = container_of(psy,
+ struct smb136_chip,
+ charger);
+
+ dev_info(&chip->client->dev, "%s: smb136_set_property (%d,%d)\n",
+ __func__, psp, val->intval);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ chip->is_enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING);
+ smb136_charging(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ switch (val->intval) {
+ case CABLE_TYPE_USB:
+ case CABLE_TYPE_AC:
+ chip->cable_type = val->intval;
+ break;
+ default:
+ dev_err(&chip->client->dev, "cable type NOT supported!\n");
+ chip->cable_type = CABLE_TYPE_NONE;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ chip->is_enable = (bool)val->intval;
+ smb136_charging(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (val->intval <= 450)
+ chip->cable_type = CABLE_TYPE_USB; /* USB */
+ else
+ chip->cable_type = CABLE_TYPE_AC; /* TA */
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1)
+static bool is_ovp_status;
+#endif
+
+static irqreturn_t smb136_irq_thread(int irq, void *data)
+{
+ struct smb136_chip *chip = data;
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1)
+ int ret = 0;
+ u8 data1 = 0;
+#endif
+
+ dev_info(&chip->client->dev, "%s\n", __func__);
+
+ smb136_test_read(chip->client);
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1)
+ smb136_i2c_read(chip->client, 0x33, &data1);
+
+ if (data1 & 0x02) {
+ if (is_ovp_status == false) {
+ is_ovp_status = true;
+ if (chip->pdata->ovp_cb)
+ ret = chip->pdata->ovp_cb(true);
+ dev_info(&chip->client->dev, "$s OVP!!\n");
+ }
+ } else {
+ if (is_ovp_status == true) {
+ is_ovp_status = false;
+ if (chip->pdata->ovp_cb)
+ ret = chip->pdata->ovp_cb(false);
+ dev_info(&chip->client->dev,
+ "$s ovp status released!!\n");
+ }
+ }
+#endif
+
+ return IRQ_HANDLED;
+}
+
+static int smb136_irq_init(struct smb136_chip *chip)
+{
+ struct i2c_client *client = chip->client;
+ int ret;
+
+ if (client->irq) {
+ ret = request_threaded_irq(client->irq, NULL,
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1)
+ smb136_irq_thread, IRQ_TYPE_EDGE_BOTH,
+#else
+ smb136_irq_thread, IRQ_TYPE_EDGE_FALLING,
+#endif
+ "SMB136 charger", chip);
+ if (ret) {
+ dev_err(&client->dev, "failed to reqeust IRQ\n");
+ return ret;
+ }
+
+ ret = enable_irq_wake(client->irq);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "failed to enable wakeup src %d\n", ret);
+ }
+
+ return 0;
+}
+
+static int smb136_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct smb136_chip *chip;
+ int ret = 0;
+ int gpio = 0;
+ u8 data;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ if (smb136_i2c_read(client, 0x36, &data) < 0) /* check HW */
+ return -EIO;
+
+ dev_info(&client->dev,
+ "%s : SMB136 Charger Driver Loading\n", __func__);
+
+ chip = kzalloc(sizeof(struct smb136_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+
+ if (!chip->pdata) {
+ dev_err(&client->dev,
+ "%s : No platform data supplied\n", __func__);
+ ret = -EINVAL;
+ goto err_pdata;
+ }
+
+ if (chip->pdata->set_charger_name)
+ chip->pdata->set_charger_name();
+
+ chip->is_enable = false;
+ chip->cable_type = CABLE_TYPE_NONE;
+
+ chip->charger.name = "smb136-charger";
+ chip->charger.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->charger.get_property = smb136_get_property;
+ chip->charger.set_property = smb136_set_property;
+ chip->charger.properties = smb136_charger_props;
+ chip->charger.num_properties = ARRAY_SIZE(smb136_charger_props);
+
+ ret = power_supply_register(&client->dev, &chip->charger);
+ if (ret) {
+ dev_err(&client->dev,
+ "failed: power supply register\n");
+ kfree(chip);
+ return ret;
+ }
+
+ /* CHG_EN pin control - active low */
+ if (chip->pdata->gpio_chg_en) {
+ s3c_gpio_cfgpin(chip->pdata->gpio_chg_en, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_chg_en, S3C_GPIO_PULL_NONE);
+
+ gpio = gpio_request(chip->pdata->gpio_chg_en, "CHG_EN");
+ if (!gpio) {
+ gpio_direction_output(chip->pdata->gpio_chg_en,
+ GPIO_LEVEL_HIGH);
+ gpio_free(chip->pdata->gpio_chg_en);
+ } else
+ dev_err(&client->dev,
+ "faile to request gpio(CHG_EN)\n");
+ }
+
+ if (chip->pdata->gpio_otg_en) {
+ s3c_gpio_cfgpin(chip->pdata->gpio_otg_en, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_otg_en, S3C_GPIO_PULL_NONE);
+
+ gpio = gpio_request(chip->pdata->gpio_otg_en, "OTG_EN");
+ if (!gpio) {
+ gpio_direction_output(chip->pdata->gpio_otg_en,
+ GPIO_LEVEL_LOW);
+ gpio_free(chip->pdata->gpio_otg_en);
+ } else
+ dev_err(&client->dev,
+ "faile to request gpio(OTG_EN)\n");
+ }
+
+ if (chip->pdata->gpio_ta_nconnected) {
+ s3c_gpio_cfgpin(chip->pdata->gpio_ta_nconnected,
+ S3C_GPIO_INPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_ta_nconnected,
+ S3C_GPIO_PULL_NONE);
+ }
+
+ if (chip->pdata->gpio_chg_ing) {
+#if 1
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1)
+ s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, S3C_GPIO_SFN(0xf));
+#endif
+ client->irq = gpio_to_irq(chip->pdata->gpio_chg_ing);
+ ret = smb136_irq_init(chip);
+ if (ret)
+ goto err_pdata;
+#else
+ s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_chg_ing, S3C_GPIO_PULL_NONE);
+#endif
+ }
+
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1)
+ is_ovp_status = false;
+#endif
+
+ smb136_test_read(client);
+
+ return 0;
+
+err_pdata:
+ kfree(chip);
+ return ret;
+}
+
+static int __devexit smb136_remove(struct i2c_client *client)
+{
+ struct smb136_chip *chip = i2c_get_clientdata(client);
+
+ kfree(chip);
+ return 0;
+}
+
+static const struct i2c_device_id smb136_id[] = {
+ {"smb136-charger", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, smb136_id);
+
+static struct i2c_driver smb136_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "smb136-charger",
+ },
+ .probe = smb136_probe,
+ .remove = __devexit_p(smb136_remove),
+ .command = NULL,
+ .id_table = smb136_id,
+};
+
+static int __init smb136_init(void)
+{
+ return i2c_add_driver(&smb136_i2c_driver);
+}
+
+static void __exit smb136_exit(void)
+{
+ i2c_del_driver(&smb136_i2c_driver);
+}
+
+module_init(smb136_init);
+module_exit(smb136_exit);
+
+MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>");
+MODULE_DESCRIPTION("smb136 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/smb328_charger.c b/drivers/power/smb328_charger.c
new file mode 100644
index 0000000..93fd8cb
--- /dev/null
+++ b/drivers/power/smb328_charger.c
@@ -0,0 +1,1046 @@
+/*
+ * smb328_charger.c
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Ikkeun Kim <iks.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/power/smb328_charger.h>
+#define DEBUG
+
+enum cable_type_t {
+ CABLE_TYPE_NONE = 0,
+ CABLE_TYPE_USB,
+ CABLE_TYPE_AC,
+ CABLE_TYPE_MISC,
+ CABLE_TYPE_OTG,
+};
+
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER)
+static bool is_ovp_status;
+#endif
+
+static int smb328_i2c_read(struct i2c_client *client, u8 reg, u8 *data)
+{
+ int ret = 0;
+
+ if (!client)
+ return -ENODEV;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0)
+ return -EIO;
+
+ *data = ret & 0xff;
+ return *data;
+}
+
+static int smb328_i2c_write(struct i2c_client *client, u8 reg, u8 data)
+{
+ if (!client)
+ return -ENODEV;
+
+ return i2c_smbus_write_byte_data(client, reg, data);
+}
+
+static void smb328_test_read(struct i2c_client *client)
+{
+ struct smb328_chip *chg = i2c_get_clientdata(client);
+ u8 data = 0;
+ u32 addr = 0;
+
+ for (addr = 0; addr < 0x0c; addr++) {
+ smb328_i2c_read(chg->client, addr, &data);
+ dev_info(&client->dev,
+ "smb328 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+
+ for (addr = 0x30; addr < 0x3D; addr++) {
+ smb328_i2c_read(chg->client, addr, &data);
+ dev_info(&client->dev,
+ "smb328 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+}
+
+static void smb328a_charger_function_conrol(struct i2c_client *client);
+
+static int smb328_get_charging_status(struct i2c_client *client)
+{
+ struct smb328_chip *chg = i2c_get_clientdata(client);
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ u8 data_a = 0;
+ u8 data_b = 0;
+ u8 data_c = 0;
+
+ smb328_i2c_read(chg->client,
+ SMB328A_BATTERY_CHARGING_STATUS_A, &data_a);
+ dev_info(&client->dev, "%s : charging status A(0x%02x)\n",
+ __func__, data_a);
+ smb328_i2c_read(chg->client,
+ SMB328A_BATTERY_CHARGING_STATUS_B, &data_b);
+ dev_info(&client->dev, "%s : charging status B(0x%02x)\n",
+ __func__, data_b);
+ smb328_i2c_read(chg->client,
+ SMB328A_BATTERY_CHARGING_STATUS_C, &data_c);
+ dev_info(&client->dev, "%s : charging status C(0x%02x)\n",
+ __func__, data_c);
+
+ /* check for safety timer in USB charging */
+ /* If safety timer is activated in USB charging, reset charger */
+#if 1
+ /* write 0xAA in register 0x30 to reset watchdog timer, */
+ /* it can replace this work-around */
+ if (chg->is_enable && chg->cable_type == CABLE_TYPE_USB) {
+ if ((data_c & 0x30) == 0x20) { /* safety timer activated */
+ /* reset charger */
+ dev_info(&client->dev,
+ "%s : Reset charger, safety timer is activated!\n",
+ __func__);
+
+ chg->is_enable = false;
+ smb328a_charger_function_conrol(chg->client);
+
+ chg->is_enable = true;
+ smb328a_charger_function_conrol(chg->client);
+ }
+ }
+#endif
+
+ /* At least one charge cycle terminated, */
+ /* Charge current < Termination Current */
+ if ((data_c & 0xc0) == 0xc0) {
+ /* top-off by full charging */
+ status = POWER_SUPPLY_STATUS_FULL;
+ goto charging_status_end;
+ }
+
+ /* Is enabled ? */
+ if (data_c & 0x01) {
+ /* check for 0x30 : 'safety timer' (0b01 or 0b10) or */
+ /* 'waiting to begin charging' (0b11) */
+ /* check for 0x06 : no charging (0b00) */
+ if ((data_c & 0x30) || !(data_c & 0x06)) {
+ /* not charging */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ goto charging_status_end;
+ } else {
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ goto charging_status_end;
+ }
+ } else
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+charging_status_end:
+ return (int)status;
+}
+
+static int smb328_get_charging_health(struct i2c_client *client)
+{
+ struct smb328_chip *chg = i2c_get_clientdata(client);
+ int health = POWER_SUPPLY_HEALTH_GOOD;
+ u8 data_a = 0;
+ u8 data_b = 0;
+ u8 data_c = 0;
+
+ smb328_i2c_read(chg->client,
+ SMB328A_BATTERY_CHARGING_STATUS_A, &data_a);
+ dev_info(&client->dev, "%s : charging status A(0x%02x)\n",
+ __func__, data_a);
+ smb328_i2c_read(chg->client,
+ SMB328A_BATTERY_CHARGING_STATUS_B, &data_b);
+ dev_info(&client->dev, "%s : charging status B(0x%02x)\n",
+ __func__, data_b);
+ smb328_i2c_read(chg->client,
+ SMB328A_BATTERY_CHARGING_STATUS_C, &data_c);
+ dev_info(&client->dev, "%s : charging status C(0x%02x)\n",
+ __func__, data_c);
+
+ /* Is enabled ? */
+ if (data_c & 0x01) {
+ if (!(data_a & 0x02)) /* Input current is NOT OK */
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ }
+
+#if defined(CONFIG_MACH_Q1_CHN)
+ {
+ u8 data_chn;
+ smb328_i2c_read(chg->client, 0x37, &data_chn);
+ dev_info(&client->dev,
+ "%s : charging interrupt status C(0x%02x)\n",
+ __func__, data_c);
+
+ if (data_chn & 0x04)
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+
+ if (health == POWER_SUPPLY_HEALTH_OVERVOLTAGE)
+ is_ovp_status = true;
+ else
+ is_ovp_status = false;
+ }
+#endif
+ return (int)health;
+}
+
+#if defined(CONFIG_MACH_Q1_CHN)
+static int smb328_is_ovp_status(struct i2c_client *client)
+{
+ struct smb328_chip *chg = i2c_get_clientdata(client);
+ int status = POWER_SUPPLY_HEALTH_UNKNOWN;
+ u8 data = 0;
+
+ smb328_i2c_read(chg->client, 0x37, &data);
+ dev_info(&client->dev, "%s : 0x37h(0x%02x)\n",
+ __func__, data);
+
+ if (data & 0x04) {
+ is_ovp_status = true;
+ status = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else {
+ is_ovp_status = false;
+ status = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ return (int)status;
+}
+#endif
+
+static void smb328a_allow_volatile_writes(struct i2c_client *client)
+{
+ int val, reg;
+ u8 data;
+
+ reg = SMB328A_COMMAND;
+ val = smb328_i2c_read(client, reg, &data);
+ if ((val >= 0) && !(val & 0x80)) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ data |= (0x1 << 7);
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)data;
+ pr_info("%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+}
+
+static void smb328a_charger_function_conrol(struct i2c_client *client)
+{
+ struct smb328_chip *chip = i2c_get_clientdata(client);
+ int val, reg;
+ u8 data, set_data;
+
+ if (chip->is_otg) {
+ dev_info(&client->dev,
+ "%s : OTG is activated. Ignore command (type:%d, enable:%s)\n",
+ __func__, chip->cable_type,
+ chip->is_enable ? "true" : "false");
+ return;
+ }
+
+ smb328a_allow_volatile_writes(client);
+
+ if (!chip->is_enable) {
+ reg = SMB328A_FUNCTION_CONTROL_B;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0x0) {
+ data = 0x0;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_COMMAND;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ data = 0x98; /* turn off charger */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ } else {
+ /* reset watchdog timer if it occured */
+ reg = SMB328A_CLEAR_IRQ;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ data = 0xaa;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+
+ reg = SMB328A_INPUT_AND_CHARGE_CURRENTS;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (chip->cable_type == CABLE_TYPE_AC) {
+ /* fast 1000mA, termination 200mA */
+ set_data = 0xb7;
+ } else if (chip->cable_type == CABLE_TYPE_MISC) {
+ /* fast 700mA, termination 200mA */
+ set_data = 0x57;
+ } else {
+ /* fast 500mA, termination 200mA */
+ set_data = 0x17;
+ }
+ if (data != set_data) {
+ /* this can be changed with top-off setting */
+ data = set_data;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_CURRENT_TERMINATION;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (chip->cable_type == CABLE_TYPE_AC) {
+ /* input 1A, threshold 4.25V, AICL enable */
+ set_data = 0xb0;
+ } else if (chip->cable_type == CABLE_TYPE_MISC) {
+ /* input 700mA, threshold 4.25V, AICL enable */
+ set_data = 0x50;
+ } else {
+ /* input 450mA, threshold 4.25V, AICL disable */
+ set_data = 0x14;
+#if defined(CONFIG_MACH_Q1_CHN)
+ /* turn off pre-bias for ovp */
+ set_data &= ~(0x10);
+#endif
+ }
+ if (data != set_data) {
+ data = set_data;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_FLOAT_VOLTAGE;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0xca) {
+ data = 0xca; /* 4.2V float voltage */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_FUNCTION_CONTROL_A1;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+#if 1
+ if (data != 0xda) {
+ data = 0xda; /* top-off by ADC */
+#else
+ if (data != 0x9a) {
+ data = 0x9a; /* top-off by charger */
+#endif
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_FUNCTION_CONTROL_A2;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ /* 0x4c -> 0x4e (watchdog timer enabled - SUMMIT) */
+ if (data != 0x4e) {
+ data = 0x4e;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_FUNCTION_CONTROL_B;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0x0) {
+#if defined(CONFIG_MACH_Q1_CHN)
+ data = 0x80;
+#else
+ data = 0x0;
+#endif
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_OTG_PWR_AND_LDO_CONTROL;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ set_data = 0xf5;
+ if (chip->cable_type == CABLE_TYPE_AC)
+ set_data = 0xf5;
+ else if (chip->cable_type == CABLE_TYPE_MISC)
+ set_data = 0xf5;
+ else
+ set_data = 0xcd;
+ if (data != set_data) {
+ data = set_data;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_VARIOUS_CONTROL_FUNCTION_A;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0xf6) {
+ /* this can be changed with top-off setting */
+ data = 0xf6;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_CELL_TEMPERATURE_MONITOR;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0x0) {
+ data = 0x0;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_INTERRUPT_SIGNAL_SELECTION;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0x0) {
+ data = 0x0;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ reg = SMB328A_COMMAND;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ /* turn on charger */
+ if (chip->cable_type == CABLE_TYPE_AC)
+ data = 0x8c;
+ else if (chip->cable_type == CABLE_TYPE_MISC)
+ data = 0x88;
+ else
+ data = 0x88; /* USB */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+}
+
+static void smb328a_charger_otg_conrol(struct i2c_client *client)
+{
+ struct smb328_chip *chip = i2c_get_clientdata(client);
+ int val, reg;
+ u8 data;
+
+ smb328a_allow_volatile_writes(client);
+
+ if (chip->is_otg) {
+ reg = SMB328A_FUNCTION_CONTROL_B;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0x0) {
+ data = 0x0;
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ /* delay for reset of charger */
+ mdelay(150);
+
+ reg = SMB328A_OTG_PWR_AND_LDO_CONTROL;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ data = 0xcd; /* OTG 350mA */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+
+ reg = SMB328A_COMMAND;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ data = 0x9a; /* turn on OTG */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ } else {
+ reg = SMB328A_FUNCTION_CONTROL_B;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ if (data != 0x0) {
+ data = 0x0c; /* turn off charger */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+
+ /* delay for reset of charger */
+ mdelay(150);
+
+ reg = SMB328A_COMMAND;
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ data = 0x98; /* turn off OTG */
+ if (smb328_i2c_write(client, reg, data) < 0)
+ pr_err("%s : error!\n", __func__);
+ val = smb328_i2c_read(client, reg, &data);
+ if (val >= 0) {
+ data = (u8)val;
+ dev_info(&client->dev,
+ "%s : => reg (0x%x) = 0x%x\n",
+ __func__, reg, data);
+ }
+ }
+ }
+}
+
+static int smb328_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb328_chip *chip = container_of(psy,
+ struct smb328_chip,
+ charger);
+ u8 data;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = smb328_get_charging_status(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = chip->cable_type;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = smb328_get_charging_health(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->is_enable;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (chip->is_enable) {
+ smb328_i2c_read(chip->client,
+ SMB328A_INPUT_AND_CHARGE_CURRENTS, &data);
+ switch (data >> 5) {
+ case 0:
+ val->intval = 450;
+ break;
+ case 1:
+ val->intval = 600;
+ break;
+ case 2:
+ val->intval = 700;
+ break;
+ case 3:
+ val->intval = 800;
+ break;
+ case 4:
+ val->intval = 900;
+ break;
+ case 5:
+ val->intval = 1000;
+ break;
+ case 6:
+ val->intval = 1100;
+ break;
+ case 7:
+ val->intval = 1200;
+ break;
+ }
+ } else
+ val->intval = 0;
+ break;
+#if defined(CONFIG_MACH_Q1_CHN)
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = smb328_is_ovp_status(chip->client);
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ dev_info(&chip->client->dev, "%s: smb328_get_property (%d,%d)\n",
+ __func__, psp, val->intval);
+
+ return 0;
+}
+
+static int smb328_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb328_chip *chip = container_of(psy,
+ struct smb328_chip,
+ charger);
+
+ dev_info(&chip->client->dev, "%s: smb328_set_property (%d,%d)\n",
+ __func__, psp, val->intval);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+#if defined(CONFIG_MACH_Q1_CHN)
+ is_ovp_status = false;
+#endif
+ chip->is_enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING);
+ smb328a_charger_function_conrol(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ /* only for OTG support */
+ chip->is_otg = val->intval;
+ smb328a_charger_otg_conrol(chip->client);
+ smb328_test_read(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ chip->is_enable = (bool)val->intval;
+ smb328a_charger_function_conrol(chip->client);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (val->intval <= 450)
+ chip->cable_type = CABLE_TYPE_USB;
+ else
+ chip->cable_type = CABLE_TYPE_AC;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static irqreturn_t smb328_irq_thread(int irq, void *data)
+{
+ struct smb328_chip *chip = data;
+ int ret = 0;
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER)
+ u8 data1 = 0;
+#endif
+
+ dev_info(&chip->client->dev, "%s: chg_ing IRQ occurred!\n", __func__);
+
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER)
+ smb328_i2c_read(chip->client, 0x37, &data1);
+
+ if (data1 & 0x04) {
+ /* if usbin(usb vbus) is in over-voltage status. */
+ if (is_ovp_status == false) {
+ is_ovp_status = true;
+ if (chip->pdata->ovp_cb)
+ ret = chip->pdata->ovp_cb(true);
+ dev_info(&chip->client->dev, "$s OVP!!\n");
+ }
+ } else {
+ if (is_ovp_status == true) {
+ is_ovp_status = false;
+ if (chip->pdata->ovp_cb)
+ ret = chip->pdata->ovp_cb(false);
+ dev_info(&chip->client->dev,
+ "$s ovp status released!!\n");
+ }
+ }
+#else
+ if (chip->pdata->topoff_cb)
+ ret = chip->pdata->topoff_cb();
+
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "%s: error from topoff_cb(%d)\n",
+ __func__, ret);
+ return IRQ_HANDLED;
+ }
+#endif
+ return IRQ_HANDLED;
+}
+
+static int smb328_irq_init(struct smb328_chip *chip)
+{
+ struct i2c_client *client = chip->client;
+ int ret;
+
+ if (client->irq) {
+ ret = request_threaded_irq(client->irq, NULL,
+ smb328_irq_thread,
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER)
+ IRQ_TYPE_EDGE_BOTH,
+#else
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+#endif
+ "SMB328 charger", chip);
+ if (ret) {
+ dev_err(&client->dev, "failed to reqeust IRQ\n");
+ return ret;
+ }
+
+ ret = enable_irq_wake(client->irq);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "failed to enable wakeup src %d\n", ret);
+ }
+
+ return 0;
+}
+
+static int smb328_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct smb328_chip *chip;
+ int ret = 0;
+ int gpio = 0;
+ u8 data;
+ int i;
+
+ i = 10;
+ while (1) {
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ goto I2CERROR;
+
+ if (smb328_i2c_read(client, 0x36, &data) >= 0) /* check HW */
+ break;
+
+I2CERROR:
+ if (!i--)
+ return -EIO;
+ msleep(300);
+ }
+
+ dev_info(&client->dev,
+ "%s : SMB328 Charger Driver Loading\n", __func__);
+
+ chip = kzalloc(sizeof(struct smb328_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+
+ if (!chip->pdata) {
+ dev_err(&client->dev,
+ "%s : No platform data supplied\n", __func__);
+ ret = -EINVAL;
+ goto err_pdata;
+ }
+
+ if (chip->pdata->set_charger_name)
+ chip->pdata->set_charger_name();
+
+ chip->is_otg = false;
+ chip->is_enable = false;
+ chip->cable_type = CABLE_TYPE_NONE;
+
+ chip->charger.name = "smb328-charger";
+ chip->charger.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->charger.get_property = smb328_get_property;
+ chip->charger.set_property = smb328_set_property;
+ chip->charger.properties = smb328_charger_props;
+ chip->charger.num_properties = ARRAY_SIZE(smb328_charger_props);
+
+ ret = power_supply_register(&client->dev, &chip->charger);
+ if (ret) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ kfree(chip);
+ return ret;
+ }
+
+ /* CHG_EN pin control - active low */
+ if (chip->pdata->gpio_chg_en) {
+ s3c_gpio_cfgpin(chip->pdata->gpio_chg_en, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_chg_en, S3C_GPIO_PULL_NONE);
+
+ gpio = gpio_request(chip->pdata->gpio_chg_en, "CHG_EN");
+ if (!gpio) {
+ gpio_direction_output(chip->pdata->gpio_chg_en,
+ GPIO_LEVEL_HIGH);
+ gpio_free(chip->pdata->gpio_chg_en);
+ } else
+ dev_err(&client->dev,
+ "faile to request gpio(CHG_EN)\n");
+ }
+
+ if (chip->pdata->gpio_otg_en) {
+ s3c_gpio_cfgpin(chip->pdata->gpio_otg_en, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_otg_en, S3C_GPIO_PULL_NONE);
+
+ gpio = gpio_request(chip->pdata->gpio_otg_en, "OTG_EN");
+ if (!gpio) {
+ gpio_direction_output(chip->pdata->gpio_otg_en,
+ GPIO_LEVEL_LOW);
+ gpio_free(chip->pdata->gpio_otg_en);
+ } else
+ dev_err(&client->dev,
+ "faile to request gpio(OTG_EN)\n");
+ }
+
+ if (chip->pdata->gpio_ta_nconnected) {
+ s3c_gpio_cfgpin(chip->pdata->gpio_ta_nconnected,
+ S3C_GPIO_INPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_ta_nconnected,
+ S3C_GPIO_PULL_NONE);
+ }
+
+ if (chip->pdata->gpio_chg_ing) {
+#if defined(CONFIG_MACH_Q1_CHN)
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER)
+ /* set external interrupt */
+ s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing,
+ S3C_GPIO_SFN(0xf));
+#endif
+ client->irq = gpio_to_irq(chip->pdata->gpio_chg_ing);
+ ret = smb328_irq_init(chip);
+ if (ret)
+ goto err_pdata;
+#else
+ s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing,
+ S3C_GPIO_INPUT);
+ s3c_gpio_setpull(chip->pdata->gpio_chg_ing,
+ S3C_GPIO_PULL_NONE);
+#endif
+ }
+
+#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER)
+ is_ovp_status = false;
+#endif
+
+ smb328_test_read(client);
+
+ return 0;
+
+err_pdata:
+ kfree(chip);
+ return ret;
+}
+
+static int __devexit smb328_remove(struct i2c_client *client)
+{
+ struct smb328_chip *chip = i2c_get_clientdata(client);
+
+ kfree(chip);
+ return 0;
+}
+
+static const struct i2c_device_id smb328_id[] = {
+ {"smb328-charger", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, smb328_id);
+
+static struct i2c_driver smb328_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "smb328-charger",
+ },
+ .probe = smb328_probe,
+ .remove = __devexit_p(smb328_remove),
+ .command = NULL,
+ .id_table = smb328_id,
+};
+
+static int __init smb328_init(void)
+{
+ return i2c_add_driver(&smb328_i2c_driver);
+}
+
+static void __exit smb328_exit(void)
+{
+ i2c_del_driver(&smb328_i2c_driver);
+}
+
+module_init(smb328_init);
+module_exit(smb328_exit);
+
+MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>");
+MODULE_DESCRIPTION("smb328 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/smb347_charger.c b/drivers/power/smb347_charger.c
new file mode 100755
index 0000000..a339776
--- /dev/null
+++ b/drivers/power/smb347_charger.c
@@ -0,0 +1,497 @@
+/*
+ * smb347_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/power/sec_battery_px.h>
+#include <linux/power/smb347_charger.h>
+#include <linux/mfd/max8997.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <plat/gpio-cfg.h>
+
+/* Slave address */
+#define SMB347_SLAVE_ADDR 0x0C
+
+/* SMB347 Registers. */
+#define SMB347_CHARGE_CURRENT 0X00
+#define SMB347_INPUT_CURRENTLIMIT 0X01
+#define SMB347_VARIOUS_FUNCTIONS 0X02
+#define SMB347_FLOAT_VOLTAGE 0X03
+#define SMB347_CHARGE_CONTROL 0X04
+#define SMB347_STAT_TIMERS_CONTROL 0x05
+#define SMB347_PIN_ENABLE_CONTROL 0x06
+#define SMB347_THERM_CONTROL_A 0x07
+#define SMB347_SYSOK_USB30_SELECTION 0x08
+#define SMB347_OTHER_CONTROL_A 0x09
+#define SMB347_OTG_TLIM_THERM_CONTROL 0x0A
+#define SMB347_LIMIT_CELL_TEMPERATURE_MONITOR 0x0B
+#define SMB347_FAULT_INTERRUPT 0x0C
+#define SMB347_STATUS_INTERRUPT 0x0D
+#define SMB347_I2C_BUS_SLAVE_ADDR 0x0E
+
+#define SMB347_COMMAND_A 0x30
+#define SMB347_COMMAND_B 0x31
+#define SMB347_COMMAND_C 0x33
+#define SMB347_INTERRUPT_STATUS_A 0x35
+#define SMB347_INTERRUPT_STATUS_B 0x36
+#define SMB347_INTERRUPT_STATUS_C 0x37
+#define SMB347_INTERRUPT_STATUS_D 0x38
+#define SMB347_INTERRUPT_STATUS_E 0x39
+#define SMB347_INTERRUPT_STATUS_F 0x3A
+#define SMB347_STATUS_A 0x3B
+#define SMB347_STATUS_B 0x3C
+#define SMB347_STATUS_C 0x3D
+#define SMB347_STATUS_D 0x3E
+#define SMB347_STATUS_E 0x3F
+
+/* Status register C */
+#define SMB347_CHARGING_ENABLE (1 << 0)
+#define SMB347_CHARGING_STATUS (1 << 5)
+#define SMB347_CHARGER_ERROR (1 << 6)
+
+struct smb347_chg_data {
+ struct i2c_client *client;
+ struct smb_charger_data *pdata;
+ struct smb_charger_callbacks *callbacks;
+};
+
+static struct smb347_chg_data *smb347_chg;
+
+static bool smb347_check_powersource(struct smb347_chg_data *chg)
+{
+#if defined(CONFIG_MACH_P4NOTE)
+ /* p4 note pq has no problem for charger power */
+ return true;
+#endif
+
+ /* Power source needs for only P4C H/W rev0.2 */
+ if (system_rev != 2)
+ return true;
+
+ /* V_BUS detect by TA_nConnected */
+ if (!gpio_get_value(chg->pdata->ta_nconnected)) {
+ pr_err("smb347 power source is not detected\n");
+ return false;
+ }
+
+ return true;
+}
+
+static int smb347_i2c_read(struct i2c_client *client, u8 reg, u8 *data)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ int ret = 0;
+
+ /* Only for P4C rev0.2, Check vbus for opeartion charger */
+ if (!smb347_check_powersource(chg))
+ return -EINVAL;
+
+ if (!client) {
+ pr_err("smb347 i2c client error(addr : 0x%02x)\n", reg);
+ return -ENODEV;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0) {
+ pr_err("smb347 i2c read error(addr : 0x%02x)\n", reg);
+ return -EIO;
+ }
+
+ *data = ret & 0xff;
+ return 0;
+}
+
+static int smb347_i2c_write(struct i2c_client *client, u8 reg, u8 data)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ int ret = 0;
+
+ /* Only for P4C rev0.2, Check vbus for opeartion charger */
+ if (!smb347_check_powersource(chg))
+ return -EINVAL;
+
+ if (!client) {
+ pr_err("smb347 i2c client error(addr:0x%02x data:0x%02x)\n",
+ reg, data);
+ return -ENODEV;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, reg, data);
+ if (ret < 0) {
+ pr_err("smb347 i2c write error(addr:0x%02x data:0x%02x)\n",
+ reg, data);
+ return -EIO;
+ }
+
+ udelay(10);
+ return ret;
+}
+
+static void smb347_test_read(void)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ u8 data = 0;
+ u32 addr = 0;
+ pr_info("%s\n", __func__);
+
+ /* Only for P4C rev0.2, Check vbus for opeartion charger */
+ if (!smb347_check_powersource(chg))
+ return;
+
+ for (addr = 0; addr <= 0x0E; addr++) {
+ smb347_i2c_read(chg->client, addr, &data);
+ pr_info("smb347 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+
+ for (addr = 0x30; addr <= 0x3F; addr++) {
+ smb347_i2c_read(chg->client, addr, &data);
+ pr_info("smb347 addr : 0x%02x data : 0x%02x\n", addr, data);
+ }
+}
+
+static void smb347_enable_charging(struct smb347_chg_data *chg)
+{
+ pr_info("%s\n", __func__);
+ smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x82);
+}
+
+static void smb347_disable_charging(struct smb347_chg_data *chg)
+{
+ pr_info("%s\n", __func__);
+ smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x80);
+}
+
+static void smb347_charger_init(struct smb347_chg_data *chg)
+{
+ pr_info("%s\n", __func__);
+
+ /* Only for P4C rev0.2, Check vbus for opeartion charger */
+ if (!smb347_check_powersource(chg))
+ return;
+
+ /* Set GPIO_TA_EN as HIGH, charging disable */
+ smb347_disable_charging(chg);
+ mdelay(100);
+
+ /* Allow volatile writes to CONFIG registers */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x80);
+
+ /* Command B : USB1 mode, USB mode */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x00);
+
+ /* Charge curr : Fast-chg 2200mA */
+ /* Pre-charge curr 250mA, Term curr 250mA */
+ smb347_i2c_write(chg->client, SMB347_CHARGE_CURRENT, 0xDD);
+
+ /* Pin enable control : Charger enable control EN Pin - I2C */
+ /* : USB5/1/HC or USB9/1.5/HC Control - Register Control */
+ /* : USB5/1/HC Input state - Tri-state Input */
+ smb347_i2c_write(chg->client, SMB347_PIN_ENABLE_CONTROL, 0x00);
+
+ /* Input current limit : DCIN 1800mA, USBIN HC 1800mA */
+ smb347_i2c_write(chg->client, SMB347_INPUT_CURRENTLIMIT, 0x66);
+
+ /* Various func. : USBIN primary input, VCHG func. enable */
+ smb347_i2c_write(chg->client, SMB347_VARIOUS_FUNCTIONS, 0xA7);
+
+ /* Float voltage : 4.2V */
+ smb347_i2c_write(chg->client, SMB347_FLOAT_VOLTAGE, 0x63);
+
+ /* Charge control : Auto recharge disable, APSD disable */
+ smb347_i2c_write(chg->client, SMB347_CHARGE_CONTROL, 0x80);
+
+ /* STAT, Timer control : STAT active low, Complete time out 1527min. */
+ smb347_i2c_write(chg->client, SMB347_STAT_TIMERS_CONTROL, 0x1A);
+
+ /* Therm control : Therm monitor disable */
+ smb347_i2c_write(chg->client, SMB347_THERM_CONTROL_A, 0xBF);
+
+ /* Other control */
+ smb347_i2c_write(chg->client, SMB347_OTHER_CONTROL_A, 0x0D);
+
+ /* OTG tlim therm control */
+ smb347_i2c_write(chg->client, SMB347_OTG_TLIM_THERM_CONTROL, 0x3F);
+
+ /* Limit cell temperature */
+ smb347_i2c_write(chg->client, SMB347_LIMIT_CELL_TEMPERATURE_MONITOR,
+ 0x01);
+
+ /* Fault interrupt : Clear */
+ smb347_i2c_write(chg->client, SMB347_FAULT_INTERRUPT, 0x00);
+
+ /* STATUS ingerrupt : Clear */
+ smb347_i2c_write(chg->client, SMB347_STATUS_INTERRUPT, 0x00);
+}
+
+static int smb347_get_charging_state(void)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ u8 data = 0;
+
+ smb347_i2c_read(chg->client, SMB347_STATUS_C, &data);
+ pr_info("%s : 0x%xh(0x%02x)\n", __func__, SMB347_STATUS_C, data);
+
+ if (data & SMB347_CHARGING_ENABLE)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else {
+ /* if error bit check, ignore the status of charger-ic */
+ if (data & SMB347_CHARGER_ERROR)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ /* At least one charge cycle terminated */
+ /*Charge current < Termination Current */
+ else if (data & SMB347_CHARGING_STATUS)
+ status = POWER_SUPPLY_STATUS_FULL;
+ }
+
+ return status;
+}
+
+static int smb347_get_charger_is_full(void)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ u8 data = 0;
+
+ smb347_i2c_read(chg->client, SMB347_STATUS_C, &data);
+ pr_info("%s : 0x%xh(0x%02x)\n", __func__, SMB347_STATUS_C, data);
+
+ if (data & SMB347_CHARGER_ERROR)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (data & SMB347_CHARGING_STATUS)
+ status = POWER_SUPPLY_STATUS_FULL;
+
+ return status;
+}
+
+static void smb347_set_charging_state(int enable, int charging_mode)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ pr_info("%s : enable(%d), charging_mode(%d)\n",
+ __func__, enable, charging_mode);
+
+ if (enable) {
+ /* Only for P4C rev0.2, Check vbus for opeartion charger */
+ if (!smb347_check_powersource(chg))
+ return;
+
+ /* Init smb347 charger */
+ smb347_charger_init(chg);
+
+ switch (charging_mode) {
+ case CABLE_TYPE_TA:
+ /* Input current limit : DCIN 1800mA, USBIN HC 1800mA */
+ smb347_i2c_write(chg->client,
+ SMB347_INPUT_CURRENTLIMIT, 0x66);
+
+ /* CommandB : High-current mode */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03);
+
+ pr_info("%s : 1.8A charging enable\n", __func__);
+ break;
+ case CABLE_TYPE_DESKDOCK:
+ /* Input current limit : DCIN 1500mA, USBIN HC 1500mA */
+ smb347_i2c_write(chg->client,
+ SMB347_INPUT_CURRENTLIMIT, 0x55);
+
+ /* CommandB : High-current mode */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03);
+ pr_info("%s : 1.5A charging enable\n", __func__);
+ break;
+ case CABLE_TYPE_USB:
+ /* CommandB : USB5 */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x02);
+ pr_info("%s : LOW(USB5) charging enable\n", __func__);
+ break;
+ default:
+ /* CommandB : USB1 */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x00);
+ pr_info("%s : LOW(USB1) charging enable\n", __func__);
+ break;
+ }
+
+ smb347_enable_charging(chg);
+ } else {
+ smb347_disable_charging(chg);
+ }
+
+ smb347_test_read();
+}
+
+int smb347_get_charging_current(void)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+ u8 data = 0;
+ int get_current = 0;
+
+ smb347_i2c_read(chg->client, SMB347_CHARGE_CURRENT, &data);
+ switch (data >> 5) {
+ case 0:
+ get_current = 700;
+ break;
+ case 1:
+ get_current = 900;
+ break;
+ case 2:
+ get_current = 1200;
+ break;
+ case 3:
+ get_current = 1500;
+ break;
+ case 4:
+ get_current = 1800;
+ break;
+ case 5:
+ get_current = 2000;
+ break;
+ case 6:
+ get_current = 2200;
+ break;
+ case 7:
+ get_current = 2500;
+ break;
+ default:
+ get_current = 700;
+ break;
+ }
+ pr_debug("%s: Get charging current as %dmA.\n", __func__, get_current);
+ return get_current;
+}
+
+void smb347_set_charging_current(int set_current)
+{
+ struct smb347_chg_data *chg = smb347_chg;
+
+ if (set_current > 450) {
+ /* CommandB : High-current mode */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03);
+ udelay(10);
+ } else {
+ /* CommandB : USB5 */
+ smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x02);
+ udelay(10);
+ }
+ pr_debug("%s: Set charging current as %dmA.\n", __func__, set_current);
+}
+
+static int smb347_i2c_probe
+(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct smb347_chg_data *chg;
+ int ret = 0;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ pr_info("%s : smb347 Charger Driver Loading\n", __func__);
+
+ chg = kzalloc(sizeof(struct smb347_chg_data), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->callbacks = kzalloc(sizeof(struct smb_charger_callbacks),
+ GFP_KERNEL);
+ if (!chg->callbacks) {
+ kfree(chg);
+ return -ENOMEM;
+ }
+
+ chg->client = client;
+ chg->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chg);
+ smb347_chg = chg;
+
+ if (!chg->pdata) {
+ pr_err("%s : No platform data supplied\n", __func__);
+ ret = -EINVAL;
+ goto err_pdata;
+ }
+
+ pr_info("register callback functions!\n");
+ chg->callbacks->set_charging_state = smb347_set_charging_state;
+ chg->callbacks->get_charging_state = smb347_get_charging_state;
+ chg->callbacks->set_charging_current = smb347_set_charging_current;
+ chg->callbacks->get_charging_current = smb347_get_charging_current;
+ chg->callbacks->get_charger_is_full = smb347_get_charger_is_full;
+ if (chg->pdata && chg->pdata->register_callbacks)
+ chg->pdata->register_callbacks(chg->callbacks);
+
+ pr_info("smb347 charger initialized.\n");
+
+ return 0;
+
+err_pdata:
+ kfree(chg->callbacks);
+ kfree(chg);
+ return ret;
+}
+
+static int __devexit smb347_remove(struct i2c_client *client)
+{
+ struct smb347_chg_data *chg = i2c_get_clientdata(client);
+
+ if (chg->pdata && chg->pdata->unregister_callbacks)
+ chg->pdata->unregister_callbacks();
+
+ kfree(chg);
+ return 0;
+}
+
+static const struct i2c_device_id smb347_id[] = {
+ { "smb347-charger", 0 },
+};
+
+
+static struct i2c_driver smb347_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "smb347-charger",
+ },
+ .probe = smb347_i2c_probe,
+ .remove = smb347_remove,
+ .id_table = smb347_id,
+};
+
+
+MODULE_DEVICE_TABLE(i2c, smb347_id);
+
+static int __init smb347_init(void)
+{
+ return i2c_add_driver(&smb347_i2c_driver);
+}
+
+static void __exit smb347_exit(void)
+{
+ i2c_del_driver(&smb347_i2c_driver);
+}
+
+module_init(smb347_init);
+module_exit(smb347_exit);
+
+MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>");
+MODULE_DESCRIPTION("smb347 charger driver");
+MODULE_LICENSE("GPL");