[9610] drivers/include/dts: s2mu004 mfd/chg/fg bring-up
authorKeunho Hwang <keunho.hwang@samsung.com>
Fri, 11 May 2018 01:19:41 +0000 (10:19 +0900)
committerJaehyoung Choi <jkkkkk.choi@samsung.com>
Fri, 11 May 2018 07:38:04 +0000 (16:38 +0900)
Change-Id: Id05565a585bd71f9830a14e4616dd1eb1ed65fbb
Signed-off-by: Keunho Hwang <keunho.hwang@samsung.com>
13 files changed:
arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/s2mu004_core.c [new file with mode: 0644]
drivers/mfd/s2mu004_irq.c [new file with mode: 0644]
drivers/power/supply/Kconfig
drivers/power/supply/Makefile
drivers/power/supply/s2mu004_charger.c [new file with mode: 0644]
drivers/power/supply/s2mu004_fuelgauge.c [new file with mode: 0644]
include/linux/mfd/samsung/s2mu004-private.h [new file with mode: 0644]
include/linux/mfd/samsung/s2mu004.h [new file with mode: 0644]
include/linux/power/s2mu004_charger.h [new file with mode: 0644]
include/linux/power/s2mu004_fuelgauge.h [new file with mode: 0644]

index 7455bf6bf54e6c7e76d0ffff808411b30414dc23..bfdc6ed6adc3f5292f020d5ec5e46d9df8f695f9 100644 (file)
                status = "okay";
        };
 };
+
+&pinctrl_0 {
+       if_pmic_irq: if-pmic-irq {
+               samsung,pins = "gpa2-1";
+               samsung,pin-function = <0>;
+               samsung,pin-pud = <0>;
+               samsung,pin-drv = <0>;
+       };
+
+       fuel_irq: fuel-irq {
+               samsung,pins = "gpa2-3";
+               samsung,pin-function = <0>;
+               samsung,pin-pud = <0>;
+               samsung,pin-drv = <0>;
+       };
+};
+
+&i2c_0 {
+       #address-cells = <1>;
+       #size-cells = <0>;
+       status = "okay";
+       s2mu004-fuelgauge@3B {
+               compatible = "samsung,s2mu004-fuelgauge";
+               reg = <0x3B>;
+               pinctrl-names = "default";
+               pinctrl-0 = <&fuel_irq>;
+               fuelgauge,fuel_int = <&gpa2 3 0>;
+               fuelgauge,fuel_alert_soc = <1>;
+               fuelgauge,capacity_max = <1000>;
+               fuelgauge,capacity_max_margin = <70>;
+               fuelgauge,capacity_min = <0>;
+               fuelgauge,capacity_calculation_type = <28>;
+               fuelgauge,type_str = "SDI";
+               fuelgauge,model_type = <1>;
+       };
+};
+
+&i2c_1 {
+       #address-cells = <1>;
+       #size-cells = <0>;
+       status = "okay";
+       s2mu004@3d {
+               compatible = "samsung,s2mu004mfd";
+               reg = <0x3d>;
+               pinctrl-names = "default";
+               pinctrl-0 = <&if_pmic_irq>;
+               s2mu004,irq-gpio = <&gpa2 1 0>;
+               s2mu004,wakeup;
+       };
+
+       s2mu004-charger {
+               status = "okay";
+               battery,charger_name = "s2mu004-charger";
+               battery,chg_gpio_en = <0>;
+               battery,chg_polarity_en = <0>;
+               battery,chg_gpio_status = <0>;
+               battery,chg_polarity_status = <0>;
+               battery,chg_float_voltage = <4350>;
+               battery,chg_recharge_vcell = <4250>;
+               battery,chg_full_vcell = <4300>;
+               battery,full_check_type = <2>;
+               battery,full_check_type_2nd = <2>;
+               battery,input_current_limit = <
+                       500 450 500 1200 500 1200 1200 1000 1000 1000
+                       1000 500 500 1200 1000 500 450>;
+               battery,fast_charging_current = <
+                       500 450 500 1200 500 1200 1200 1000 1000 1000
+                       1000 500 500 1200 1000 500 450>;
+               battery,full_check_current_1st = <
+                       300 0 300 300 300 300 300 300 300 300
+                       300 300 300 300 300 300 0>;
+               battery,full_check_current_2nd = <
+                       100 0 100 100 100 100 100 100 100 100
+                       100 100 100 100 100 100 0>;
+       };
+};
index fc5e4fef89d222aa587473634bf056d9dee39578..7ed65424eb5ebe9061f19084f30e8521b0454e7a 100644 (file)
@@ -1002,6 +1002,16 @@ config MFD_SEC_CORE
         additional drivers must be enabled in order to use the functionality
         of the device
 
+config MFD_S2MU004
+       bool "Samsung Electronics S2MU004 IF PMIC Series Support"
+       depends on I2C=y
+       select MFD_CORE
+       help
+         Support for the Samsung Electronics MFD series.
+         This driver provides common support for accessing the device,
+         additional drivers must be enabled in order to use the functionality
+         of the device
+
 config MFD_SI476X_CORE
        tristate "Silicon Laboratories 4761/64/68 AM/FM radio."
        depends on I2C
index 8703ff17998e9119d006978dd5ee8c1aeebf5541..052f7b821ab72dd54a9e09e98f84fe502803745e 100644 (file)
@@ -200,6 +200,7 @@ obj-$(CONFIG_MFD_RC5T583)   += rc5t583.o rc5t583-irq.o
 obj-$(CONFIG_MFD_RK808)                += rk808.o
 obj-$(CONFIG_MFD_RN5T618)      += rn5t618.o
 obj-$(CONFIG_MFD_SEC_CORE)     += sec-core.o sec-irq.o
+obj-$(CONFIG_MFD_S2MU004)      += s2mu004_core.o s2mu004_irq.o
 obj-$(CONFIG_MFD_SYSCON)       += syscon.o
 obj-$(CONFIG_MFD_LM3533)       += lm3533-core.o lm3533-ctrlbank.o
 obj-$(CONFIG_MFD_VEXPRESS_SYSREG)      += vexpress-sysreg.o
diff --git a/drivers/mfd/s2mu004_core.c b/drivers/mfd/s2mu004_core.c
new file mode 100644 (file)
index 0000000..0fc8cd9
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+ * s2mu004.c - mfd core driver for the s2mu004
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ *
+ *
+ * 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
+ *
+ * This driver is based on max77843.c
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/samsung/s2mu004.h>
+#include <linux/mfd/samsung/s2mu004-private.h>
+#include <linux/regulator/machine.h>
+
+#if defined(CONFIG_OF)
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#endif /* CONFIG_OF */
+
+static struct mfd_cell s2mu004_devs[] = {
+#if defined(CONFIG_MUIC_S2MU004)
+       { .name = "s2mu004-muic", },
+#endif /* CONFIG_MUIC_S2MU004 */
+#if defined(CONFIG_AFC_S2MU004)
+       { .name = "s2mu004-afc", },
+#endif /* CONFIG_AFC_S2MU004 */
+#if defined(CONFIG_REGULATOR_S2MU004)
+       { .name = "s2mu004-safeout", },
+#endif /* CONFIG_REGULATOR_S2MU004 */
+#if defined(CONFIG_CHARGER_S2MU004)
+       { .name = "s2mu004-charger", },
+#endif
+#if defined(CONFIG_BATTERY_S2MU00X)
+       { .name = "s2mu00x-battery", },
+#endif
+#if defined(CONFIG_LEDS_S2MU004_RGB)
+       { .name = "leds-s2mu004-rgb", },
+#endif /* CONFIG_LEDS_S2MU004_RGB */
+};
+
+int s2mu004_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_read_byte_data(i2c, reg);
+       mutex_unlock(&s2mu004->i2c_lock);
+       if (ret < 0) {
+               pr_err("%s:%s reg(0x%x), ret(%d)\n", MFD_DEV_NAME,
+                               __func__, reg, ret);
+               return ret;
+       }
+
+       ret &= 0xff;
+       *dest = ret;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(s2mu004_read_reg);
+
+int s2mu004_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf);
+       mutex_unlock(&s2mu004->i2c_lock);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(s2mu004_bulk_read);
+
+int s2mu004_read_word(struct i2c_client *i2c, u8 reg)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_read_word_data(i2c, reg);
+       mutex_unlock(&s2mu004->i2c_lock);
+       if (ret < 0)
+               return ret;
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(s2mu004_read_word);
+
+int s2mu004_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_write_byte_data(i2c, reg, value);
+       mutex_unlock(&s2mu004->i2c_lock);
+       if (ret < 0)
+               pr_err("%s:%s reg(0x%x), ret(%d)\n",
+                               MFD_DEV_NAME, __func__, reg, ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(s2mu004_write_reg);
+
+int s2mu004_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf);
+       mutex_unlock(&s2mu004->i2c_lock);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(s2mu004_bulk_write);
+
+int s2mu004_write_word(struct i2c_client *i2c, u8 reg, u16 value)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_write_word_data(i2c, reg, value);
+       mutex_unlock(&s2mu004->i2c_lock);
+       if (ret < 0)
+               return ret;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(s2mu004_write_word);
+
+int s2mu004_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int ret;
+       u8 old_val, new_val;
+
+       mutex_lock(&s2mu004->i2c_lock);
+       ret = i2c_smbus_read_byte_data(i2c, reg);
+       if (ret >= 0) {
+               old_val = ret & 0xff;
+               new_val = (val & mask) | (old_val & (~mask));
+               ret = i2c_smbus_write_byte_data(i2c, reg, new_val);
+       }
+       mutex_unlock(&s2mu004->i2c_lock);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(s2mu004_update_reg);
+
+#if defined(CONFIG_OF)
+static int of_s2mu004_dt(struct device *dev,
+                               struct s2mu004_platform_data *pdata)
+{
+       struct device_node *np_s2mu004 = dev->of_node;
+       int ret = 0;
+
+       if (!np_s2mu004)
+               return -EINVAL;
+
+       ret = of_get_named_gpio(np_s2mu004, "s2mu004,irq-gpio", 0);
+       if (ret < 0)
+               return ret;
+       else
+               pdata->irq_gpio = ret;
+
+       pdata->wakeup = of_property_read_bool(np_s2mu004, "s2mu004,wakeup");
+
+       pr_debug("%s: irq-gpio: %u\n", __func__, pdata->irq_gpio);
+
+       return 0;
+}
+#else
+static int of_s2mu004_dt(struct device *dev,
+                               struct max77834_platform_data *pdata)
+{
+       return 0;
+}
+#endif /* CONFIG_OF */
+
+static int s2mu004_i2c_probe(struct i2c_client *i2c,
+                               const struct i2c_device_id *dev_id)
+{
+       struct s2mu004_dev *s2mu004;
+       struct s2mu004_platform_data *pdata = i2c->dev.platform_data;
+
+       int ret = 0;
+       u8 temp = 0;
+
+       dev_info(&i2c->dev, "%s start\n", __func__);
+
+       s2mu004 = kzalloc(sizeof(struct s2mu004_dev), GFP_KERNEL);
+       if (!s2mu004) {
+               dev_err(&i2c->dev, "%s: Failed to alloc mem for s2mu004\n",
+                               __func__);
+               return -ENOMEM;
+       }
+
+       if (i2c->dev.of_node) {
+               pdata = devm_kzalloc(&i2c->dev,
+                        sizeof(struct s2mu004_platform_data), GFP_KERNEL);
+               if (!pdata) {
+                       dev_err(&i2c->dev, "Failed to allocate memory\n");
+                       ret = -ENOMEM;
+                       goto err;
+               }
+
+               ret = of_s2mu004_dt(&i2c->dev, pdata);
+               if (ret < 0) {
+                       dev_err(&i2c->dev, "Failed to get device of_node\n");
+                       goto err;
+               }
+
+               i2c->dev.platform_data = pdata;
+       } else
+               pdata = i2c->dev.platform_data;
+
+       s2mu004->dev = &i2c->dev;
+       s2mu004->i2c = i2c;
+       s2mu004->irq = i2c->irq;
+       if (pdata) {
+               s2mu004->pdata = pdata;
+
+               pdata->irq_base = irq_alloc_descs(-1, 0, S2MU004_IRQ_NR, -1);
+               if (pdata->irq_base < 0) {
+                       pr_err("%s:%s irq_alloc_descs Fail! ret(%d)\n",
+                               MFD_DEV_NAME, __func__, pdata->irq_base);
+                       ret = -EINVAL;
+                       goto err;
+               } else
+                       s2mu004->irq_base = pdata->irq_base;
+
+               s2mu004->irq_gpio = pdata->irq_gpio;
+               s2mu004->wakeup = pdata->wakeup;
+       } else {
+               ret = -EINVAL;
+               goto err;
+       }
+       mutex_init(&s2mu004->i2c_lock);
+
+       i2c_set_clientdata(i2c, s2mu004);
+
+       s2mu004_read_reg(s2mu004->i2c, S2MU004_REG_REV_ID, &temp);
+       if (temp < 0)
+               pr_err("[s2mu004 mfd] %s : i2c read error\n", __func__);
+
+       s2mu004->pmic_ver = temp & 0x0F;
+       pr_err("[s2mu004 mfd] %s : ver=0x%x\n", __func__, s2mu004->pmic_ver);
+
+       ret = s2mu004_irq_init(s2mu004);
+
+       if (ret < 0)
+               goto err_irq_init;
+
+       ret = mfd_add_devices(s2mu004->dev, -1, s2mu004_devs,
+                       ARRAY_SIZE(s2mu004_devs), NULL, 0, NULL);
+       if (ret < 0)
+               goto err_mfd;
+
+       device_init_wakeup(s2mu004->dev, pdata->wakeup);
+
+       return ret;
+
+err_mfd:
+       mutex_destroy(&s2mu004->i2c_lock);
+       mfd_remove_devices(s2mu004->dev);
+err_irq_init:
+       i2c_unregister_device(s2mu004->i2c);
+err:
+       kfree(s2mu004);
+       i2c_set_clientdata(i2c, NULL);
+
+       return ret;
+}
+
+static int s2mu004_i2c_remove(struct i2c_client *i2c)
+{
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+
+       mfd_remove_devices(s2mu004->dev);
+       i2c_unregister_device(s2mu004->i2c);
+       kfree(s2mu004);
+       i2c_set_clientdata(i2c, NULL);
+
+       return 0;
+}
+
+static const struct i2c_device_id s2mu004_i2c_id[] = {
+       { MFD_DEV_NAME, TYPE_S2MU004 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, s2mu004_i2c_id);
+
+#if defined(CONFIG_OF)
+static struct of_device_id s2mu004_i2c_dt_ids[] = {
+       {.compatible = "samsung,s2mu004mfd"},
+       { },
+};
+#endif /* CONFIG_OF */
+
+#if defined(CONFIG_PM)
+static int s2mu004_suspend(struct device *dev)
+{
+       struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+
+       if (device_may_wakeup(dev))
+               enable_irq_wake(s2mu004->irq);
+
+       disable_irq(s2mu004->irq);
+
+       return 0;
+}
+
+static int s2mu004_resume(struct device *dev)
+{
+       struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+
+       pr_debug("%s:%s\n", MFD_DEV_NAME, __func__);
+
+       if (device_may_wakeup(dev))
+               disable_irq_wake(s2mu004->irq);
+
+       enable_irq(s2mu004->irq);
+
+       return 0;
+}
+#else
+#define s2mu004_suspend        NULL
+#define s2mu004_resume NULL
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_HIBERNATION
+#if false
+u8 s2mu004_dumpaddr_pmic[] = {
+#if 0
+       s2mu004_LED_REG_IFLASH,
+       s2mu004_LED_REG_IFLASH1,
+       s2mu004_LED_REG_IFLASH2,
+       s2mu004_LED_REG_ITORCH,
+       s2mu004_LED_REG_ITORCHTORCHTIMER,
+       s2mu004_LED_REG_FLASH_TIMER,
+       s2mu004_LED_REG_FLASH_EN,
+       s2mu004_LED_REG_MAX_FLASH1,
+       s2mu004_LED_REG_MAX_FLASH2,
+       s2mu004_LED_REG_VOUT_CNTL,
+       s2mu004_LED_REG_VOUT_FLASH,
+       s2mu004_LED_REG_VOUT_FLASH1,
+       s2mu004_LED_REG_FLASH_INT_STATUS,
+#endif
+       s2mu004_PMIC_REG_PMICID1,
+       s2mu004_PMIC_REG_PMICREV,
+       s2mu004_PMIC_REG_MAINCTRL1,
+       s2mu004_PMIC_REG_MCONFIG,
+};
+#endif
+
+u8 s2mu004_dumpaddr_muic[] = {
+       s2mu004_MUIC_REG_INTMASK1,
+       s2mu004_MUIC_REG_INTMASK2,
+       s2mu004_MUIC_REG_INTMASK3,
+       s2mu004_MUIC_REG_CDETCTRL1,
+       s2mu004_MUIC_REG_CDETCTRL2,
+       s2mu004_MUIC_REG_CTRL1,
+       s2mu004_MUIC_REG_CTRL2,
+       s2mu004_MUIC_REG_CTRL3,
+};
+
+#if false
+u8 s2mu004_dumpaddr_haptic[] = {
+       s2mu004_HAPTIC_REG_CONFIG1,
+       s2mu004_HAPTIC_REG_CONFIG2,
+       s2mu004_HAPTIC_REG_CONFIG_CHNL,
+       s2mu004_HAPTIC_REG_CONFG_CYC1,
+       s2mu004_HAPTIC_REG_CONFG_CYC2,
+       s2mu004_HAPTIC_REG_CONFIG_PER1,
+       s2mu004_HAPTIC_REG_CONFIG_PER2,
+       s2mu004_HAPTIC_REG_CONFIG_PER3,
+       s2mu004_HAPTIC_REG_CONFIG_PER4,
+       s2mu004_HAPTIC_REG_CONFIG_DUTY1,
+       s2mu004_HAPTIC_REG_CONFIG_DUTY2,
+       s2mu004_HAPTIC_REG_CONFIG_PWM1,
+       s2mu004_HAPTIC_REG_CONFIG_PWM2,
+       s2mu004_HAPTIC_REG_CONFIG_PWM3,
+       s2mu004_HAPTIC_REG_CONFIG_PWM4,
+};
+#endif
+
+u8 s2mu004_dumpaddr_led[] = {
+       s2mu004_RGBLED_REG_LEDEN,
+       s2mu004_RGBLED_REG_LED0BRT,
+       s2mu004_RGBLED_REG_LED1BRT,
+       s2mu004_RGBLED_REG_LED2BRT,
+       s2mu004_RGBLED_REG_LED3BRT,
+       s2mu004_RGBLED_REG_LEDBLNK,
+       s2mu004_RGBLED_REG_LEDRMP,
+};
+
+static int s2mu004_freeze(struct device *dev)
+{
+       struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_pmic); i++)
+               s2mu004_read_reg(i2c, s2mu004_dumpaddr_pmic[i],
+                               &s2mu004->reg_pmic_dump[i]);
+
+       for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_muic); i++)
+               s2mu004_read_reg(i2c, s2mu004_dumpaddr_muic[i],
+                               &s2mu004->reg_muic_dump[i]);
+
+       for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_led); i++)
+               s2mu004_read_reg(i2c, s2mu004_dumpaddr_led[i],
+                               &s2mu004->reg_led_dump[i]);
+
+       disable_irq(s2mu004->irq);
+
+       return 0;
+}
+
+static int s2mu004_restore(struct device *dev)
+{
+       struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+       struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c);
+       int i;
+
+       enable_irq(s2mu004->irq);
+
+       for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_pmic); i++)
+               s2mu004_write_reg(i2c, s2mu004_dumpaddr_pmic[i],
+                               s2mu004->reg_pmic_dump[i]);
+
+       for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_muic); i++)
+               s2mu004_write_reg(i2c, s2mu004_dumpaddr_muic[i],
+                               s2mu004->reg_muic_dump[i]);
+
+       for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_led); i++)
+               s2mu004_write_reg(i2c, s2mu004_dumpaddr_led[i],
+                               s2mu004->reg_led_dump[i]);
+
+       return 0;
+}
+#endif
+
+const struct dev_pm_ops s2mu004_pm = {
+       .suspend = s2mu004_suspend,
+       .resume = s2mu004_resume,
+#ifdef CONFIG_HIBERNATION
+       .freeze =  s2mu004_freeze,
+       .thaw = s2mu004_restore,
+       .restore = s2mu004_restore,
+#endif
+};
+
+static struct i2c_driver s2mu004_i2c_driver = {
+       .driver         = {
+               .name   = MFD_DEV_NAME,
+               .owner  = THIS_MODULE,
+#if defined(CONFIG_PM)
+               .pm     = &s2mu004_pm,
+#endif /* CONFIG_PM */
+#if defined(CONFIG_OF)
+               .of_match_table = s2mu004_i2c_dt_ids,
+#endif /* CONFIG_OF */
+       },
+       .probe          = s2mu004_i2c_probe,
+       .remove         = s2mu004_i2c_remove,
+       .id_table       = s2mu004_i2c_id,
+};
+
+static int __init s2mu004_i2c_init(void)
+{
+       pr_info("%s:%s\n", MFD_DEV_NAME, __func__);
+       return i2c_add_driver(&s2mu004_i2c_driver);
+}
+/* init early so consumer devices can complete system boot */
+subsys_initcall(s2mu004_i2c_init);
+
+static void __exit s2mu004_i2c_exit(void)
+{
+       i2c_del_driver(&s2mu004_i2c_driver);
+}
+module_exit(s2mu004_i2c_exit);
+
+MODULE_DESCRIPTION("s2mu004 multi-function core driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/s2mu004_irq.c b/drivers/mfd/s2mu004_irq.c
new file mode 100644 (file)
index 0000000..d725b8c
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * s2mu004-irq.c - Interrupt controller support for s2mu004
+ *
+ * Copyright (C) 2015 Samsung Electronics Co.Ltd
+ *
+ *
+ * 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
+ *
+ * This driver is based on s2mu004-irq.c
+ */
+
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/mfd/samsung/s2mu004.h>
+#include <linux/mfd/samsung/s2mu004-private.h>
+
+static const u8 s2mu004_mask_reg[] = {
+       [CHG_INT1] = S2MU004_REG_SC_INT1_MASK,
+       [CHG_INT2] = S2MU004_REG_SC_INT2_MASK,
+#if defined(CONFIG_MUIC_S2MU004_HV)
+       [AFC_INT] = S2MU004_REG_AFC_INT_MASK,
+#endif
+       [MUIC_INT1] = S2MU004_REG_MUIC_INT1_MASK,
+       [MUIC_INT2] = S2MU004_REG_MUIC_INT2_MASK,
+};
+
+struct s2mu004_irq_data {
+       int mask;
+       enum s2mu004_irq_source group;
+};
+
+#define DECLARE_IRQ(idx, _group, _mask)                \
+       [(idx)] = { .group = (_group), .mask = (_mask) }
+
+static const struct s2mu004_irq_data s2mu004_irqs[] = {
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_SYS,       CHG_INT1,       1 << 0),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_Poor_CHG,  CHG_INT1,       1 << 1),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_CHG_Fault, CHG_INT1,       1 << 2),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_CHG_RSTART, CHG_INT1,      1 << 3),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_DONE,      CHG_INT1,       1 << 4),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_TOP_OFF,   CHG_INT1,       1 << 5),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_WCIN,      CHG_INT1,       1 << 6),
+       DECLARE_IRQ(S2MU004_CHG1_IRQ_CHGIN,     CHG_INT1,       1 << 7),
+
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_ICR,       CHG_INT2,       1 << 0),
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_IVR,       CHG_INT2,       1 << 1),
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_AICL,      CHG_INT2,       1 << 2),
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_TX_Fault,  CHG_INT2,       1 << 3),
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_OTG_Fault, CHG_INT2,       1 << 4),
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_DET_BAT,   CHG_INT2,       1 << 5),
+       DECLARE_IRQ(S2MU004_CHG2_IRQ_BAT,       CHG_INT2,       1 << 6),
+
+#if defined(CONFIG_HV_MUIC_S2MU004_AFC)
+       DECLARE_IRQ(S2MU004_AFC_IRQ_VbADC,      AFC_INT,        1 << 0),
+       DECLARE_IRQ(S2MU004_AFC_IRQ_VDNMon,     AFC_INT,        1 << 1),
+       DECLARE_IRQ(S2MU004_AFC_IRQ_DNRes,      AFC_INT,        1 << 2),
+       DECLARE_IRQ(S2MU004_AFC_IRQ_MPNack,     AFC_INT,        1 << 3),
+       DECLARE_IRQ(S2MU004_AFC_IRQ_MRxTrf,     AFC_INT,        1 << 5),
+       DECLARE_IRQ(S2MU004_AFC_IRQ_MRxPerr,    AFC_INT,        1 << 6),
+       DECLARE_IRQ(S2MU004_AFC_IRQ_MRxRdy,     AFC_INT,        1 << 7),
+#endif
+       DECLARE_IRQ(S2MU004_MUIC_IRQ1_ATTATCH,  MUIC_INT1,      1 << 0),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ1_DETACH,   MUIC_INT1,      1 << 1),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ1_KP,       MUIC_INT1,      1 << 2),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ1_LKP,      MUIC_INT1,      1 << 3),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ1_LKR,      MUIC_INT1,      1 << 4),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ1_RID_CHG,  MUIC_INT1,      1 << 5),
+
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_VBUS_ON,          MUIC_INT2, 1 << 0),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_RSVD_ATTACH,      MUIC_INT2, 1 << 1),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_ADC_CHANGE,       MUIC_INT2, 1 << 2),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_STUCK,            MUIC_INT2, 1 << 3),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_STUCKRCV,         MUIC_INT2, 1 << 4),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_MHDL,             MUIC_INT2, 1 << 5),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_AV_CHARGE,        MUIC_INT2, 1 << 6),
+       DECLARE_IRQ(S2MU004_MUIC_IRQ2_VBUS_OFF,         MUIC_INT2, 1 << 7),
+};
+
+static void s2mu004_irq_lock(struct irq_data *data)
+{
+       struct s2mu004_dev *s2mu004 = irq_get_chip_data(data->irq);
+
+       mutex_lock(&s2mu004->irqlock);
+}
+
+static void s2mu004_irq_sync_unlock(struct irq_data *data)
+{
+       struct s2mu004_dev *s2mu004 = irq_get_chip_data(data->irq);
+       int i;
+
+       for (i = 0; i < S2MU004_IRQ_GROUP_NR; i++) {
+               u8 mask_reg = s2mu004_mask_reg[i];
+               struct i2c_client *i2c = s2mu004->i2c;
+
+               if (mask_reg == S2MU004_REG_INVALID ||
+                               IS_ERR_OR_NULL(i2c))
+                       continue;
+               s2mu004->irq_masks_cache[i] = s2mu004->irq_masks_cur[i];
+
+               s2mu004_write_reg(i2c, s2mu004_mask_reg[i],
+                               s2mu004->irq_masks_cur[i]);
+       }
+
+       mutex_unlock(&s2mu004->irqlock);
+}
+
+static const inline struct s2mu004_irq_data *
+irq_to_s2mu004_irq(struct s2mu004_dev *s2mu004, int irq)
+{
+       return &s2mu004_irqs[irq - s2mu004->irq_base];
+}
+
+static void s2mu004_irq_mask(struct irq_data *data)
+{
+       struct s2mu004_dev *s2mu004 = irq_get_chip_data(data->irq);
+       const struct s2mu004_irq_data *irq_data =
+           irq_to_s2mu004_irq(s2mu004, data->irq);
+
+       if (irq_data->group >= S2MU004_IRQ_GROUP_NR)
+               return;
+
+       s2mu004->irq_masks_cur[irq_data->group] |= irq_data->mask;
+}
+
+static void s2mu004_irq_unmask(struct irq_data *data)
+{
+       struct s2mu004_dev *s2mu004 = irq_get_chip_data(data->irq);
+       const struct s2mu004_irq_data *irq_data =
+           irq_to_s2mu004_irq(s2mu004, data->irq);
+
+       if (irq_data->group >= S2MU004_IRQ_GROUP_NR)
+               return;
+
+       s2mu004->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
+}
+
+static struct irq_chip s2mu004_irq_chip = {
+       .name                   = MFD_DEV_NAME,
+       .irq_bus_lock   = s2mu004_irq_lock,
+       .irq_bus_sync_unlock    = s2mu004_irq_sync_unlock,
+       .irq_mask               = s2mu004_irq_mask,
+       .irq_unmask             = s2mu004_irq_unmask,
+};
+
+static irqreturn_t s2mu004_irq_thread(int irq, void *data)
+{
+       struct s2mu004_dev *s2mu004 = data;
+       u8 irq_reg[S2MU004_IRQ_GROUP_NR] = {0};
+       int i, ret;
+       u8 temp, temp_2;
+#if defined(CONFIG_MUIC_S2MU004_HV)
+       u8 temp_vdadc;
+#endif
+
+       pr_debug("%s: irq gpio pre-state(0x%02x)\n", __func__,
+                       gpio_get_value(s2mu004->irq_gpio));
+
+       /* CHG_INT1 ~ INT2 */
+       ret = s2mu004_read_reg(s2mu004->i2c, S2MU004_REG_SC_INT1,
+                               &irq_reg[CHG_INT1]);
+       if (irq_reg[CHG_INT1])
+               pr_info("%s: charger interrupt(0x%02x)\n",
+                               __func__, irq_reg[CHG_INT1]);
+
+       ret = s2mu004_read_reg(s2mu004->i2c, S2MU004_REG_SC_INT2,
+                               &irq_reg[CHG_INT2]);
+       if (irq_reg[CHG_INT2])
+               pr_info("%s: charger interrupt(0x%02x)\n",
+                               __func__, irq_reg[CHG_INT2]);
+
+#if defined(CONFIG_MUIC_S2MU004_HV)
+       /* AFC_INT */
+       ret = s2mu004_read_reg(s2mu004->i2c, S2MU004_REG_AFC_INT,
+                               &irq_reg[AFC_INT]);
+       if (irq_reg[AFC_INT]) {
+               pr_info("%s: AFC interrupt(0x%02x)\n",
+                               __func__, irq_reg[AFC_INT]);
+
+               ret = s2mu004_read_reg(s2mu004->i2c, 0x48,
+                               &temp_vdadc);
+               pr_info("%s: 0x48 (0x%02x)\n",
+                               __func__, temp_vdadc);
+       }
+#endif
+
+       /* MUIC INT1 ~ INT2 */
+       ret = s2mu004_bulk_read(s2mu004->i2c, S2MU004_REG_MUIC_INT1,
+                               S2MU004_NUM_IRQ_MUIC_REGS, &irq_reg[MUIC_INT1]);
+       if (irq_reg[MUIC_INT1] || irq_reg[MUIC_INT2])
+               pr_info("%s: muic interrupt(0x%02x, 0x%02x)\n", __func__,
+                               irq_reg[MUIC_INT1], irq_reg[MUIC_INT2]);
+
+       if (s2mu004->pmic_rev == 0) {
+               s2mu004_read_reg(s2mu004->i2c, S2MU004_REG_MUIC_ADC, &temp);
+               temp &= 0x1F;
+               /* checking VBUS_WAKEUP bit of R(0x61) */
+               s2mu004_read_reg(s2mu004->i2c, 0x69, &temp_2);
+               if ((temp_2 & 0x02) && (temp != 0x18)
+                       && (temp != 0x19) && (temp != 0x1C) && (temp != 0x1D))
+                       s2mu004_update_reg(s2mu004->i2c, 0x89, 0x01, 0x03);
+               if (irq_reg[MUIC_INT2] & 0x80)
+                       s2mu004_update_reg(s2mu004->i2c, 0x89, 0x03, 0x03);
+       }
+
+       /* Apply masking */
+       for (i = 0; i < S2MU004_IRQ_GROUP_NR; i++)
+               irq_reg[i] &= ~s2mu004->irq_masks_cur[i];
+
+       /* Report */
+       for (i = 0; i < S2MU004_IRQ_NR; i++) {
+               if (irq_reg[s2mu004_irqs[i].group] & s2mu004_irqs[i].mask)
+                       handle_nested_irq(s2mu004->irq_base + i);
+       }
+
+       return IRQ_HANDLED;
+}
+static int irq_is_enable = true;
+int s2mu004_irq_init(struct s2mu004_dev *s2mu004)
+{
+       int i;
+       int ret;
+       struct i2c_client *i2c = s2mu004->i2c;
+       int cur_irq;
+
+       if (!s2mu004->irq_gpio) {
+               dev_warn(s2mu004->dev, "No interrupt specified.\n");
+               s2mu004->irq_base = 0;
+               return 0;
+       }
+
+       if (!s2mu004->irq_base) {
+               dev_err(s2mu004->dev, "No interrupt base specified.\n");
+               return 0;
+       }
+
+       mutex_init(&s2mu004->irqlock);
+
+       s2mu004->irq = gpio_to_irq(s2mu004->irq_gpio);
+       pr_err("%s:%s irq=%d, irq->gpio=%d\n", MFD_DEV_NAME, __func__,
+                       s2mu004->irq, s2mu004->irq_gpio);
+
+       ret = gpio_request(s2mu004->irq_gpio, "if_pmic_irq");
+       if (ret) {
+               dev_err(s2mu004->dev, "%s: failed requesting gpio %d\n",
+                       __func__, s2mu004->irq_gpio);
+               return ret;
+       }
+       gpio_direction_input(s2mu004->irq_gpio);
+       gpio_free(s2mu004->irq_gpio);
+
+       /* Mask individual interrupt sources */
+       for (i = 0; i < S2MU004_IRQ_GROUP_NR; i++) {
+
+               s2mu004->irq_masks_cur[i] = 0xff;
+               s2mu004->irq_masks_cache[i] = 0xff;
+
+               if (IS_ERR_OR_NULL(i2c))
+                       continue;
+               if (s2mu004_mask_reg[i] == S2MU004_REG_INVALID)
+                       continue;
+               s2mu004_write_reg(i2c, s2mu004_mask_reg[i], 0xff);
+       }
+
+       /* Register with genirq */
+       for (i = 0; i < S2MU004_IRQ_NR; i++) {
+               cur_irq = 0;
+               cur_irq = i + s2mu004->irq_base;
+               irq_set_chip_data(cur_irq, s2mu004);
+               irq_set_chip_and_handler(cur_irq, &s2mu004_irq_chip,
+                                               handle_level_irq);
+               irq_set_nested_thread(cur_irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(cur_irq, IRQF_VALID);
+#else
+               irq_set_noprobe(cur_irq);
+#endif
+       }
+
+       if (irq_is_enable) {
+               ret = request_threaded_irq(s2mu004->irq, NULL,
+                               s2mu004_irq_thread,
+                               IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                               "s2mu004-irq", s2mu004);
+       }
+
+       if (ret) {
+               dev_err(s2mu004->dev, "Failed to request IRQ %d: %d\n",
+                       s2mu004->irq, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+void s2mu004_irq_exit(struct s2mu004_dev *s2mu004)
+{
+       if (s2mu004->irq)
+               free_irq(s2mu004->irq, s2mu004);
+}
index 5ab90c1f3f7c47f0ecbb47fc9c3fd0d1820ca84c..f6cd93a340b4fd615248f1fe72e791f580b24e2d 100644 (file)
@@ -610,4 +610,20 @@ config CHARGER_RT9455
        help
          Say Y to enable support for Richtek RT9455 battery charger.
 
+config FUELGAUGE_S2MU004
+    tristate "s2mu004 fuel gauge driver"
+    default n
+    depends on (MFD_S2MU004) && I2C
+    help
+      say y to include support
+      for s.lsi s2mu004 fuel gauge driver.
+
+config CHARGER_S2MU004
+    tristate "S2MU004 charger support"
+    depends on (MFD_S2MU004) && I2C
+    help
+      Say Y here to enable support for the S2MU004 charger
+      S2MU004 incluse pmic, led driver.
+      You have to define MFD_S2MU004
+
 endif # POWER_SUPPLY
index aae4e4a8bbb358d588b8858789e2c003eee8a71b..dddc844e2a6980a30435f201ff223f8c816678bc 100644 (file)
@@ -82,3 +82,5 @@ obj-$(CONFIG_CHARGER_TPS65090)        += tps65090-charger.o
 obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
 obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
 obj-$(CONFIG_AXP288_CHARGER)   += axp288_charger.o
+obj-$(CONFIG_FUELGAUGE_S2MU004) += s2mu004_fuelgauge.o
+obj-$(CONFIG_CHARGER_S2MU004)   += s2mu004_charger.o
diff --git a/drivers/power/supply/s2mu004_charger.c b/drivers/power/supply/s2mu004_charger.c
new file mode 100644 (file)
index 0000000..1ce826d
--- /dev/null
@@ -0,0 +1,1247 @@
+/*
+ * s2mu004_charger.c - S2MU004 Charger Driver
+ *
+ * Copyright (C) 2016 Samsung Electronics Co.Ltd
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include <linux/mfd/samsung/s2mu004.h>
+#include <linux/power/s2mu004_charger.h>
+#include <linux/version.h>
+//#include <linux/muic/muic.h>
+
+#define ENABLE_MIVR 0
+
+#define EN_OVP_IRQ 1
+#define EN_IEOC_IRQ 1
+#define EN_TOPOFF_IRQ 1
+#define EN_RECHG_REQ_IRQ 0
+#define EN_TR_IRQ 0
+#define EN_MIVR_SW_REGULATION 0
+#define EN_BST_IRQ 0
+#define EN_BAT_DET_IRQ 0
+#define MINVAL(a, b) ((a <= b) ? a : b)
+
+#define EOC_DEBOUNCE_CNT 2
+#define HEALTH_DEBOUNCE_CNT 1
+#define DEFAULT_CHARGING_CURRENT 500
+
+#define EOC_SLEEP 200
+#define EOC_TIMEOUT (EOC_SLEEP * 6)
+#ifndef EN_TEST_READ
+#define EN_TEST_READ 1
+#endif
+
+#define ENABLE 1
+#define DISABLE 0
+
+static char *s2mu004_supplied_to[] = {
+       "battery",
+};
+
+static enum power_supply_property s2mu004_charger_props[] = {
+};
+
+static enum power_supply_property s2mu004_otg_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int s2mu004_get_charging_health(struct s2mu004_charger_data *charger);
+
+static void s2mu004_test_read(struct i2c_client *i2c)
+{
+       u8 data;
+       char str[1016] = {0,};
+       int i;
+
+       for (i = 0x0A; i <= 0x24; i++) {
+               s2mu004_read_reg(i2c, i, &data);
+
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+       s2mu004_read_reg(i2c, 0x33, &data);
+       pr_err("%s: %s0x33:0x%02x\n", __func__, str, data);
+}
+
+static int s2mu004_charger_otg_control(
+               struct s2mu004_charger_data *charger, bool enable)
+{
+       u8 chg_sts2, chg_ctrl0, temp;
+       pr_info("%s: called charger otg control : %s\n", __func__,
+                       enable ? "ON" : "OFF");
+
+       if (charger->is_charging) {
+               pr_info("%s: Charger is enabled and OTG noti received!!!\n", __func__);
+               pr_info("%s: is_charging: %d, otg_on: %d",
+                               __func__, charger->is_charging, charger->otg_on);
+               s2mu004_test_read(charger->i2c);
+               return 0;
+       }
+
+       if (charger->otg_on == enable)
+               return 0;
+
+       mutex_lock(&charger->charger_mutex);
+       if (!enable) {
+               s2mu004_update_reg(charger->i2c,
+                               S2MU004_CHG_CTRL0, CHG_MODE, REG_MODE_MASK);
+               s2mu004_update_reg(charger->i2c, 0xAE, 0x80, 0xF0);
+       } else {
+               s2mu004_update_reg(charger->i2c,
+                               S2MU004_CHG_CTRL4,
+                               S2MU004_SET_OTG_OCP_1500mA << SET_OTG_OCP_SHIFT,
+                               SET_OTG_OCP_MASK);
+               msleep(30);
+               s2mu004_update_reg(charger->i2c, 0xAE, 0x00, 0xF0);
+               s2mu004_update_reg(charger->i2c,
+                               S2MU004_CHG_CTRL0, OTG_BST_MODE, REG_MODE_MASK);
+               charger->cable_type = POWER_SUPPLY_TYPE_OTG;
+       }
+       charger->otg_on = enable;
+       mutex_unlock(&charger->charger_mutex);
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS2, &chg_sts2);
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL0, &chg_ctrl0);
+       s2mu004_read_reg(charger->i2c, 0xAE, &temp);
+       pr_info("%s S2MU004_CHG_STATUS2: 0x%x\n", __func__, chg_sts2);
+       pr_info("%s S2MU004_CHG_CTRL0: 0x%x\n", __func__, chg_ctrl0);
+       pr_info("%s 0xAE: 0x%x\n", __func__, temp);
+
+       power_supply_changed(charger->psy_otg);
+       return enable;
+}
+
+static void s2mu004_enable_charger_switch(
+       struct s2mu004_charger_data *charger, int onoff)
+{
+       if (charger->otg_on) {
+               pr_info("[DEBUG] %s: skipped set(%d) : OTG is on\n", __func__, onoff);
+               return;
+       }
+
+       if (onoff > 0) {
+               pr_info("[DEBUG]%s: turn on charger\n", __func__);
+
+               /* forced ASYNC */
+               s2mu004_update_reg(charger->i2c, 0x30, 0x03, 0x03);
+
+               mdelay(30);
+
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL0, CHG_MODE, REG_MODE_MASK);
+
+               /* timer fault set 16hr(max) */
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL16,
+                               S2MU004_FC_CHG_TIMER_16hr << SET_TIME_CHG_SHIFT,
+                               SET_TIME_CHG_MASK);
+
+               mdelay(100);
+
+               /* Auto SYNC to ASYNC - default */
+               s2mu004_update_reg(charger->i2c, 0x30, 0x01, 0x03);
+
+               /* async off */
+               s2mu004_update_reg(charger->i2c, 0x96, 0x00, 0x01 << 3);
+       } else {
+               pr_info("[DEBUG] %s: turn off charger\n", __func__);
+
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL0, BUCK_MODE, REG_MODE_MASK);
+
+               /* async on */
+               s2mu004_update_reg(charger->i2c, 0x96, 0x01 << 3, 0x01 << 3);
+               mdelay(100);
+       }
+}
+
+static void s2mu004_set_buck(
+       struct s2mu004_charger_data *charger, int enable) {
+
+       if (enable) {
+               pr_info("[DEBUG]%s: set buck on\n", __func__);
+               s2mu004_enable_charger_switch(charger, charger->is_charging);
+       } else {
+               pr_info("[DEBUG]%s: set buck off (charger off mode)\n", __func__);
+
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL0, CHARGER_OFF_MODE, REG_MODE_MASK);
+
+               /* async on */
+               s2mu004_update_reg(charger->i2c, 0x96, 0x01 << 3, 0x01 << 3);
+               mdelay(100);
+       }
+}
+
+static void s2mu004_set_regulation_voltage(
+               struct s2mu004_charger_data *charger, int float_voltage)
+{
+       u8 data;
+
+       pr_info("[DEBUG]%s: float_voltage %d\n", __func__, float_voltage);
+       if (float_voltage <= 3900)
+               data = 0;
+       else if (float_voltage > 3900 && float_voltage <= 4530)
+               data = (float_voltage - 3900) / 10;
+       else
+               data = 0x3f;
+
+       s2mu004_update_reg(charger->i2c,
+                       S2MU004_CHG_CTRL6, data << SET_VF_VBAT_SHIFT, SET_VF_VBAT_MASK);
+}
+
+static int s2mu004_get_regulation_voltage(struct s2mu004_charger_data *charger)
+{
+       u8 reg_data = 0;
+       int float_voltage;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL6, &reg_data);
+       reg_data &= 0x3F;
+       float_voltage = reg_data * 10 + 3900;
+       pr_debug("%s: battery cv reg : 0x%x, float voltage val : %d\n",
+                       __func__, reg_data, float_voltage);
+
+       return float_voltage;
+}
+
+static void s2mu004_set_input_current_limit(
+               struct s2mu004_charger_data *charger, int charging_current)
+{
+       u8 data;
+
+       if (charging_current <= 100)
+               data = 0x02;
+       else if (charging_current > 100 && charging_current <= 2500)
+               data = (charging_current - 50) / 25;
+       else
+               data = 0x62;
+
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL2,
+                       data << INPUT_CURRENT_LIMIT_SHIFT, INPUT_CURRENT_LIMIT_MASK);
+
+       pr_info("[DEBUG]%s: current  %d, 0x%x\n", __func__, charging_current, data);
+
+#if EN_TEST_READ
+       s2mu004_test_read(charger->i2c);
+#endif
+}
+
+static int s2mu004_get_input_current_limit(struct s2mu004_charger_data *charger)
+{
+       u8 data;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL2, &data);
+       if (data < 0)
+               return data;
+
+       data = data & INPUT_CURRENT_LIMIT_MASK;
+       if (data > 0x62) {
+               pr_err("%s: Invalid current limit in register\n", __func__);
+               data = 0x62;
+       }
+       return  data * 25 + 50;
+}
+
+static void s2mu004_set_fast_charging_current(
+               struct s2mu004_charger_data *charger, int charging_current)
+{
+       u8 data;
+
+       if (charging_current <= 100)
+               data = 0x03;
+       else if (charging_current > 100 && charging_current <= 3150)
+               data = (charging_current / 25) - 1;
+       else
+               data = 0x7D;
+
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL9,
+                       data << FAST_CHARGING_CURRENT_SHIFT, FAST_CHARGING_CURRENT_MASK);
+
+       pr_info("[DEBUG]%s: current  %d, 0x%02x\n", __func__, charging_current, data);
+
+       if (data > 0x11)
+               data = 0x11; /* 0x11 : 450mA */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL8,
+                       data << COOL_CHARGING_CURRENT_SHIFT, COOL_CHARGING_CURRENT_MASK);
+
+#if EN_TEST_READ
+       s2mu004_test_read(charger->i2c);
+#endif
+}
+
+static int s2mu004_get_fast_charging_current(
+               struct s2mu004_charger_data *charger)
+{
+       u8 data;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL9, &data);
+       if (data < 0)
+               return data;
+
+       data = data & FAST_CHARGING_CURRENT_MASK;
+
+       if (data > 0x7D) {
+               pr_err("%s: Invalid fast charging current in register\n", __func__);
+               data = 0x7D;
+       }
+       return (data + 1) * 25;
+}
+
+static void s2mu004_set_topoff_current(
+               struct s2mu004_charger_data *charger,
+               int eoc_1st_2nd, int current_limit)
+{
+       int data;
+
+       pr_info("[DEBUG]%s: current  %d\n", __func__, current_limit);
+       if (current_limit <= 100)
+               data = 0;
+       else if (current_limit > 100 && current_limit <= 475)
+               data = (current_limit - 100) / 25;
+       else
+               data = 0x0F;
+
+       switch (eoc_1st_2nd) {
+       case 1:
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL11,
+                               data << FIRST_TOPOFF_CURRENT_SHIFT, FIRST_TOPOFF_CURRENT_MASK);
+               break;
+       default:
+               break;
+       }
+}
+
+static int s2mu004_get_topoff_setting(
+               struct s2mu004_charger_data *charger)
+{
+       u8 data;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL11, &data);
+       if (data < 0)
+               return data;
+
+       data = data & FIRST_TOPOFF_CURRENT_MASK;
+
+       if (data > 0x0F)
+               data = 0x0F;
+       return data * 25 + 100;
+}
+
+enum {
+       S2MU004_CHG_2L_IVR_4300MV = 0,
+       S2MU004_CHG_2L_IVR_4500MV,
+       S2MU004_CHG_2L_IVR_4700MV,
+       S2MU004_CHG_2L_IVR_4900MV,
+};
+
+#if ENABLE_MIVR
+/* charger input regulation voltage setting */
+static void s2mu004_set_ivr_level(struct s2mu004_charger_data *charger)
+{
+       int chg_2l_ivr = S2MU004_CHG_2L_IVR_4500MV;
+
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL5,
+                       chg_2l_ivr << SET_CHG_2L_DROP_SHIFT, SET_CHG_2L_DROP_MASK);
+}
+#endif /*ENABLE_MIVR*/
+
+static bool s2mu004_chg_init(struct s2mu004_charger_data *charger)
+{
+       u8 temp;
+
+       /* Read Charger IC Dev ID */
+       s2mu004_read_reg(charger->i2c, S2MU004_REG_REV_ID, &temp);
+       charger->dev_id = (temp & 0xF0) >> 4;
+
+       pr_info("%s : DEV ID : 0x%x\n", __func__, charger->dev_id);
+
+       /* Poor-Chg-INT Masking */
+       s2mu004_update_reg(charger->i2c, 0x32, 0x03, 0x03);
+
+       /*
+        * When Self Discharge Function is activated, Charger doesn't stop charging.
+        * If you write 0xb0[4]=1, charger will stop the charging, when self discharge
+        * condition is satisfied.
+        */
+       s2mu004_update_reg(charger->i2c, 0xb0, 0x0, 0x1 << 4);
+
+       s2mu004_update_reg(charger->i2c, S2MU004_REG_SC_INT1_MASK,
+                       Poor_CHG_INT_MASK, Poor_CHG_INT_MASK);
+
+       s2mu004_write_reg(charger->i2c, 0x02, 0x0);
+       s2mu004_write_reg(charger->i2c, 0x03, 0x0);
+
+       /* ready for self-discharge, 0x76 */
+       s2mu004_update_reg(charger->i2c, S2MU004_REG_SELFDIS_CFG3,
+                       SELF_DISCHG_MODE_MASK, SELF_DISCHG_MODE_MASK);
+
+       /* Set Top-Off timer to 30 minutes */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL17,
+                       S2MU004_TOPOFF_TIMER_30m << TOP_OFF_TIME_SHIFT,
+                       TOP_OFF_TIME_MASK);
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL17, &temp);
+       pr_info("%s : S2MU004_CHG_CTRL17 : 0x%x\n", __func__, temp);
+
+       /* enable Watchdog timer and only Charging off */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL13,
+                       ENABLE << SET_EN_WDT_SHIFT | DISABLE << SET_EN_WDT_AP_RESET_SHIFT,
+                       SET_EN_WDT_MASK | SET_EN_WDT_AP_RESET_MASK);
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL13, &temp);
+       pr_info("%s : S2MU004_CHG_CTRL13 : 0x%x\n", __func__, temp);
+
+       /* set watchdog timer to 80 seconds */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL17,
+                       S2MU004_WDT_TIMER_80s << WDT_TIME_SHIFT,
+                       WDT_TIME_MASK);
+
+       /* IVR Recovery enable */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL13,
+                       0x1 << SET_IVR_Recovery_SHIFT, SET_IVR_Recovery_MASK);
+
+       /* Boost OSC 1Mhz */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL15,
+                       0x02 << SET_OSC_BST_SHIFT, SET_OSC_BST_MASK);
+
+       /* QBAT switch speed config */
+       s2mu004_update_reg(charger->i2c, 0xB2, 0x0, 0xf << 4);
+
+       /* Top off debounce time set 1 sec */
+       s2mu004_update_reg(charger->i2c, 0xC0, 0x3 << 6, 0x3 << 6);
+
+       /* SC_CTRL21 register Minumum Charging OCP Level set to 6A */
+       s2mu004_write_reg(charger->i2c, 0x29, 0x04);
+
+       switch (charger->pdata->chg_switching_freq) {
+       case S2MU004_OSC_BUCK_FRQ_750kHz:
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL12,
+                               S2MU004_OSC_BUCK_FRQ_750kHz << SET_OSC_BUCK_SHIFT, SET_OSC_BUCK_MASK);
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL12,
+                               S2MU004_OSC_BUCK_FRQ_750kHz << SET_OSC_BUCK_3L_SHIFT, SET_OSC_BUCK_3L_MASK);
+               break;
+       default:
+               /* Set OSC BUCK/BUCK 3L frequencies to default 1MHz */
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL12,
+                               S2MU004_OSC_BUCK_FRQ_1MHz << SET_OSC_BUCK_SHIFT, SET_OSC_BUCK_MASK);
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL12,
+                               S2MU004_OSC_BUCK_FRQ_1MHz << SET_OSC_BUCK_3L_SHIFT, SET_OSC_BUCK_3L_MASK);
+               break;
+       }
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL12, &temp);
+       pr_info("%s : S2MU004_CHG_CTRL12 : 0x%x\n", __func__, temp);
+
+       return true;
+}
+
+static int s2mu004_get_charging_status(
+               struct s2mu004_charger_data *charger)
+{
+       int status = POWER_SUPPLY_STATUS_UNKNOWN;
+       int ret;
+       u8 chg_sts0, chg_sts1;
+       union power_supply_propval value;
+       struct power_supply *psy;
+
+       ret = s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS0, &chg_sts0);
+       ret = s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS1, &chg_sts1);
+
+       psy = power_supply_get_by_name(charger->pdata->fuelgauge_name);
+       if (!psy)
+               return -EINVAL;
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_AVG, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+
+       if (ret < 0)
+               return status;
+
+       if (chg_sts1 & 0x80)
+               status = POWER_SUPPLY_STATUS_DISCHARGING;
+       else if (chg_sts1 & 0x02 || chg_sts1 & 0x01) {
+               pr_info("%s: full check curr_avg(%d), topoff_curr(%d)\n",
+                               __func__, value.intval, charger->topoff_current);
+               if (value.intval < charger->topoff_current)
+                       status = POWER_SUPPLY_STATUS_FULL;
+               else
+                       status = POWER_SUPPLY_STATUS_CHARGING;
+       } else if ((chg_sts0 & 0xE0) == 0xA0 || (chg_sts0 & 0xE0) == 0x60)
+               status = POWER_SUPPLY_STATUS_CHARGING;
+       else
+               status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+#if EN_TEST_READ
+       s2mu004_test_read(charger->i2c);
+#endif
+       return status;
+}
+
+static int s2mu004_get_charge_type(struct s2mu004_charger_data *charger)
+{
+       int status = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+       u8 ret;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS3, &ret);
+       if (ret < 0)
+               pr_err("%s fail\n", __func__);
+
+       switch ((ret & BAT_STATUS_MASK) >> BAT_STATUS_SHIFT) {
+       case 0x4:
+       case 0x5:
+               status = POWER_SUPPLY_CHARGE_TYPE_FAST;
+               break;
+       case 0x2:
+               /* pre-charge mode */
+               status = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+               break;
+       }
+       return status;
+}
+
+static bool s2mu004_get_batt_present(struct s2mu004_charger_data *charger)
+{
+       u8 ret;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS3, &ret);
+       if (ret < 0)
+               return false;
+
+       return (ret & DET_BAT_STATUS_MASK) ? true : false;
+}
+
+static void s2mu004_wdt_clear(struct s2mu004_charger_data *charger)
+{
+       u8 reg_data, chg_fault_status, en_chg;
+
+       /* watchdog kick */
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL14,
+                       0x1 << WDT_CLR_SHIFT, WDT_CLR_MASK);
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS1, &reg_data);
+       chg_fault_status = (reg_data & CHG_FAULT_STATUS_MASK) >> CHG_FAULT_STATUS_SHIFT;
+
+       if ((chg_fault_status == CHG_STATUS_WD_SUSPEND) ||
+                       (chg_fault_status == CHG_STATUS_WD_RST)) {
+               pr_info("%s: watchdog error status(0x%02x,%d)\n",
+                               __func__, reg_data, chg_fault_status);
+               if (charger->is_charging) {
+                       pr_info("%s: toggle charger\n", __func__);
+                       s2mu004_enable_charger_switch(charger, false);
+                       s2mu004_enable_charger_switch(charger, true);
+               }
+       }
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL0, &en_chg);
+       if (!(en_chg & 0x80))
+               s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL0,
+                               0x1 << EN_CHG_SHIFT, EN_CHG_MASK);
+}
+
+static int s2mu004_get_charging_health(struct s2mu004_charger_data *charger)
+{
+
+       u8 ret;
+       union power_supply_propval value;
+       struct power_supply *psy;
+
+       if (charger->is_charging)
+               s2mu004_wdt_clear(charger);
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS0, &ret);
+       pr_info("[DEBUG] %s: S2MU004_CHG_STATUS0 0x%x\n", __func__, ret);
+       if (ret < 0)
+               return POWER_SUPPLY_HEALTH_UNKNOWN;
+
+       ret = (ret & (CHGIN_STATUS_MASK)) >> CHGIN_STATUS_SHIFT;
+
+       switch (ret) {
+       case 0x03:
+       case 0x05:
+               charger->ovp = false;
+               charger->unhealth_cnt = 0;
+               return POWER_SUPPLY_HEALTH_GOOD;
+       default:
+               break;
+       }
+
+       charger->unhealth_cnt++;
+       if (charger->unhealth_cnt < HEALTH_DEBOUNCE_CNT)
+               return POWER_SUPPLY_HEALTH_GOOD;
+
+       /* 005 need to check ovp & health count */
+       charger->unhealth_cnt = HEALTH_DEBOUNCE_CNT;
+       if (charger->ovp)
+               return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+
+       psy = power_supply_get_by_name("battery");
+       if (!psy)
+               return -EINVAL;
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+
+       if (value.intval == POWER_SUPPLY_TYPE_USB_PD)
+               return POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
+       else
+               return POWER_SUPPLY_HEALTH_GOOD;
+
+#if EN_TEST_READ
+       s2mu004_test_read(charger->i2c);
+#endif
+}
+
+static int s2mu004_chg_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       int chg_curr, aicr;
+       struct s2mu004_charger_data *charger =
+               power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = charger->is_charging ? 1 : 0;
+               break;
+       case POWER_SUPPLY_PROP_STATUS:
+               val->intval = s2mu004_get_charging_status(charger);
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               val->intval = s2mu004_get_charging_health(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               val->intval = s2mu004_get_input_current_limit(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               if (charger->charging_current) {
+                       aicr = s2mu004_get_input_current_limit(charger);
+                       chg_curr = s2mu004_get_fast_charging_current(charger);
+                       val->intval = MINVAL(aicr, chg_curr);
+               } else
+                       val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+               val->intval = s2mu004_get_fast_charging_current(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_FULL:
+               val->intval = s2mu004_get_topoff_setting(charger);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               val->intval = s2mu004_get_charge_type(charger);
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+               val->intval = s2mu004_get_regulation_voltage(charger);
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = s2mu004_get_batt_present(charger);
+               break;
+       case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+               val->intval = charger->is_charging;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mu004_chg_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct s2mu004_charger_data *charger = power_supply_get_drvdata(psy);
+       int buck_state = ENABLE;
+       union power_supply_propval value;
+       int ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               charger->status = val->intval;
+               break;
+               /* val->intval : type */
+       case POWER_SUPPLY_PROP_ONLINE:
+               charger->cable_type = val->intval;
+               if (charger->cable_type != POWER_SUPPLY_TYPE_OTG) {
+                       if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY ||
+                       charger->cable_type == POWER_SUPPLY_TYPE_UNKNOWN) {
+                               pr_err("[DEBUG]%s:[BATT] Type Battery\n", __func__);
+                               value.intval = 0;
+                       } else {
+#if ENABLE_MIVR
+                               s2mu004_set_ivr_level(charger);
+#endif
+                               value.intval = 1;
+                       }
+
+                       psy = power_supply_get_by_name(charger->pdata->fuelgauge_name);
+                       if (!psy)
+                               return -EINVAL;
+                       ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_ENERGY_AVG, &value);
+                       if (ret < 0)
+                               pr_err("%s: Fail to execute property\n", __func__);
+               }
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               {
+                       int input_current = val->intval;
+
+                       s2mu004_set_input_current_limit(charger, input_current);
+                       charger->input_current = input_current;
+               }
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               pr_info("[DEBUG] %s: is_charging %d\n", __func__, charger->is_charging);
+               charger->charging_current = val->intval;
+               /* set charging current */
+               if (charger->is_charging)
+                       s2mu004_set_fast_charging_current(charger, charger->charging_current);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_FULL:
+               charger->topoff_current = val->intval;
+               s2mu004_set_topoff_current(charger, 1, val->intval);
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+               pr_info("[DEBUG]%s: float voltage(%d)\n", __func__, val->intval);
+               charger->pdata->chg_float_voltage = val->intval;
+               s2mu004_set_regulation_voltage(charger,
+                               charger->pdata->chg_float_voltage);
+       case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL:
+               s2mu004_charger_otg_control(charger, val->intval);
+               break;
+       case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+               charger->charge_mode = val->intval;
+
+               psy = power_supply_get_by_name("battery");
+               if (!psy)
+                       return -EINVAL;
+               ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &value);
+               if (ret < 0)
+                       pr_err("%s: Fail to execute property\n", __func__);
+
+               if (value.intval != POWER_SUPPLY_TYPE_OTG) {
+                       switch (charger->charge_mode) {
+                       case S2MU00X_BAT_CHG_MODE_BUCK_OFF:
+                               buck_state = DISABLE;
+                       case S2MU00X_BAT_CHG_MODE_CHARGING_OFF:
+                               charger->is_charging = false;
+                               break;
+                       case S2MU00X_BAT_CHG_MODE_CHARGING:
+                               charger->is_charging = true;
+                               break;
+                       }
+                       value.intval = charger->is_charging;
+
+                       psy = power_supply_get_by_name(charger->pdata->fuelgauge_name);
+                       if (!psy)
+                               return -EINVAL;
+                       ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGING_ENABLED, &value);
+                       if (ret < 0)
+                               pr_err("%s: Fail to execute property\n", __func__);
+
+                       if (buck_state)
+                               s2mu004_enable_charger_switch(charger, charger->is_charging);
+                       else
+                               s2mu004_set_buck(charger, buck_state);
+               } else {
+                       pr_info("[DEBUG]%s: SKIP CHARGING CONTROL while OTG(%d)\n",
+                                       __func__, value.intval);
+               }
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_NOW:
+               break;
+       case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION:
+               if (val->intval) {
+                       pr_debug("%s: Relieve VBUS2BAT\n", __func__);
+                       s2mu004_write_reg(charger->i2c, 0x2F, 0x5D);
+               }
+               break;
+       case POWER_SUPPLY_PROP_FUELGAUGE_RESET:
+               s2mu004_write_reg(charger->i2c, 0x6F, 0xC4);
+               msleep(1000);
+               s2mu004_write_reg(charger->i2c, 0x6F, 0x04);
+               msleep(50);
+               pr_info("%s: reset fuelgauge when surge occur!\n", __func__);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mu004_otg_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct s2mu004_charger_data *charger = power_supply_get_drvdata(psy);
+       u8 reg;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = charger->otg_on;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_POWERED_OTG_CONTROL:
+               s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS2, &reg);
+               pr_info("%s: S2MU004_CHG_STATUS2 : 0x%X\n", __func__, reg);
+               if ((reg & 0xE0) == 0x60)
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               s2mu004_read_reg(charger->i2c, S2MU004_CHG_CTRL0, &reg);
+               pr_info("%s: S2MU004_CHG_CTRL0 : 0x%X\n", __func__, reg);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int s2mu004_otg_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct s2mu004_charger_data *charger =  power_supply_get_drvdata(psy);
+       union power_supply_propval value;
+       int ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               value.intval = val->intval;
+               pr_info("%s: OTG %s\n", __func__, value.intval > 0 ? "ON" : "OFF");
+
+               psy = power_supply_get_by_name(charger->pdata->charger_name);
+               if (!psy)
+                       return -EINVAL;
+               ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, &value);
+               if (ret < 0)
+                       pr_err("%s: Fail to execute property\n", __func__);
+
+               power_supply_changed(charger->psy_otg);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static void s2mu004_charger_otg_vbus_work(struct work_struct *work)
+{
+       struct s2mu004_charger_data *charger = container_of(work,
+                       struct s2mu004_charger_data,
+                       otg_vbus_work.work);
+
+       s2mu004_update_reg(charger->i2c, S2MU004_CHG_CTRL7, 0x2 << SET_VF_VBYP_SHIFT, SET_VF_VBYP_MASK);
+}
+
+#if EN_BAT_DET_IRQ
+/* s2mu004 interrupt service routine */
+static irqreturn_t s2mu004_det_bat_isr(int irq, void *data)
+{
+       struct s2mu004_charger_data *charger = data;
+       u8 val;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS3, &val);
+       if ((val & DET_BAT_STATUS_MASK) == 0) {
+               s2mu004_enable_charger_switch(charger, 0);
+               pr_err("charger-off if battery removed\n");
+       }
+       return IRQ_HANDLED;
+}
+#endif
+
+static irqreturn_t s2mu004_done_isr(int irq, void *data)
+{
+       struct s2mu004_charger_data *charger = data;
+       u8 val;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS1, &val);
+       pr_info("%s , %02x\n", __func__, val);
+       if (val & (DONE_STATUS_MASK)) {
+               pr_err("add self chg done\n");
+               /* add chg done code here */
+       }
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t s2mu004_chg_isr(int irq, void *data)
+{
+       struct s2mu004_charger_data *charger = data;
+       union power_supply_propval value;
+       u8 val;
+       struct power_supply *psy;
+       int ret;
+
+       value.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS0, &val);
+       pr_info("%s , %02x\n", __func__, val);
+#if EN_OVP_IRQ
+       if ((val & CHGIN_STATUS_MASK) == (2 << CHGIN_STATUS_SHIFT)) {
+               charger->ovp = true;
+               pr_info("%s: OVP triggered\n", __func__);
+               value.intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+               s2mu004_update_reg(charger->i2c, 0xBE, 0x10, 0x10);
+       } else if ((val & CHGIN_STATUS_MASK) == (3 << CHGIN_STATUS_SHIFT) ||
+                       (val & CHGIN_STATUS_MASK) == (5 << CHGIN_STATUS_SHIFT)) {
+               dev_dbg(&charger->i2c->dev, "%s: Vbus status 0x%x\n", __func__, val);
+               charger->unhealth_cnt = HEALTH_DEBOUNCE_CNT;
+               charger->ovp = false;
+               pr_info("%s: recover from OVP\n", __func__);
+               value.intval = POWER_SUPPLY_HEALTH_GOOD;
+               s2mu004_update_reg(charger->i2c, 0xBE, 0x00, 0x10);
+       }
+
+       psy = power_supply_get_by_name("battery");
+       if (!psy)
+               return -EINVAL;
+       ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_HEALTH, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+
+#endif
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t s2mu004_event_isr(int irq, void *data)
+{
+       struct s2mu004_charger_data *charger = data;
+       u8 val;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS0, &val);
+       pr_info("%s , %02x\n", __func__, val);
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t s2mu004_ovp_isr(int irq, void *data)
+{
+       struct s2mu004_charger_data *charger = data;
+       u8 val;
+
+       s2mu004_read_reg(charger->i2c, S2MU004_CHG_STATUS0, &val);
+       pr_info("%s ovp %02x\n", __func__, val);
+
+       return IRQ_HANDLED;
+}
+
+static int s2mu004_charger_parse_dt(struct device *dev,
+               struct s2mu004_charger_platform_data *pdata)
+{
+       struct device_node *np = of_find_node_by_name(NULL, "s2mu004-charger");
+       int ret = 0;
+
+       if (!np) {
+               pr_err("%s np NULL(s2mu004-charger)\n", __func__);
+       } else {
+               ret = of_property_read_u32(np, "battery,chg_switching_freq",
+                               &pdata->chg_switching_freq);
+               if (ret < 0)
+                       pr_info("%s: Charger switching FRQ is Empty\n", __func__);
+       }
+
+       np = of_find_node_by_name(NULL, "battery");
+       if (!np) {
+               pr_err("%s np NULL\n", __func__);
+       } else {
+               int len;
+               unsigned int i;
+               const u32 *p;
+
+               ret = of_property_read_string(np,
+                               "battery,fuelgauge_name",
+                               (char const **)&pdata->fuelgauge_name);
+               if (ret < 0)
+                       pr_info("%s: Fuel-gauge name is Empty\n", __func__);
+
+               ret = of_property_read_u32(np, "battery,chg_float_voltage",
+                               &pdata->chg_float_voltage);
+               if (ret) {
+                       pr_info("%s: battery,chg_float_voltage is Empty\n", __func__);
+                       pdata->chg_float_voltage = 4200;
+               }
+               pr_info("%s: battery,chg_float_voltage is %d\n",
+                               __func__, pdata->chg_float_voltage);
+
+               pdata->chg_eoc_dualpath = of_property_read_bool(np,
+                               "battery,chg_eoc_dualpath");
+
+               p = of_get_property(np, "battery,input_current_limit", &len);
+               if (!p)
+                       return 1;
+
+               len = len / sizeof(u32);
+
+               pdata->charging_current =
+                       kzalloc(sizeof(s2mu00x_charging_current_t) * len,
+                                       GFP_KERNEL);
+
+               for (i = 0; i < len; i++) {
+                       ret = of_property_read_u32_index(np,
+                                       "battery,input_current_limit", i,
+                                       &pdata->charging_current[i].input_current_limit);
+                       if (ret)
+                               pr_info("%s : Input_current_limit is Empty\n",
+                                               __func__);
+
+                       ret = of_property_read_u32_index(np,
+                                       "battery,fast_charging_current", i,
+                                       &pdata->charging_current[i].fast_charging_current);
+                       if (ret)
+                               pr_info("%s : Fast charging current is Empty\n",
+                                               __func__);
+
+                       ret = of_property_read_u32_index(np,
+                                       "battery,full_check_current", i,
+                                       &pdata->charging_current[i].full_check_current);
+                       if (ret)
+                               pr_info("%s : Full check current is Empty\n",
+                                               __func__);
+               }
+       }
+
+       pr_info("%s DT file parsed succesfully, %d\n", __func__, ret);
+       return ret;
+}
+
+/* if need to set s2mu004 pdata */
+static const struct of_device_id s2mu004_charger_match_table[] = {
+       { .compatible = "samsung,s2mu004-charger",},
+       {},
+};
+
+static int s2mu004_charger_probe(struct platform_device *pdev)
+{
+       struct s2mu004_dev *s2mu004 = dev_get_drvdata(pdev->dev.parent);
+       struct s2mu004_platform_data *pdata = dev_get_platdata(s2mu004->dev);
+       struct s2mu004_charger_data *charger;
+       struct power_supply_config psy_cfg = {};
+       int ret = 0;
+
+       pr_info("%s:[BATT] S2MU004 Charger driver probe\n", __func__);
+       charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+       if (!charger)
+               return -ENOMEM;
+
+       mutex_init(&charger->charger_mutex);
+       charger->otg_on = false;
+
+       charger->dev = &pdev->dev;
+       charger->i2c = s2mu004->i2c;
+
+       charger->pdata = devm_kzalloc(&pdev->dev, sizeof(*(charger->pdata)),
+                       GFP_KERNEL);
+       if (!charger->pdata) {
+               ret = -ENOMEM;
+               goto err_parse_dt_nomem;
+       }
+       ret = s2mu004_charger_parse_dt(&pdev->dev, charger->pdata);
+       if (ret < 0)
+               goto err_parse_dt;
+
+       platform_set_drvdata(pdev, charger);
+
+       if (charger->pdata->charger_name == NULL)
+               charger->pdata->charger_name = "s2mu004-charger";
+       if (charger->pdata->fuelgauge_name == NULL)
+               charger->pdata->fuelgauge_name = "s2mu004-fuelgauge";
+
+       charger->psy_chg_desc.name           = charger->pdata->charger_name;
+       charger->psy_chg_desc.type           = POWER_SUPPLY_TYPE_UNKNOWN;
+       charger->psy_chg_desc.get_property   = s2mu004_chg_get_property;
+       charger->psy_chg_desc.set_property   = s2mu004_chg_set_property;
+       charger->psy_chg_desc.properties     = s2mu004_charger_props;
+       charger->psy_chg_desc.num_properties = ARRAY_SIZE(s2mu004_charger_props);
+
+       charger->psy_otg_desc.name           = "otg";
+       charger->psy_otg_desc.type           = POWER_SUPPLY_TYPE_UNKNOWN;
+       charger->psy_otg_desc.get_property   = s2mu004_otg_get_property;
+       charger->psy_otg_desc.set_property   = s2mu004_otg_set_property;
+       charger->psy_otg_desc.properties     = s2mu004_otg_props;
+       charger->psy_otg_desc.num_properties = ARRAY_SIZE(s2mu004_otg_props);
+
+       s2mu004_chg_init(charger);
+       charger->input_current = s2mu004_get_input_current_limit(charger);
+       charger->charging_current = s2mu004_get_fast_charging_current(charger);
+
+       psy_cfg.drv_data = charger;
+       psy_cfg.supplied_to = s2mu004_supplied_to;
+       psy_cfg.num_supplicants = ARRAY_SIZE(s2mu004_supplied_to);
+
+       charger->psy_chg = power_supply_register(&pdev->dev, &charger->psy_chg_desc, &psy_cfg);
+       if (IS_ERR(charger->psy_chg)) {
+               pr_err("%s: Failed to Register psy_chg\n", __func__);
+               ret = PTR_ERR(charger->psy_chg);
+               goto err_power_supply_register;
+       }
+
+       charger->psy_otg = power_supply_register(&pdev->dev, &charger->psy_otg_desc, &psy_cfg);
+       if (IS_ERR(charger->psy_otg)) {
+               pr_err("%s: Failed to Register psy_otg\n", __func__);
+               ret = PTR_ERR(charger->psy_otg);
+               goto err_power_supply_register_otg;
+       }
+
+       charger->charger_wqueue = create_singlethread_workqueue("charger-wq");
+       if (!charger->charger_wqueue) {
+               pr_info("%s: failed to create wq.\n", __func__);
+               ret = -ESRCH;
+               goto err_create_wq;
+       }
+
+       /*
+        * irq request
+        * if you need to add irq , please refer below code.
+        */
+       charger->irq_sys = pdata->irq_base + S2MU004_CHG1_IRQ_SYS;
+       ret = request_threaded_irq(charger->irq_sys, NULL,
+                       s2mu004_ovp_isr, 0, "sys-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu004->dev, "%s: Fail to request SYS in IRQ: %d: %d\n",
+                               __func__, charger->irq_sys, ret);
+               goto err_reg_irq;
+       }
+
+#if EN_BAT_DET_IRQ
+       charger->irq_det_bat = pdata->irq_base + S2MU004_CHG2_IRQ_DET_BAT;
+       ret = request_threaded_irq(charger->irq_det_bat, NULL,
+                       s2mu004_det_bat_isr, 0, "det_bat-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu004->dev, "%s: Fail to request DET_BAT in IRQ: %d: %d\n",
+                               __func__, charger->irq_det_bat, ret);
+               goto err_reg_irq;
+       }
+#endif
+
+       charger->irq_chgin = pdata->irq_base + S2MU004_CHG1_IRQ_CHGIN;
+       ret = request_threaded_irq(charger->irq_chgin, NULL,
+                       s2mu004_chg_isr, 0, "chgin-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu004->dev, "%s: Fail to request CHGIN in IRQ: %d: %d\n",
+                               __func__, charger->irq_chgin, ret);
+               goto err_reg_irq;
+       }
+
+       charger->irq_rst = pdata->irq_base + S2MU004_CHG1_IRQ_CHG_RSTART;
+       ret = request_threaded_irq(charger->irq_rst, NULL,
+                       s2mu004_chg_isr, 0, "restart-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu004->dev, "%s: Fail to request CHG_Restart in IRQ: %d: %d\n",
+                               __func__, charger->irq_rst, ret);
+               goto err_reg_irq;
+       }
+
+       charger->irq_done = pdata->irq_base + S2MU004_CHG1_IRQ_DONE;
+       ret = request_threaded_irq(charger->irq_done, NULL,
+                       s2mu004_done_isr, 0, "done-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu004->dev, "%s: Fail to request DONE in IRQ: %d: %d\n",
+                               __func__, charger->irq_done, ret);
+               goto err_reg_irq;
+       }
+
+       charger->irq_chg_fault = pdata->irq_base + S2MU004_CHG1_IRQ_CHG_Fault;
+       ret = request_threaded_irq(charger->irq_chg_fault, NULL,
+                       s2mu004_event_isr, 0, "chg_fault-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu004->dev, "%s: Fail to request CHG_Fault in IRQ: %d: %d\n",
+                               __func__, charger->irq_chg_fault, ret);
+               goto err_reg_irq;
+       }
+
+       INIT_DELAYED_WORK(&charger->otg_vbus_work, s2mu004_charger_otg_vbus_work);
+
+#if EN_TEST_READ
+       s2mu004_test_read(charger->i2c);
+#endif
+       pr_info("%s:[BATT] S2MU004 charger driver loaded OK\n", __func__);
+
+       return 0;
+
+err_reg_irq:
+       destroy_workqueue(charger->charger_wqueue);
+err_create_wq:
+       power_supply_unregister(charger->psy_otg);
+err_power_supply_register_otg:
+       power_supply_unregister(charger->psy_chg);
+err_power_supply_register:
+err_parse_dt:
+err_parse_dt_nomem:
+       mutex_destroy(&charger->charger_mutex);
+       kfree(charger);
+       return ret;
+}
+
+static int s2mu004_charger_remove(struct platform_device *pdev)
+{
+       struct s2mu004_charger_data *charger =
+               platform_get_drvdata(pdev);
+
+       power_supply_unregister(charger->psy_chg);
+       mutex_destroy(&charger->charger_mutex);
+       kfree(charger);
+       return 0;
+}
+
+#if defined CONFIG_PM
+static int s2mu004_charger_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int s2mu004_charger_resume(struct device *dev)
+{
+       return 0;
+}
+#else
+#define s2mu004_charger_suspend NULL
+#define s2mu004_charger_resume NULL
+#endif
+
+static void s2mu004_charger_shutdown(struct device *dev)
+{
+       pr_info("%s: S2MU004 Charger driver shutdown\n", __func__);
+}
+
+static SIMPLE_DEV_PM_OPS(s2mu004_charger_pm_ops, s2mu004_charger_suspend,
+               s2mu004_charger_resume);
+
+static struct platform_driver s2mu004_charger_driver = {
+       .driver         = {
+               .name   = "s2mu004-charger",
+               .owner  = THIS_MODULE,
+               .of_match_table = s2mu004_charger_match_table,
+               .pm     = &s2mu004_charger_pm_ops,
+               .shutdown   =   s2mu004_charger_shutdown,
+       },
+       .probe          = s2mu004_charger_probe,
+       .remove     = s2mu004_charger_remove,
+};
+
+static int __init s2mu004_charger_init(void)
+{
+       int ret = 0;
+
+       ret = platform_driver_register(&s2mu004_charger_driver);
+
+       return ret;
+}
+module_init(s2mu004_charger_init);
+
+static void __exit s2mu004_charger_exit(void)
+{
+       platform_driver_unregister(&s2mu004_charger_driver);
+}
+module_exit(s2mu004_charger_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_DESCRIPTION("Charger driver for S2MU004");
diff --git a/drivers/power/supply/s2mu004_fuelgauge.c b/drivers/power/supply/s2mu004_fuelgauge.c
new file mode 100644 (file)
index 0000000..6019fa5
--- /dev/null
@@ -0,0 +1,1585 @@
+/*
+ *  s2mu004_fuelgauge.c
+ *  Samsung S2MU004 Fuel Gauge Driver
+ *
+ *  Copyright (C) 2015 Samsung Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define DEBUG
+
+#define SINGLE_BYTE    1
+#define TABLE_SIZE     22
+#define FAKE_TEMP 1
+
+#include <linux/power/s2mu004_fuelgauge.h>
+#include <linux/of_gpio.h>
+
+static enum power_supply_property s2mu004_fuelgauge_props[] = {
+};
+
+static int s2mu004_get_vbat(struct s2mu004_fuelgauge_data *fuelgauge);
+static int s2mu004_get_ocv(struct s2mu004_fuelgauge_data *fuelgauge);
+static int s2mu004_get_current(struct s2mu004_fuelgauge_data *fuelgauge);
+static int s2mu004_get_avgcurrent(struct s2mu004_fuelgauge_data *fuelgauge);
+static int s2mu004_get_avgvbat(struct s2mu004_fuelgauge_data *fuelgauge);
+static int s2mu004_get_monout_avgvbat(struct s2mu004_fuelgauge_data *fuelgauge);
+
+static int s2mu004_write_reg_byte(struct i2c_client *client, int reg, u8 data)
+{
+       int ret, i = 0;
+
+       ret = i2c_smbus_write_byte_data(client, reg, data);
+       if (ret < 0) {
+               for (i = 0; i < 3; i++) {
+                       ret = i2c_smbus_write_byte_data(client, reg,  data);
+                       if (ret >= 0)
+                               break;
+               }
+
+               if (i >= 3)
+                       dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret);
+       }
+
+       return ret;
+}
+
+static int s2mu004_write_reg(struct i2c_client *client, int reg, u8 *buf)
+{
+#if SINGLE_BYTE
+       int ret = 0;
+
+       s2mu004_write_reg_byte(client, reg, buf[0]);
+       s2mu004_write_reg_byte(client, reg+1, buf[1]);
+#else
+       int ret, i = 0;
+
+       ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf);
+       if (ret < 0) {
+               for (i = 0; i < 3; i++) {
+                       ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf);
+                       if (ret >= 0)
+                               break;
+               }
+
+               if (i >= 3)
+                       dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret);
+       }
+#endif
+       return ret;
+}
+
+static int s2mu004_read_reg_byte(struct i2c_client *client, int reg, void *data)
+{
+       int ret = 0;
+
+       ret = i2c_smbus_read_byte_data(client, reg);
+       if (ret < 0)
+               return ret;
+       *(u8 *)data = (u8)ret;
+
+       return ret;
+}
+
+static int s2mu004_read_reg(struct i2c_client *client, int reg, u8 *buf)
+{
+#if SINGLE_BYTE
+       int ret = 0;
+       u8 data1 = 0, data2 = 0;
+
+       s2mu004_read_reg_byte(client, reg, &data1);
+       s2mu004_read_reg_byte(client, reg+1, &data2);
+       buf[0] = data1;
+       buf[1] = data2;
+#else
+       int ret = 0, i = 0;
+
+       ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf);
+       if (ret < 0) {
+               for (i = 0; i < 3; i++) {
+                       ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf);
+                       if (ret >= 0)
+                               break;
+               }
+
+               if (i >= 3)
+                       dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret);
+       }
+#endif
+       return ret;
+}
+
+static void s2mu004_fg_test_read(struct i2c_client *client)
+{
+               u8 data;
+               char str[1016] = {0,};
+               int i;
+
+       /* address 0x00 ~ 0x1f */
+       for (i = 0x0; i <= 0x1F; i++) {
+               s2mu004_read_reg_byte(client, i, &data);
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+
+       /* address 0x25 */
+       s2mu004_read_reg_byte(client, 0x25, &data);
+       sprintf(str+strlen(str), "0x25:0x%02x, ", data);
+
+       /* address 0x27 */
+       s2mu004_read_reg_byte(client, 0x27, &data);
+       sprintf(str+strlen(str), "0x27:0x%02x, ", data);
+       /* print buffer */
+       pr_info("[FG]%s: %s\n", __func__, str);
+}
+
+static void WA_0_issue_at_init(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       int a = 0;
+       u8 v_4e = 0, v_4f = 0, temp1, temp2;
+       int FG_volt, UI_volt, offset;
+       u8 v_40 = 0;
+       u8 temp_REG26 = 0, temp_REG27 = 0, temp = 0;
+
+       /* Step 1: [Surge test]  get UI voltage (0.1mV)*/
+       UI_volt = s2mu004_get_ocv(fuelgauge);
+
+       /* current fix for soc */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x27, &temp_REG27);
+       temp = temp_REG27;
+       temp |= 0x0F;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x26, &temp_REG26);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x26, 0xFF);
+
+       /* avgvbat factor value set to 0xFF  */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x40, &v_40);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, 0xFF);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+       msleep(50);
+
+       /* Step 2: [Surge test] get FG voltage (0.1mV) */
+       FG_volt = s2mu004_get_vbat(fuelgauge) * 10;
+
+       /* Step 3: [Surge test] get offset */
+       offset = UI_volt - FG_volt;
+       pr_err("%s: UI_volt(%d), FG_volt(%d), offset(%d)\n",
+                       __func__, UI_volt, FG_volt, offset);
+
+       /* Step 4: [Surge test] */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4f, &v_4f);
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4e, &v_4e);
+       pr_err("%s: v_4f(0x%x), v_4e(0x%x)\n", __func__, v_4f, v_4e);
+
+       a = (v_4f & 0x0F) << 8;
+       a += v_4e;
+       pr_err("%s: a before add offset (0x%x)\n", __func__, a);
+
+       /* 2`s complement */
+       if (a & (0x01 << 11))
+               a = (-10000 * ((a^0xFFF) + 1)) >> 13;
+       else
+               a = (10000 * a) >> 13;
+
+       a = a + offset;
+       pr_err("%s: a after add offset (0x%x)\n", __func__, a);
+
+       /* limit upper/lower offset */
+       if (a > 2490)
+               a = 2490;
+
+       if (a < (-2490))
+               a = -2490;
+
+       a = (a << 13) / 10000;
+       if (a < 0)
+               a = -1*((a^0xFFF)+1);
+
+       pr_err("%s: a after add offset (0x%x)\n", __func__, a);
+
+       a &= 0xfff;
+       pr_err("%s: (a)&0xFFF (0x%x)\n", __func__, a);
+
+       /* modify 0x4f[3:0] */
+       temp1 = v_4f & 0xF0;
+       temp2 = (u8)((a&0xF00) >> 8);
+       temp1 |= temp2;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4f, temp1);
+
+       /* modify 0x4e[7:0] */
+       temp2 = (u8)(a & 0xFF);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4e, temp2);
+
+       /* restart and dumpdone */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+       msleep(300);
+
+       /* restore current register */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp_REG27);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x26, temp_REG26);
+
+       /* recovery 0x4e and 0x4f */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4f, &temp1);
+       temp1 &= 0xF0;
+       temp1 |= (v_4f & 0x0F);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4f, temp1);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4e, v_4e);
+
+       /* restore monout avgvbat factor value */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, v_40);
+}
+
+static int s2mu004_get_soc_from_ocv(struct s2mu004_fuelgauge_data *fuelgauge, int target_ocv)
+{
+       /* 22 values of mapping table for EVT1*/
+
+       int *soc_arr;
+       int *ocv_arr;
+       int soc = 0;
+       int ocv = target_ocv * 10;
+
+       int high_index = TABLE_SIZE - 1;
+       int low_index = 0;
+       int mid_index = 0;
+
+       soc_arr = fuelgauge->info.soc_arr_val;
+       ocv_arr = fuelgauge->info.ocv_arr_val;
+
+       pr_err("%s: soc_arr(%d) ocv_arr(%d)\n", __func__, *soc_arr, *ocv_arr);
+
+       if (ocv <= ocv_arr[TABLE_SIZE - 1]) {
+               soc = soc_arr[TABLE_SIZE - 1];
+               goto soc_ocv_mapping;
+       } else if (ocv >= ocv_arr[0]) {
+               soc = soc_arr[0];
+               goto soc_ocv_mapping;
+       }
+       while (low_index <= high_index) {
+               mid_index = (low_index + high_index) >> 1;
+               if (ocv_arr[mid_index] > ocv)
+                       low_index = mid_index + 1;
+               else if (ocv_arr[mid_index] < ocv)
+                       high_index = mid_index - 1;
+               else {
+                       soc = soc_arr[mid_index];
+                       goto soc_ocv_mapping;
+               }
+       }
+       soc = soc_arr[high_index];
+       soc += ((soc_arr[low_index] - soc_arr[high_index]) *
+                                       (ocv - ocv_arr[high_index])) /
+                                       (ocv_arr[low_index] - ocv_arr[high_index]);
+
+soc_ocv_mapping:
+       dev_info(&fuelgauge->i2c->dev, "%s: ocv (%d), soc (%d)\n", __func__, ocv, soc);
+       return soc;
+}
+
+static void WA_0_issue_at_init1(struct s2mu004_fuelgauge_data *fuelgauge, int target_ocv)
+{
+       int a = 0;
+       u8 v_4e = 0, v_4f = 0, temp1, temp2;
+       int FG_volt, UI_volt, offset;
+       u8 v_40 = 0;
+       u8 temp_REG26 = 0, temp_REG27 = 0, temp = 0;
+
+       /* Step 1: [Surge test]  get UI voltage (0.1mV)*/
+       UI_volt = target_ocv * 10;
+
+       /* avgvbat factor value set to 0xFF  */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x40, &v_40);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, 0xFF);
+
+       /* current fix for soc */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x27, &temp_REG27);
+       temp = temp_REG27;
+       temp |= 0x0F;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x26, &temp_REG26);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x26, 0xFF);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+       msleep(50);
+
+       /* Step 2: [Surge test] get FG voltage (0.1mV) */
+       FG_volt = s2mu004_get_vbat(fuelgauge) * 10;
+
+       /* Step 3: [Surge test] get offset */
+       offset = UI_volt - FG_volt;
+       pr_err("%s: UI_volt(%d), FG_volt(%d), offset(%d)\n",
+                       __func__, UI_volt, FG_volt, offset);
+
+       /* Step 4: [Surge test] */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4f, &v_4f);
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4e, &v_4e);
+       pr_err("%s: v_4f(0x%x), v_4e(0x%x)\n", __func__, v_4f, v_4e);
+
+       a = (v_4f & 0x0F) << 8;
+       a += v_4e;
+       pr_err("%s: a before add offset (0x%x)\n", __func__, a);
+
+       /* 2`s complement */
+       if (a & (0x01 << 11))
+               a = (-10000 * ((a^0xFFF) + 1)) >> 13;
+       else
+               a = (10000 * a) >> 13;
+
+       a = a + offset;
+       pr_err("%s: a after add offset (0x%x)\n", __func__, a);
+
+       /* limit upper/lower offset */
+       if (a > 2490)
+               a = 2490;
+
+       if (a < (-2490))
+               a = -2490;
+
+       a = (a << 13) / 10000;
+       if (a < 0)
+               a = -1*((a^0xFFF)+1);
+
+       pr_err("%s: a after add offset (0x%x)\n", __func__, a);
+
+       a &= 0xfff;
+       pr_err("%s: (a)&0xFFF (0x%x)\n", __func__, a);
+
+       /* modify 0x4f[3:0] */
+       temp1 = v_4f & 0xF0;
+       temp2 = (u8)((a&0xF00) >> 8);
+       temp1 |= temp2;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4f, temp1);
+
+       /* modify 0x4e[7:0] */
+       temp2 = (u8)(a & 0xFF);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4e, temp2);
+
+       /* restart and dumpdone */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+       msleep(300);
+
+       /* restore current register */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp_REG27);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x26, temp_REG26);
+
+       pr_info("%s: S2MU004 VBAT : %d\n", __func__, s2mu004_get_vbat(fuelgauge) * 10);
+
+       /* recovery 0x4e and 0x4f */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4f, &temp1);
+       temp1 &= 0xF0;
+       temp1 |= (v_4f & 0x0F);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4f, temp1);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4e, v_4e);
+
+       /* restore monout avgvbat factor value */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, v_40);
+}
+
+static void s2mu004_reset_fg(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       int i;
+       u8 temp = 0;
+
+       /* step 0: [Surge test] initialize register of FG */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x0E, fuelgauge->info.batcap[0]);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x0F, fuelgauge->info.batcap[1]);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x10, fuelgauge->info.batcap[2]);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x11, fuelgauge->info.batcap[3]);
+
+       for (i = 0x92; i <= 0xe9; i++)
+               s2mu004_write_reg_byte(fuelgauge->i2c, i, fuelgauge->info.battery_table3[i - 0x92]);
+
+       for (i = 0xea; i <= 0xff; i++)
+               s2mu004_write_reg_byte(fuelgauge->i2c, i, fuelgauge->info.battery_table4[i - 0xea]);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x21, 0x13);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x14, 0x40);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x45, &temp);
+       temp &= 0xF0;
+       temp |= fuelgauge->info.accum[0];
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x45, temp);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x44, fuelgauge->info.accum[1]);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x27, &temp);
+       temp |= 0x10;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp);
+
+       /* Interrupt source reference at mixed mode */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x43, &temp);
+       temp &= 0xF3;
+       temp |= 0x08;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x43, temp);
+
+       /* Charger top off current sensing method change for int. 0x49[7]=0 */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x49, &temp);
+       temp &= 0x7F;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x49, temp);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4B, 0x0B);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4A, 0x10);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x03, &temp);
+       temp |= 0x10;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x03, temp);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, 0x08);
+
+       WA_0_issue_at_init(fuelgauge);
+       pr_err("%s: Reset FG completed\n", __func__);
+}
+
+static void s2mu004_restart_gauging(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 temp = 0, temp_REG26 = 0, temp_REG27 = 0;
+       u8 data[2], r_data[2];
+       u8 v_40;
+
+       pr_err("%s: Re-calculate SOC and voltage\n", __func__);
+
+       s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_IRQ, data);
+       pr_info("%s: irq_reg data (%02x%02x)\n", __func__, data[1], data[0]);
+
+       /* store data for interrupt mask */
+       r_data[0] = data[0];
+       r_data[1] = data[1];
+
+       /* disable irq for unwanted interrupt */
+       data[1] |= 0x0f;
+       s2mu004_write_reg(fuelgauge->i2c, S2MU004_REG_IRQ, data);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x27, &temp_REG27);
+       temp = temp_REG27;
+       temp |= 0x0F;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x26, &temp_REG26);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x26, 0xFF);
+
+       /* avgvbat factor value set to 0xFF  */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x40, &v_40);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, 0xFF);
+
+       /* restart gauge */
+       /* s2mu004_write_reg_byte(fuelgauge->i2c, 0x1f, 0x01); */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x21, 0x13);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+
+       msleep(300);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp_REG27);
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x26, temp_REG26);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x27, &temp);
+       pr_info("%s: 0x27 : %02x\n", __func__, temp);
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x26, &temp);
+       pr_info("%s: 0x26 : %02x\n", __func__, temp);
+
+       /* restore monout avgvbat factor value */
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x40, v_40);
+
+       /* enable irq after reset */
+       s2mu004_write_reg(fuelgauge->i2c, S2MU004_REG_IRQ, r_data);
+       pr_info("%s: re-store irq_reg data (%02x%02x)\n", __func__, r_data[1], r_data[0]);
+}
+
+static void s2mu004_init_regs(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 temp = 0;
+
+       pr_err("%s: s2mu004 fuelgauge initialize\n", __func__);
+
+       /* Reduce top-off current difference between
+        * Power on charging and Power off charging
+        */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x27, &temp);
+       temp |= 0x10;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x27, temp);
+
+       /* Interrupt source reference at mixed mode */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x43, &temp);
+       temp &= 0xF3;
+       temp |= 0x08;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x43, temp);
+
+       /* Charger top off current sensing method change for int. 0x49[7]=0 */
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x49, &temp);
+       temp &= 0x7F;
+       s2mu004_write_reg_byte(fuelgauge->i2c, 0x49, temp);
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4F, &temp);
+       fuelgauge->reg_OTP_4F = temp;
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4E, &temp);
+       fuelgauge->reg_OTP_4E = temp;
+}
+
+static void s2mu004_alert_init(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+
+       /* VBAT Threshold setting */
+       data[0] = ((fuelgauge->pdata->fuel_alert_vol - 2800) / 50) & 0x0f;
+
+       /* SOC Threshold setting */
+       data[0] = data[0] | (fuelgauge->pdata->fuel_alert_soc << 4);
+
+       data[1] = 0x00;
+       s2mu004_write_reg(fuelgauge->i2c, S2MU004_REG_IRQ_LVL, data);
+
+       pr_info("%s: irq_lvl(vbat:0x%x, soc:0x%x)\n", __func__, data[0] & 0x0F, data[0] & 0xF0);
+}
+
+static int s2mu004_set_temperature(struct s2mu004_fuelgauge_data *fuelgauge,
+               int temperature)
+{
+       /*
+        * s5mu004 include temperature sensor so,
+        * do not need to set temperature value.
+        */
+       return temperature;
+}
+
+static int s2mu004_get_temperature(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment;
+       s32 temperature = 0;
+       /*
+        *  use monitor regiser.
+        *  monitor register default setting is temperature
+        */
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, 0x10);
+       if (s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_MONOUT, data) < 0)
+               goto err;
+
+       mutex_unlock(&fuelgauge->fg_lock);
+       compliment = (data[1] << 8) | (data[0]);
+
+       /* data[] store 2's compliment format number */
+       if (compliment & (0x1 << 15)) {
+               /* Negative */
+               temperature = -1 * ((~compliment & 0xFFFF) + 1);
+       } else {
+               temperature = compliment & 0x7FFF;
+       }
+       temperature = ((temperature * 100) >> 8)/10;
+
+       dev_dbg(&fuelgauge->i2c->dev, "%s: temperature (%d)\n",
+               __func__, temperature);
+
+       /* For test, return room temperature */
+       /* To use IC's value, check there is NTC & register setting is right*/
+#if FAKE_TEMP
+       temperature = 250;
+#endif
+
+       return temperature;
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -ERANGE;
+}
+
+static int s2mu004_get_rawsoc(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2], check_data[2];
+       u16 compliment;
+       int rsoc, i;
+       u8 por_state = 0;
+       u8 reg = S2MU004_REG_RSOC;
+       u8 reg_OTP_4E = 0, reg_OTP_4F = 0;
+       int fg_reset = 0;
+       bool charging_enabled = false;
+       union power_supply_propval value;
+       int force_power_off_voltage = 0;
+       int rbat = 0;
+
+       int avg_current = 0, avg_vbat = 0, vbat = 0, curr = 0, avg_monout_vbat = 0;
+       int ocv_pwroff = 0, ocv_pwr_voltagemode = 0;
+       int target_soc = 0;
+
+       /* SOC VM Monitoring For debugging SOC error */
+       u8 r_monoutsel;
+       u8 mount_data[2];
+       u32 mount_compliment;
+       int rvmsoc;
+
+       struct power_supply *psy;
+       int ret;
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x1F, &por_state);
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4F, &reg_OTP_4F);
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4E, &reg_OTP_4E);
+
+       dev_err(&fuelgauge->i2c->dev, "%s: OTP 4E(%02x) 4F(%02x) current 4E(%02x) 4F(%02x)\n",
+                       __func__, fuelgauge->reg_OTP_4E, fuelgauge->reg_OTP_4F, reg_OTP_4E, reg_OTP_4F);
+
+       if ((por_state & 0x10) ||
+                       (fuelgauge->probe_done == true &&
+                        (fuelgauge->reg_OTP_4E != reg_OTP_4E || fuelgauge->reg_OTP_4F != reg_OTP_4F))) {
+
+               /* check charging enable */
+
+               psy = power_supply_get_by_name("s2mu004-charger");
+               if (!psy)
+                       return -EINVAL;
+               ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CHARGING_ENABLED, &value);
+               if (ret < 0)
+                       pr_err("%s: Fail to execute property\n", __func__);
+
+               charging_enabled = value.intval;
+
+               value.intval = S2MU00X_BAT_CHG_MODE_CHARGING_OFF;
+
+               psy = power_supply_get_by_name("s2mu004-charger");
+               if (!psy)
+                       return -EINVAL;
+               ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGING_ENABLED, &value);
+               if (ret < 0)
+                       pr_err("%s: Fail to execute property\n", __func__);
+
+               if (fuelgauge->reg_OTP_4E != reg_OTP_4E || fuelgauge->reg_OTP_4F != reg_OTP_4F) {
+
+                       psy = power_supply_get_by_name("s2mu004-charger");
+                       if (!psy)
+                               return -EINVAL;
+                       ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_FUELGAUGE_RESET, &value);
+                       if (ret < 0)
+                               pr_err("%s: Fail to execute property\n", __func__);
+
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1F, 0x40);
+                       msleep(50);
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x1F, 0x01);
+
+                       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4F, &reg_OTP_4F);
+                       s2mu004_read_reg_byte(fuelgauge->i2c, 0x4E, &reg_OTP_4E);
+
+                       dev_err(&fuelgauge->i2c->dev, "1st reset after %s: OTP 4E(%02x) 4F(%02x) current 4E(%02x) 4F(%02x)\n",
+                                       __func__, fuelgauge->reg_OTP_4E, fuelgauge->reg_OTP_4F, reg_OTP_4E, reg_OTP_4F);
+
+                       if (fuelgauge->reg_OTP_4E != reg_OTP_4E || fuelgauge->reg_OTP_4F != reg_OTP_4F) {
+
+                               psy = power_supply_get_by_name("s2mu004-charger");
+                               if (!psy)
+                                       return -EINVAL;
+                               ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_FUELGAUGE_RESET, &value);
+                               if (ret < 0)
+                                       pr_err("%s: Fail to execute property\n", __func__);
+
+                               s2mu004_write_reg_byte(fuelgauge->i2c, 0x1F, 0x40);
+                               msleep(50);
+                               s2mu004_write_reg_byte(fuelgauge->i2c, 0x1F, 0x01);
+                               dev_err(&fuelgauge->i2c->dev, "%s : 2nd reset\n", __func__);
+                       }
+               }
+
+               dev_info(&fuelgauge->i2c->dev, "%s: FG reset\n", __func__);
+               s2mu004_reset_fg(fuelgauge);
+               por_state &= ~0x10;
+               s2mu004_write_reg_byte(fuelgauge->i2c, 0x1F, por_state);
+
+               fg_reset = 1;
+       }
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       reg = S2MU004_REG_RSOC;
+
+       for (i = 0; i < 50; i++) {
+               if (s2mu004_read_reg(fuelgauge->i2c, reg, data) < 0)
+                       goto err;
+               if (s2mu004_read_reg(fuelgauge->i2c, reg, check_data) < 0)
+                       goto err;
+
+               dev_dbg(&fuelgauge->i2c->dev, "[DEBUG]%s: data0 (%d) data1 (%d)\n", __func__, data[0], data[1]);
+               if ((data[0] == check_data[0]) && (data[1] == check_data[1]))
+                       break;
+       }
+
+       /* SOC VM Monitoring For debugging SOC error */
+       s2mu004_read_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, &r_monoutsel);
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, 0x02);
+       mdelay(10);
+       s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_MONOUT, mount_data);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, r_monoutsel);
+
+       mutex_unlock(&fuelgauge->fg_lock);
+
+       /* SOC VM Monitoring For debugging SOC error */
+       mount_compliment  = ((mount_data[0] + (mount_data[1] << 8)) * 10000) >> 12;
+       rvmsoc = mount_compliment;
+
+       dev_dbg(&fuelgauge->i2c->dev, "%s: vm soc raw data0 (%d) data1 (%d)\n", __func__, mount_data[0], mount_data[1]);
+       dev_info(&fuelgauge->i2c->dev, "%s: vm soc (%d)\n", __func__, rvmsoc);
+
+       if (fg_reset && charging_enabled) {
+               value.intval = S2MU00X_BAT_CHG_MODE_CHARGING;
+
+               psy = power_supply_get_by_name("s2mu004-charger");
+               if (!psy)
+                       return -EINVAL;
+               ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGING_ENABLED, &value);
+               if (ret < 0)
+                       pr_err("%s: Fail to execute property\n", __func__);
+       }
+
+       compliment = (data[1] << 8) | (data[0]);
+
+       /* data[] store 2's compliment format number */
+       if (compliment & (0x1 << 15)) {
+               /* Negative */
+               rsoc = ((~compliment) & 0xFFFF) + 1;
+               rsoc = (rsoc * (-10000)) / (0x1 << 14);
+       } else {
+               rsoc = compliment & 0x7FFF;
+               rsoc = ((rsoc * 10000) / (0x1 << 14));
+       }
+
+       if (fg_reset)
+               fuelgauge->diff_soc = fuelgauge->info.soc - rsoc;
+
+
+       dev_info(&fuelgauge->i2c->dev, "%s: current_soc (%d), previous soc (%d), diff (%d), FG_mode(%d)\n",
+                       __func__, rsoc, fuelgauge->info.soc, fuelgauge->diff_soc, fuelgauge->mode);
+
+       fuelgauge->info.soc = rsoc + fuelgauge->diff_soc;
+
+       avg_current = s2mu004_get_avgcurrent(fuelgauge);
+       avg_monout_vbat =  s2mu004_get_monout_avgvbat(fuelgauge);
+       ocv_pwr_voltagemode = avg_monout_vbat - avg_current*30 / 100;
+
+       if (avg_current < (-500))
+               rbat = 10;
+       else
+               rbat = 30;
+
+       ocv_pwr_voltagemode = avg_monout_vbat - avg_current*rbat / 100;
+
+       /* switch to voltage mocd for accuracy */
+       if ((fuelgauge->info.soc <= 300) || ((ocv_pwr_voltagemode <= 3600) && (avg_current < 10))) {
+               if (fuelgauge->mode == CURRENT_MODE) { /* switch to VOLTAGE_MODE */
+                       fuelgauge->mode = LOW_SOC_VOLTAGE_MODE;
+
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4A, 0xFF);
+
+                       dev_info(&fuelgauge->i2c->dev, "%s: FG is in low soc voltage mode\n", __func__);
+               }
+       } else if ((fuelgauge->info.soc > 325) && ((ocv_pwr_voltagemode > 3650) || (avg_current >= 10))) {
+               if (fuelgauge->mode == LOW_SOC_VOLTAGE_MODE) {
+                       fuelgauge->mode = CURRENT_MODE;
+
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4A, 0x10);
+
+                       dev_info(&fuelgauge->i2c->dev, "%s: FG is in current mode\n", __func__);
+               }
+       }
+
+       psy = power_supply_get_by_name("battery");
+       if (!psy)
+               return -EINVAL;
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+
+       if (value.intval >= 98) {
+               if (fuelgauge->mode == CURRENT_MODE) { /* switch to VOLTAGE_MODE */
+                       fuelgauge->mode = HIGH_SOC_VOLTAGE_MODE;
+
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4A, 0xFF);
+
+                       dev_info(&fuelgauge->i2c->dev, "%s: FG is in high soc voltage mode\n", __func__);
+               }
+       } else if (value.intval < 97) {
+               if (fuelgauge->mode == HIGH_SOC_VOLTAGE_MODE) {
+                       fuelgauge->mode = CURRENT_MODE;
+
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x4A, 0x10);
+
+                       dev_info(&fuelgauge->i2c->dev, "%s: FG is in current mode\n", __func__);
+               }
+       }
+
+       avg_vbat =  s2mu004_get_avgvbat(fuelgauge);
+       vbat = s2mu004_get_vbat(fuelgauge);
+       curr = s2mu004_get_current(fuelgauge);
+
+       psy = power_supply_get_by_name("battery");
+       if (!psy)
+               return -EINVAL;
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+
+       if (value.intval <= (-150))
+               force_power_off_voltage = 3550;
+       else
+               force_power_off_voltage = 3300;
+
+       dev_info(&fuelgauge->i2c->dev,
+                       "%s: Fuelgauge Mode: %d, Force power-off voltage: %d\n",
+                       __func__, fuelgauge->mode, force_power_off_voltage);
+
+       if (((avg_current < (-17)) && (curr < (-17))) &&
+                       ((avg_monout_vbat - avg_current*rbat / 100) <= 3500) && (fuelgauge->info.soc > 100)) {
+               ocv_pwroff = 3300;
+               target_soc = s2mu004_get_soc_from_ocv(fuelgauge, ocv_pwroff);
+               pr_info("%s : F/G reset Start - current flunctuation\n", __func__);
+               WA_0_issue_at_init1(fuelgauge, ocv_pwroff);
+       } else if (avg_current < (-60) && avg_vbat <= force_power_off_voltage) {
+               if (fuelgauge->mode == CURRENT_MODE) {
+                       if (abs(avg_vbat - vbat) <= 20 && abs(avg_current - curr) <= 30) {
+                               ocv_pwroff = avg_vbat - avg_current * 15 / 100;
+                               target_soc = s2mu004_get_soc_from_ocv(fuelgauge, ocv_pwroff);
+                               if (abs(target_soc - fuelgauge->info.soc) > 300) {
+                                       pr_info("%s : F/G reset Start - current mode: %d\n", __func__, target_soc);
+                                       WA_0_issue_at_init1(fuelgauge, ocv_pwroff);
+
+                               }
+                       }
+               } else {
+                       if (abs(avg_vbat - vbat) <= 20) {
+                               ocv_pwroff = avg_vbat;
+                               target_soc = s2mu004_get_soc_from_ocv(fuelgauge, ocv_pwroff);
+                               if (abs(target_soc - fuelgauge->info.soc) > 300) {
+                                       pr_info("%s : F/G reset Start\n", __func__);
+                                       WA_0_issue_at_init1(fuelgauge, ocv_pwroff);
+                               }
+                       }
+               }
+       }
+
+
+       s2mu004_fg_test_read(fuelgauge->i2c);
+       return min(fuelgauge->info.soc, 10000);
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -EINVAL;
+}
+
+static int s2mu004_get_current(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment;
+       int curr = 0;
+
+       if (s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_RCUR_CC, data) < 0)
+               return -EINVAL;
+       compliment = (data[1] << 8) | (data[0]);
+       dev_dbg(&fuelgauge->i2c->dev, "%s: rCUR_CC(0x%4x)\n", __func__, compliment);
+
+       if (compliment & (0x1 << 15)) { /* Charging */
+               curr = ((~compliment) & 0xFFFF) + 1;
+               curr = (curr * 1000) >> 12;
+       } else { /* dischaging */
+               curr = compliment & 0x7FFF;
+               curr = (curr * (-1000)) >> 12;
+       }
+
+       dev_info(&fuelgauge->i2c->dev, "%s: current (%d)mA\n", __func__, curr);
+
+       return curr;
+}
+
+#define TABLE_SIZE  22
+static int s2mu004_get_ocv(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       /* 22 values of mapping table for EVT1*/
+
+       int *soc_arr;
+       int *ocv_arr;
+
+       int soc = fuelgauge->info.soc;
+       int ocv = 0;
+
+       int high_index = TABLE_SIZE - 1;
+       int low_index = 0;
+       int mid_index = 0;
+
+       soc_arr = fuelgauge->info.soc_arr_val;
+       ocv_arr = fuelgauge->info.ocv_arr_val;
+
+       dev_err(&fuelgauge->i2c->dev,
+                       "%s: soc (%d) soc_arr[TABLE_SIZE-1] (%d) ocv_arr[TABLE_SIZE-1) (%d)\n",
+                       __func__, soc, soc_arr[TABLE_SIZE-1], ocv_arr[TABLE_SIZE-1]);
+       if (soc <= soc_arr[TABLE_SIZE - 1]) {
+               ocv = ocv_arr[TABLE_SIZE - 1];
+               goto ocv_soc_mapping;
+       } else if (soc >= soc_arr[0]) {
+               ocv = ocv_arr[0];
+               goto ocv_soc_mapping;
+       }
+       while (low_index <= high_index) {
+               mid_index = (low_index + high_index) >> 1;
+               if (soc_arr[mid_index] > soc)
+                       low_index = mid_index + 1;
+               else if (soc_arr[mid_index] < soc)
+                       high_index = mid_index - 1;
+               else {
+                       ocv = ocv_arr[mid_index];
+                       goto ocv_soc_mapping;
+               }
+       }
+       ocv = ocv_arr[high_index];
+       ocv += ((ocv_arr[low_index] - ocv_arr[high_index]) *
+                                       (soc - soc_arr[high_index])) /
+                                       (soc_arr[low_index] - soc_arr[high_index]);
+
+ocv_soc_mapping:
+       dev_info(&fuelgauge->i2c->dev, "%s: soc (%d), ocv (%d)\n", __func__, soc, ocv);
+       return ocv;
+}
+
+static int s2mu004_get_avgcurrent(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment;
+       int curr = 0;
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, 0x26);
+
+       if (s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_MONOUT, data) < 0)
+               goto err;
+       compliment = (data[1] << 8) | (data[0]);
+
+       if (compliment & (0x1 << 15)) { /* Charging */
+               curr = ((~compliment) & 0xFFFF) + 1;
+               curr = (curr * 1000) >> 12;
+       } else { /* dischaging */
+               curr = compliment & 0x7FFF;
+               curr = (curr * (-1000)) >> 12;
+       }
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, 0x10);
+
+       mutex_unlock(&fuelgauge->fg_lock);
+
+       dev_info(&fuelgauge->i2c->dev, "%s: MONOUT(0x%4x), avg current (%d)mA\n",
+                       __func__, compliment, curr);
+
+       return curr;
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -EINVAL;
+}
+
+static int s2mu004_maintain_avgcurrent(
+               struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       static int cnt;
+       int vcell = 0;
+       int curr = 0;
+
+       curr = s2mu004_get_avgcurrent(fuelgauge);
+
+       vcell = s2mu004_get_vbat(fuelgauge);
+       if ((cnt < 10) && (curr < 0) && (fuelgauge->is_charging) &&
+                       (vcell < 3500)) {
+               curr = 1;
+               cnt++;
+               dev_info(&fuelgauge->i2c->dev, "%s: vcell (%d)mV,  modified avg current (%d)mA\n",
+                               __func__, vcell, curr);
+       }
+
+       return curr;
+}
+
+static int s2mu004_get_vbat(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u8 vbat_src;
+       u32 vbat = 0;
+
+       if (s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_RVBAT, data) < 0)
+               return -EINVAL;
+
+       dev_dbg(&fuelgauge->i2c->dev, "%s: data0 (%d) data1 (%d)\n", __func__, data[0], data[1]);
+       vbat = ((data[0] + (data[1] << 8)) * 1000) >> 13;
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, S2MU004_REG_CTRL0, &vbat_src);
+       dev_info(&fuelgauge->i2c->dev, "%s: vbat (%d), src (0x%02X)\n",
+                       __func__, vbat, (vbat_src & 0x30) >> 4);
+
+       return vbat;
+}
+
+static int s2mu004_get_monout_avgvbat(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment, avg_vbat;
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, 0x27);
+
+       mdelay(50);
+
+       if (s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_MONOUT, data) < 0)
+               goto err;
+       compliment = (data[1] << 8) | (data[0]);
+
+       avg_vbat = (compliment * 1000) >> 12;
+
+       s2mu004_write_reg_byte(fuelgauge->i2c, S2MU004_REG_MONOUT_SEL, 0x10);
+
+       mutex_unlock(&fuelgauge->fg_lock);
+
+       dev_info(&fuelgauge->i2c->dev, "%s: avgvbat (%d)\n", __func__, avg_vbat);
+
+       return avg_vbat;
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -EINVAL;
+}
+
+static int s2mu004_get_avgvbat(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u32 new_vbat, old_vbat = 0;
+       int cnt;
+
+       for (cnt = 0; cnt < 5; cnt++) {
+               if (s2mu004_read_reg(fuelgauge->i2c, S2MU004_REG_RVBAT, data) < 0)
+                       return -EINVAL;
+
+               new_vbat = ((data[0] + (data[1] << 8)) * 1000) >> 13;
+
+               if (cnt == 0)
+                       old_vbat = new_vbat;
+               else
+                       old_vbat = new_vbat / 2 + old_vbat / 2;
+       }
+
+       dev_dbg(&fuelgauge->i2c->dev, "%s: avgvbat (%d)\n", __func__, old_vbat);
+
+       return old_vbat;
+}
+
+bool s2mu004_fuelgauge_fuelalert_init(struct i2c_client *client, int soc)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge = i2c_get_clientdata(client);
+       u8 data[2];
+
+       fuelgauge->is_fuel_alerted = false;
+
+       /* 1. Set s2mu004 alert configuration. */
+       s2mu004_alert_init(fuelgauge);
+
+       if (s2mu004_read_reg(client, S2MU004_REG_IRQ, data) < 0)
+               return -1;
+
+       /*Enable VBAT, SOC */
+       data[1] &= 0xfc;
+
+       /*Disable IDLE_ST, INIT)ST */
+       data[1] |= 0x0c;
+
+       s2mu004_write_reg(client, S2MU004_REG_IRQ, data);
+
+       dev_dbg(&client->dev, "%s: irq_reg(%02x%02x) irq(%d)\n",
+                       __func__, data[1], data[0], fuelgauge->pdata->fg_irq);
+
+       return true;
+}
+
+static int s2mu004_fg_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge =
+                                       power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               return -ENODATA;
+       case POWER_SUPPLY_PROP_ENERGY_NOW:
+               val->intval = fuelgauge->pdata->capacity_full;
+               break;
+               /* Cell voltage (VCELL, mV) */
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = s2mu004_get_vbat(fuelgauge);
+               break;
+               /* Additional Voltage Information (mV) */
+       case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+               switch (val->intval) {
+               case S2MU00X_BATTERY_VOLTAGE_AVERAGE:
+                       val->intval = s2mu004_get_monout_avgvbat(fuelgauge);
+                       break;
+               case S2MU00X_BATTERY_VOLTAGE_OCV:
+                       val->intval = s2mu004_get_ocv(fuelgauge);
+                       break;
+               }
+               break;
+               /* Current (mA) */
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               if (val->intval == S2MU00X_BATTERY_CURRENT_UA)
+                       val->intval = s2mu004_get_current(fuelgauge) * 1000;
+               else
+                       val->intval = s2mu004_get_current(fuelgauge);
+               break;
+               /* Average Current (mA) */
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               if (val->intval == S2MU00X_BATTERY_CURRENT_UA)
+                       val->intval = s2mu004_maintain_avgcurrent(fuelgauge) * 1000;
+               else
+                       val->intval = s2mu004_maintain_avgcurrent(fuelgauge);
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               val->intval = s2mu004_get_rawsoc(fuelgauge) / 10;
+
+               /* capacity should be between 0% and 100%
+                * (0.1% degree)
+                */
+               if (val->intval > 1000)
+                       val->intval = 1000;
+               if (val->intval < 0)
+                       val->intval = 0;
+
+               /* get only integer part */
+               val->intval /= 10;
+
+               /* check whether doing the wake_unlock */
+               if ((val->intval > fuelgauge->pdata->fuel_alert_soc) &&
+                               fuelgauge->is_fuel_alerted) {
+                       wake_unlock(&fuelgauge->fuel_alert_wake_lock);
+                       s2mu004_fuelgauge_fuelalert_init(fuelgauge->i2c,
+                                       fuelgauge->pdata->fuel_alert_soc);
+               }
+               break;
+       /* IFPMIC Temperature */
+       case POWER_SUPPLY_PROP_TEMP:
+       case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+               val->intval = s2mu004_get_temperature(fuelgauge);
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+               val->intval = fuelgauge->capacity_max;
+               break;
+       case POWER_SUPPLY_PROP_SCOPE:
+               val->intval = fuelgauge->mode;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mu004_fg_set_property(struct power_supply *psy,
+                               enum power_supply_property psp,
+                               const union power_supply_propval *val)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge =
+                               power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               /*full*/
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               fuelgauge->cable_type = val->intval;
+               break;
+       case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+               if (val->intval)
+                       fuelgauge->is_charging = true;
+               else
+                       fuelgauge->is_charging = false;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               if (val->intval == S2MU00X_FUELGAUGE_CAPACITY_TYPE_RESET) {
+                       fuelgauge->initial_update_of_soc = true;
+                       s2mu004_restart_gauging(fuelgauge);
+               }
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+       case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+               s2mu004_set_temperature(fuelgauge, val->intval);
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+               dev_dbg(&fuelgauge->i2c->dev,
+                       "%s: capacity_max changed, %d -> %d\n",
+                       __func__, fuelgauge->capacity_max, val->intval);
+               fuelgauge->initial_update_of_soc = true;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_EMPTY:
+               pr_info("%s: WA for battery 0 percent\n", __func__);
+               s2mu004_write_reg_byte(fuelgauge->i2c, 0x1F, 0x01);
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_AVG:
+               pr_info("%s: WA for power off issue: val(%d)\n", __func__, val->intval);
+               if (val->intval)
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x41, 0x10); /* charger start */
+               else
+                       s2mu004_write_reg_byte(fuelgauge->i2c, 0x41, 0x04); /* charger end */
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void s2mu004_fg_isr_work(struct work_struct *work)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge =
+               container_of(work, struct s2mu004_fuelgauge_data, isr_work.work);
+       u8 fg_alert_status = 0;
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, S2MU004_REG_STATUS, &fg_alert_status);
+       dev_info(&fuelgauge->i2c->dev, "%s : fg_alert_status(0x%x)\n",
+                       __func__, fg_alert_status);
+
+       fg_alert_status &= 0x03;
+       if (fg_alert_status & 0x01)
+               pr_info("%s : Battery Level is very Low!\n", __func__);
+
+       if (fg_alert_status & 0x02) {
+               int voltage = s2mu004_get_vbat(fuelgauge);
+
+               pr_info("%s : Battery Votage is very Low! (%dmV)\n",
+                               __func__, voltage);
+       }
+
+       if (!fg_alert_status) {
+               fuelgauge->is_fuel_alerted = false;
+               pr_info("%s : Battery Health is good!\n", __func__);
+               wake_unlock(&fuelgauge->fuel_alert_wake_lock);
+       }
+}
+
+static irqreturn_t s2mu004_fg_irq_thread(int irq, void *irq_data)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge = irq_data;
+       u8 fg_irq = 0;
+
+       s2mu004_read_reg_byte(fuelgauge->i2c, S2MU004_REG_IRQ, &fg_irq);
+       pr_info("%s: fg_irq(0x%x)\n", __func__, fg_irq);
+
+       if (fuelgauge->is_fuel_alerted) {
+               return IRQ_HANDLED;
+       } else {
+               wake_lock(&fuelgauge->fuel_alert_wake_lock);
+               fuelgauge->is_fuel_alerted = true;
+               schedule_delayed_work(&fuelgauge->isr_work, 0);
+       }
+
+       return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_OF
+static int s2mu004_fuelgauge_parse_dt(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       struct device_node *np = of_find_node_by_name(NULL, "s2mu004-fuelgauge");
+       int ret;
+
+       /* reset, irq gpio info */
+       if (np == NULL) {
+               pr_err("%s np NULL\n", __func__);
+       } else {
+               fuelgauge->pdata->fg_irq = of_get_named_gpio(np, "fuelgauge,fuel_int", 0);
+               if (fuelgauge->pdata->fg_irq < 0)
+                       pr_err("%s error reading fg_irq = %d\n",
+                                       __func__, fuelgauge->pdata->fg_irq);
+
+               ret = of_property_read_u32(np, "fuelgauge,capacity_max",
+                               &fuelgauge->pdata->capacity_max);
+               if (ret < 0)
+                       pr_err("%s error reading capacity_max %d\n", __func__, ret);
+
+               ret = of_property_read_u32(np, "fuelgauge,capacity_max_margin",
+                               &fuelgauge->pdata->capacity_max_margin);
+               if (ret < 0)
+                       pr_err("%s error reading capacity_max_margin %d\n", __func__, ret);
+
+               ret = of_property_read_u32(np, "fuelgauge,capacity_min",
+                               &fuelgauge->pdata->capacity_min);
+               if (ret < 0)
+                       pr_err("%s error reading capacity_min %d\n", __func__, ret);
+
+               ret = of_property_read_u32(np, "fuelgauge,fuel_alert_soc",
+                               &fuelgauge->pdata->fuel_alert_soc);
+               if (ret < 0)
+                       pr_err("%s error reading pdata->fuel_alert_soc %d\n",
+                                       __func__, ret);
+
+               ret = of_property_read_u32(np, "fuelgauge,capacity_full",
+                               &fuelgauge->pdata->capacity_full);
+               if (ret < 0)
+                       pr_err("%s error reading pdata->capacity_full %d\n",
+                                       __func__, ret);
+
+               ret = of_property_read_u32(np, "fuelgauge,fuel_alert_vol",
+                               &fuelgauge->pdata->fuel_alert_vol);
+               if (ret < 0)
+                       pr_err("%s error reading pdata->fuel_alert_vol %d\n",
+                                       __func__, ret);
+
+               fuelgauge->pdata->repeated_fuelalert = of_property_read_bool(np,
+                               "fuelgauge,repeated_fuelalert");
+       }
+
+       /* get battery node */
+       np = of_find_node_by_name(NULL, "battery");
+       if (!np) {
+               pr_err("%s battery node NULL\n", __func__);
+       } else {
+               /* get battery_table */
+               ret = of_property_read_u32_array(np, "battery,battery_table3", fuelgauge->info.battery_table3, 88);
+               if (ret < 0)
+                       pr_err("%s error reading battery,battery_table3\n", __func__);
+
+               ret = of_property_read_u32_array(np, "battery,battery_table4", fuelgauge->info.battery_table4, 22);
+               if (ret < 0)
+                       pr_err("%s error reading battery,battery_table4\n", __func__);
+
+               ret = of_property_read_u32_array(np, "battery,batcap", fuelgauge->info.batcap, 4);
+               if (ret < 0)
+                       pr_err("%s error reading battery,batcap\n", __func__);
+
+               ret = of_property_read_u32_array(np, "battery,accum", fuelgauge->info.accum, 2);
+               if (ret < 0)
+                       pr_err("%s error reading battery,accum\n", __func__);
+
+               ret = of_property_read_u32_array(np, "battery,soc_arr_val", fuelgauge->info.soc_arr_val, 22);
+               if (ret < 0)
+                       pr_err("%s error reading battery,soc_arr_val\n", __func__);
+
+               ret = of_property_read_u32_array(np, "battery,ocv_arr_val", fuelgauge->info.ocv_arr_val, 22);
+               if (ret < 0)
+                       pr_err("%s error reading battery,ocv_arr_val\n", __func__);
+       }
+
+       return 0;
+}
+
+static const struct of_device_id s2mu004_fuelgauge_match_table[] = {
+               { .compatible = "samsung,s2mu004-fuelgauge",},
+               {},
+};
+#else
+static int s2mu004_fuelgauge_parse_dt(struct s2mu004_fuelgauge_data *fuelgauge)
+{
+       return -ENOSYS;
+}
+
+#define s2mu004_fuelgauge_match_table NULL
+#endif /* CONFIG_OF */
+
+static int s2mu004_fuelgauge_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       struct s2mu004_fuelgauge_data *fuelgauge;
+       union power_supply_propval raw_soc_val;
+       struct power_supply_config psy_cfg = {};
+       int ret = 0;
+       u8 temp = 0;
+
+       pr_info("%s: S2MU004 Fuelgauge Driver Loading\n", __func__);
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+               return -EIO;
+
+       fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL);
+       if (!fuelgauge)
+               return -ENOMEM;
+
+       mutex_init(&fuelgauge->fg_lock);
+
+       fuelgauge->i2c = client;
+
+       if (client->dev.of_node) {
+               fuelgauge->pdata = devm_kzalloc(&client->dev, sizeof(*(fuelgauge->pdata)),
+                               GFP_KERNEL);
+               if (!fuelgauge->pdata) {
+                       ret = -ENOMEM;
+                       goto err_parse_dt_nomem;
+               }
+               ret = s2mu004_fuelgauge_parse_dt(fuelgauge);
+               if (ret < 0)
+                       goto err_parse_dt;
+       } else {
+               fuelgauge->pdata = client->dev.platform_data;
+       }
+
+       i2c_set_clientdata(client, fuelgauge);
+
+       if (fuelgauge->pdata->fuelgauge_name == NULL)
+               fuelgauge->pdata->fuelgauge_name = "s2mu004-fuelgauge";
+
+       fuelgauge->psy_fg_desc.name          = fuelgauge->pdata->fuelgauge_name;
+       fuelgauge->psy_fg_desc.type          = POWER_SUPPLY_TYPE_UNKNOWN;
+       fuelgauge->psy_fg_desc.get_property  = s2mu004_fg_get_property;
+       fuelgauge->psy_fg_desc.set_property  = s2mu004_fg_set_property;
+       fuelgauge->psy_fg_desc.properties    = s2mu004_fuelgauge_props;
+       fuelgauge->psy_fg_desc.num_properties =
+                       ARRAY_SIZE(s2mu004_fuelgauge_props);
+
+       /* 0x48[7:4]=0010 : EVT2 */
+       fuelgauge->revision = 0;
+       s2mu004_read_reg_byte(fuelgauge->i2c, 0x48, &temp);
+       fuelgauge->revision = (temp & 0xF0) >> 4;
+
+       pr_info("%s: S2MU004 Fuelgauge revision: %d, reg 0x48 = 0x%x\n", __func__, fuelgauge->revision, temp);
+
+       fuelgauge->capacity_max = fuelgauge->pdata->capacity_max;
+       fuelgauge->info.soc = 0;
+       fuelgauge->mode = CURRENT_MODE;
+
+       raw_soc_val.intval = s2mu004_get_rawsoc(fuelgauge);
+       raw_soc_val.intval = raw_soc_val.intval / 10;
+
+       s2mu004_init_regs(fuelgauge);
+
+       psy_cfg.drv_data = fuelgauge;
+       fuelgauge->psy_fg = power_supply_register(&client->dev, &fuelgauge->psy_fg_desc, &psy_cfg);
+       if (IS_ERR(fuelgauge->psy_fg)) {
+               pr_err("%s: Failed to Register psy_fg\n", __func__);
+               ret = PTR_ERR(fuelgauge->psy_fg);
+               goto err_data_free;
+       }
+
+       fuelgauge->is_fuel_alerted = false;
+       if (fuelgauge->pdata->fuel_alert_soc >= 0) {
+               s2mu004_fuelgauge_fuelalert_init(fuelgauge->i2c,
+                                       fuelgauge->pdata->fuel_alert_soc);
+               wake_lock_init(&fuelgauge->fuel_alert_wake_lock,
+                                       WAKE_LOCK_SUSPEND, "fuel_alerted");
+
+               if (fuelgauge->pdata->fg_irq > 0) {
+                       INIT_DELAYED_WORK(
+                                       &fuelgauge->isr_work, s2mu004_fg_isr_work);
+
+                       fuelgauge->fg_irq = gpio_to_irq(fuelgauge->pdata->fg_irq);
+                       dev_info(&client->dev,
+                                       "%s : fg_irq = %d\n", __func__, fuelgauge->fg_irq);
+                       if (fuelgauge->fg_irq > 0) {
+                               ret = request_threaded_irq(fuelgauge->fg_irq,
+                                               NULL, s2mu004_fg_irq_thread,
+                                               IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+                                               "fuelgauge-irq", fuelgauge);
+                               if (ret) {
+                                       dev_err(&client->dev,
+                                                       "%s: Failed to Request IRQ\n", __func__);
+                                       goto err_supply_unreg;
+                               }
+
+                               ret = enable_irq_wake(fuelgauge->fg_irq);
+                               if (ret < 0)
+                                       dev_err(&client->dev,
+                                                       "%s: Failed to Enable Wakeup Source(%d)\n",
+                                                       __func__, ret);
+                       } else {
+                               dev_err(&client->dev, "%s: Failed gpio_to_irq(%d)\n",
+                                               __func__, fuelgauge->fg_irq);
+                               goto err_supply_unreg;
+                       }
+               }
+       }
+
+       fuelgauge->sleep_initial_update_of_soc = false;
+       fuelgauge->initial_update_of_soc = true;
+
+       fuelgauge->cc_on = true;
+       fuelgauge->probe_done = true;
+
+       pr_info("%s: S2MU004 Fuelgauge Driver Loaded\n", __func__);
+       return 0;
+
+err_supply_unreg:
+       power_supply_unregister(fuelgauge->psy_fg);
+err_data_free:
+       if (client->dev.of_node)
+               kfree(fuelgauge->pdata);
+
+err_parse_dt:
+err_parse_dt_nomem:
+       mutex_destroy(&fuelgauge->fg_lock);
+       kfree(fuelgauge);
+
+       return ret;
+}
+
+static const struct i2c_device_id s2mu004_fuelgauge_id[] = {
+       {"s2mu004-fuelgauge", 0},
+       {}
+};
+
+static void s2mu004_fuelgauge_shutdown(struct i2c_client *client)
+{
+
+}
+
+static int s2mu004_fuelgauge_remove(struct i2c_client *client)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge = i2c_get_clientdata(client);
+
+       if (fuelgauge->pdata->fuel_alert_soc >= 0)
+               wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock);
+
+       return 0;
+}
+
+#if defined CONFIG_PM
+static int s2mu004_fuelgauge_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int s2mu004_fuelgauge_resume(struct device *dev)
+{
+       struct s2mu004_fuelgauge_data *fuelgauge = dev_get_drvdata(dev);
+
+       fuelgauge->sleep_initial_update_of_soc = true;
+
+       return 0;
+}
+#else
+#define s2mu004_fuelgauge_suspend NULL
+#define s2mu004_fuelgauge_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(s2mu004_fuelgauge_pm_ops, s2mu004_fuelgauge_suspend,
+               s2mu004_fuelgauge_resume);
+
+static struct i2c_driver s2mu004_fuelgauge_driver = {
+       .driver = {
+               .name = "s2mu004-fuelgauge",
+               .owner = THIS_MODULE,
+               .pm = &s2mu004_fuelgauge_pm_ops,
+               .of_match_table = s2mu004_fuelgauge_match_table,
+       },
+       .probe  = s2mu004_fuelgauge_probe,
+       .remove = s2mu004_fuelgauge_remove,
+       .shutdown   = s2mu004_fuelgauge_shutdown,
+       .id_table   = s2mu004_fuelgauge_id,
+};
+
+static int __init s2mu004_fuelgauge_init(void)
+{
+       pr_info("%s: S2MU004 Fuelgauge Init\n", __func__);
+       return i2c_add_driver(&s2mu004_fuelgauge_driver);
+}
+
+static void __exit s2mu004_fuelgauge_exit(void)
+{
+       i2c_del_driver(&s2mu004_fuelgauge_driver);
+}
+module_init(s2mu004_fuelgauge_init);
+module_exit(s2mu004_fuelgauge_exit);
+
+MODULE_DESCRIPTION("Samsung S2MU004 Fuel Gauge Driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/samsung/s2mu004-private.h b/include/linux/mfd/samsung/s2mu004-private.h
new file mode 100644 (file)
index 0000000..a29ec84
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * s2mu004-private.h - Voltage regulator driver for the s2mu004
+ *
+ * Copyright (C) 2016 Samsung Electronics Co.Ltd
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LINUX_MFD_S2MU004_PRIV_H
+#define __LINUX_MFD_S2MU004_PRIV_H
+
+#include <linux/i2c.h>
+
+//#include <linux/battery/charger/s2mu004_charger.h>
+//#include <linux/battery/fuelgauge/s2mu004_fuelgauge.h>
+
+#define S2MU004_I2C_ADDR               (0x7A)
+#define S2MU004_REG_INVALID            (0xff)
+
+enum s2mu004_reg {
+       /* Slave addr = 0x7A */
+       S2MU004_REG_SC_INT1,
+       S2MU004_REG_SC_INT2,
+       S2MU004_REG_SC_INT1_MASK,
+       S2MU004_REG_SC_INT2_MASK,
+       S2MU004_REG_AFC_INT,
+       S2MU004_REG_AFC_INT_MASK,
+       S2MU004_REG_MUIC_INT1,
+       S2MU004_REG_MUIC_INT2,
+       S2MU004_REG_MUIC_INT1_MASK,
+       S2MU004_REG_MUIC_INT2_MASK,
+
+       S2MU004_REG_SC_STATUS0,
+       S2MU004_REG_SC_STATUS1,
+       S2MU004_REG_SC_STATUS2,
+       S2MU004_REG_SC_STATUS3,
+       S2MU004_REG_SC_STATUS4,
+       S2MU004_REG_SC_STATUS5,
+       S2MU004_REG_SC_CTRL0,
+       S2MU004_REG_SC_CTRL1,
+       S2MU004_REG_SC_CTRL2,
+       S2MU004_REG_SC_CTRL3,
+       S2MU004_REG_SC_CTRL4,
+       S2MU004_REG_SC_CTRL5,
+       S2MU004_REG_SC_CTRL6,
+       S2MU004_REG_SC_CTRL7,
+       S2MU004_REG_SC_CTRL8,
+       S2MU004_REG_SC_CTRL9,
+       S2MU004_REG_SC_CTRL10,
+       S2MU004_REG_SC_CTRL11,
+       S2MU004_REG_SC_CTRL12,
+       S2MU004_REG_SC_CTRL13,
+       S2MU004_REG_SC_CTRL14,
+       S2MU004_REG_SC_CTRL15,
+       S2MU004_REG_SC_CTRL16,
+       S2MU004_REG_SC_CTRL17,
+       S2MU004_REG_SC_CTRL18,
+       S2MU004_REG_SC_TEST0,
+       S2MU004_REG_SC_TEST1,
+       S2MU004_REG_SC_TEST2,
+       S2MU004_REG_SC_TEST3,
+       S2MU004_REG_SC_TEST4,
+       S2MU004_REG_SC_TEST5,
+       S2MU004_REG_SC_TEST6,
+       S2MU004_REG_SC_TEST7,
+       S2MU004_REG_SC_TEST8,
+       S2MU004_REG_SC_RSVD2B,
+       S2MU004_REG_SC_TEST10,
+
+       S2MU004_REG_LED_EN = 0x39,
+       S2MU004_REG_LED1_CURRENT,
+       S2MU004_REG_LED2_CURRENT,
+       S2MU004_REG_LED3_CURRENT,
+       S2MU004_REG_LED4_CURRENT,
+       S2MU004_REG_LED1_RAMP,
+       S2MU004_REG_LED1_DUR,
+       S2MU004_REG_LED2_RAMP,
+       S2MU004_REG_LED2_DUR,
+       S2MU004_REG_LED3_RAMP,
+       S2MU004_REG_LED3_DUR,
+       S2MU004_REG_LED4_RAMP,
+       S2MU004_REG_LED4_DUR,
+       S2MU004_REG_LED_TEST0,
+       S2MU004_REG_LED_CTRL0,
+
+       S2MU004_REG_AFC_STATUS = 0x48,
+       S2MU004_REG_AFC_CTRL1,
+       S2MU004_REG_AFC_CTRL2,
+       S2MU004_REG_TX_BYTE1,
+       S2MU004_REG_RX_BYTE1,
+       S2MU004_REG_RX_BYTE2,
+       S2MU004_REG_RX_BYTE3,
+       S2MU004_REG_RX_BYTE4,
+       S2MU004_REG_RX_BYTE5,
+       S2MU004_REG_RX_BYTE6,
+       S2MU004_REG_RX_BYTE7,
+       S2MU004_REG_RX_BYTE8,
+       S2MU004_REG_RX_BYTE9,
+       S2MU004_REG_RX_BYTE10,
+       S2MU004_REG_RX_BYTE11,
+       S2MU004_REG_RX_BYTE12,
+       S2MU004_REG_RX_BYTE13,
+       S2MU004_REG_RX_BYTE14,
+       S2MU004_REG_RX_BYTE15,
+       S2MU004_REG_RX_BYTE16,
+
+       S2MU004_REG_AFC_LOGIC_CTRL2 = 0x5F,
+
+       S2MU004_REG_MUIC_ADC = 0x61,
+       S2MU004_REG_MUIC_DEVICE_TYPE1,
+       S2MU004_REG_MUIC_DEVICE_TYPE2,
+       S2MU004_REG_MUIC_DEVICE_TYPE3,
+       S2MU004_REG_MUIC_BUTTON1,
+       S2MU004_REG_MUIC_BUTTON2,
+       S2MU004_REG_MUIC_RESET,
+       S2MU004_REG_MUIC_CHG_TYPE,
+       S2MU004_REG_MUIC_DEVICE_APPLE,
+       S2MU004_REG_MUIC_BCD_RESCAN,
+       S2MU004_REG_MUIC_TEST1,
+       S2MU004_REG_MUIC_TEST2,
+       S2MU004_REG_MUIC_TEST3,
+       S2MU004_REG_MUIC_TEST4,
+
+       S2MU004_REG_COMMON_CFG1,
+       S2MU004_REG_COMMON_CFG2,
+       S2MU004_REG_MRSTB,
+       S2MU004_REG_PWRSEL_CTRL0,
+       S2MU004_REG_RSVD73,
+       S2MU004_REG_SELFDIS_CFG1,
+       S2MU004_REG_SELFDIS_CFG2,
+       S2MU004_REG_SELFDIS_CFG3,
+       S2MU004_REG_RSVD77,
+
+       S2MU004_REG_REV_ID = 0x82,
+
+       S2MU004_REG_MUIC_RID_CTRL = 0xCC,
+       S2MU004_REG_MUIC_CTRL1 = 0xC7,
+       S2MU004_REG_MUIC_TIMER_SET1,
+       S2MU004_REG_MUIC_TIMER_SET2,
+       S2MU004_REG_MUIC_SW_CTRL,
+       S2MU004_REG_MUIC_TIMER_SET3,
+       S2MU004_REG_MUIC_CTRL2,
+       S2MU004_REG_MUIC_CTRL3,
+       S2MU004_REG_CHARGER_DET_OTP = 0xCE,
+
+       S2MU004_REG_LDOADC_VSETL = 0xD4,
+       S2MU004_REG_LDOADC_VSETH,
+       S2MU004_REG_AFC_OTP6 = 0xDA,
+
+       S2MU004_REG_END,
+};
+
+enum s2mu004_irq_source {
+       CHG_INT1 = 0,
+       CHG_INT2,
+       AFC_INT,
+       MUIC_INT1,
+       MUIC_INT2,
+
+       S2MU004_IRQ_GROUP_NR,
+};
+
+#define MUIC_MAX_INT                   MUIC_INT2
+#define S2MU004_NUM_IRQ_MUIC_REGS      (MUIC_MAX_INT - MUIC_INT1 + 1)
+
+enum s2mu004_irq {
+
+       S2MU004_CHG1_IRQ_SYS,
+       S2MU004_CHG1_IRQ_Poor_CHG,
+       S2MU004_CHG1_IRQ_CHG_Fault,
+       S2MU004_CHG1_IRQ_CHG_RSTART,
+       S2MU004_CHG1_IRQ_DONE,
+       S2MU004_CHG1_IRQ_TOP_OFF,
+       S2MU004_CHG1_IRQ_WCIN,
+       S2MU004_CHG1_IRQ_CHGIN,
+
+       S2MU004_CHG2_IRQ_ICR,
+       S2MU004_CHG2_IRQ_IVR,
+       S2MU004_CHG2_IRQ_AICL,
+       S2MU004_CHG2_IRQ_TX_Fault,
+       S2MU004_CHG2_IRQ_OTG_Fault,
+       S2MU004_CHG2_IRQ_DET_BAT,
+       S2MU004_CHG2_IRQ_BAT,
+
+       S2MU004_AFC_IRQ_VbADC,
+       S2MU004_AFC_IRQ_VDNMon,
+       S2MU004_AFC_IRQ_DNRes,
+       S2MU004_AFC_IRQ_MPNack,
+       S2MU004_AFC_IRQ_MRxBufOw,
+       S2MU004_AFC_IRQ_MRxTrf,
+       S2MU004_AFC_IRQ_MRxPerr,
+       S2MU004_AFC_IRQ_MRxRdy,
+
+       S2MU004_MUIC_IRQ1_ATTATCH,
+       S2MU004_MUIC_IRQ1_DETACH,
+       S2MU004_MUIC_IRQ1_KP,
+       S2MU004_MUIC_IRQ1_LKP,
+       S2MU004_MUIC_IRQ1_LKR,
+       S2MU004_MUIC_IRQ1_RID_CHG,
+
+       S2MU004_MUIC_IRQ2_VBUS_ON,
+       S2MU004_MUIC_IRQ2_RSVD_ATTACH,
+       S2MU004_MUIC_IRQ2_ADC_CHANGE,
+       S2MU004_MUIC_IRQ2_STUCK,
+       S2MU004_MUIC_IRQ2_STUCKRCV,
+       S2MU004_MUIC_IRQ2_MHDL,
+       S2MU004_MUIC_IRQ2_AV_CHARGE,
+       S2MU004_MUIC_IRQ2_VBUS_OFF,
+       S2MU004_IRQ_NR,
+};
+
+struct s2mu004_dev {
+       struct device *dev;
+       struct i2c_client *i2c; /* Slave addr = 0x7A */
+       struct mutex i2c_lock;
+
+       int type;
+
+       int irq;
+       int irq_base;
+       int irq_gpio;
+       bool wakeup;
+       struct mutex irqlock;
+       int irq_masks_cur[S2MU004_IRQ_GROUP_NR];
+       int irq_masks_cache[S2MU004_IRQ_GROUP_NR];
+
+#ifdef CONFIG_HIBERNATION
+       /* For hibernation */
+       u8 reg_pmic_dump[S2MU004_PMIC_REG_END];
+       u8 reg_muic_dump[S2MU004_MUIC_REG_END];
+       u8 reg_led_dump[S2MU004_LED_REG_END];
+#endif
+
+       /* pmic VER/REV register */
+       u8 pmic_rev;    /* pmic Rev */
+       u8 pmic_ver;    /* pmic version */
+
+       struct s2mu004_platform_data *pdata;
+};
+
+enum s2mu004_types {
+       TYPE_S2MU004,
+};
+
+extern int s2mu004_irq_init(struct s2mu004_dev *s2mu004);
+extern void s2mu004_irq_exit(struct s2mu004_dev *s2mu004);
+
+/* s2mu004 shared i2c API function */
+extern int s2mu004_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
+extern int s2mu004_bulk_read(struct i2c_client *i2c, u8 reg, int count,
+                               u8 *buf);
+extern int s2mu004_write_reg(struct i2c_client *i2c, u8 reg, u8 value);
+extern int s2mu004_bulk_write(struct i2c_client *i2c, u8 reg, int count,
+                               u8 *buf);
+extern int s2mu004_write_word(struct i2c_client *i2c, u8 reg, u16 value);
+extern int s2mu004_read_word(struct i2c_client *i2c, u8 reg);
+
+extern int s2mu004_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask);
+
+/* s2mu004 check muic path fucntion */
+extern bool is_muic_usb_path_ap_usb(void);
+extern bool is_muic_usb_path_cp_usb(void);
+
+/* s2mu004 Debug. ft */
+extern void s2mu004_muic_read_register(struct i2c_client *i2c);
+
+/* for charger api */
+extern void s2mu004_hv_muic_charger_init(void);
+
+#endif /* __LINUX_MFD_S2MU004_PRIV_H */
+
diff --git a/include/linux/mfd/samsung/s2mu004.h b/include/linux/mfd/samsung/s2mu004.h
new file mode 100644 (file)
index 0000000..0025dc6
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * s2mu004.h - Driver for the s2mu004
+ *
+ * Copyright (C) 2016 Samsung Electronics Co.Ltd
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * This driver is based on max8997.h
+ *
+ * s2mu004 has Flash LED, SVC LED, Haptic, MUIC devices.
+ * The devices share the same I2C bus and included in
+ * this mfd driver.
+ */
+
+#ifndef __S2MU004_H__
+#define __S2MU004_H__
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MFD_DEV_NAME "s2mu004"
+#define M2SH(m) ((m) & 0x0F ? ((m) & 0x03 ? ((m) & 0x01 ? 0 : 1) : ((m) & 0x04 ? 2 : 3)) : \
+               ((m) & 0x30 ? ((m) & 0x10 ? 4 : 5) : ((m) & 0x40 ? 6 : 7)))
+
+#ifdef CONFIG_VIBETONZ
+#if false
+struct s2mu004_haptic_platform_data {
+       u16 max_timeout;
+       u16 duty;
+       u16 period;
+       u16 reg2;
+       char *regulator_name;
+       unsigned int pwm_id;
+
+       void (*init_hw)(void);
+       void (*motor_en)(bool);
+};
+#endif
+#endif
+
+struct s2mu004_regulator_data {
+       int id;
+       struct regulator_init_data *initdata;
+       struct device_node *reg_node;
+};
+
+struct s2mu004_platform_data {
+       /* IRQ */
+       int irq_base;
+       int irq_gpio;
+       bool wakeup;
+       int num_regulators;
+       struct s2mu004_regulator_data *regulators;
+#ifdef CONFIG_VIBETONZ
+       /* haptic motor data */
+       /* struct s2mu004_haptic_platform_data *haptic_data; */
+#endif
+       struct mfd_cell *sub_devices;
+       int num_subdevs;
+};
+
+struct s2mu004 {
+       struct regmap *regmap;
+};
+
+#endif /* __S2MU004_H__ */
+
diff --git a/include/linux/power/s2mu004_charger.h b/include/linux/power/s2mu004_charger.h
new file mode 100644 (file)
index 0000000..1433e88
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ * s2mu004_charger.h - Header of S2MU004 Charger Driver
+ *
+ * Copyright (C) 2016 Samsung Electronics Co.Ltd
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef S2MU004_CHARGER_H
+#define S2MU004_CHARGER_H
+#include <linux/mfd/samsung/s2mu004.h>
+#include <linux/mfd/samsung/s2mu004-private.h>
+
+#if defined(CONFIG_MUIC_NOTIFIER)
+#include <linux/muic/muic.h>
+#include <linux/muic/muic_notifier.h>
+#endif /* CONFIG_MUIC_NOTIFIER */
+
+//#include <linux/muic/s2mu004-muic.h>
+#include <linux/power/s2mu00x_battery.h>
+
+
+#define MASK(width, shift)     (((0x1 << (width)) - 1) << shift)
+
+#define S2MU004_CHG_STATUS0            0x0A
+#define S2MU004_CHG_STATUS1            0x0B
+#define S2MU004_CHG_STATUS2            0x0C
+#define S2MU004_CHG_STATUS3            0x0D
+#define S2MU004_CHG_STATUS4            0x0E
+#define S2MU004_CHG_STATUS5            0x0F
+#define S2MU004_CHG_CTRL0              0x10
+#define S2MU004_CHG_CTRL1              0x11
+#define S2MU004_CHG_CTRL2              0x12
+#define S2MU004_CHG_CTRL3              0x13
+#define S2MU004_CHG_CTRL4              0x14
+#define S2MU004_CHG_CTRL5              0x15
+#define S2MU004_CHG_CTRL6              0x16
+#define S2MU004_CHG_CTRL7              0x17
+#define S2MU004_CHG_CTRL8              0x18
+#define S2MU004_CHG_CTRL9              0x19
+#define S2MU004_CHG_CTRL10             0x1A
+#define S2MU004_CHG_CTRL11             0x1B
+#define S2MU004_CHG_CTRL12             0x1C
+#define S2MU004_CHG_CTRL13             0x1D
+#define S2MU004_CHG_CTRL14             0x1E
+#define S2MU004_CHG_CTRL15             0x1F
+#define S2MU004_CHG_CTRL16             0x20
+#define S2MU004_CHG_CTRL17             0x21
+#define S2MU004_CHG_CTRL18             0x22
+#define S2MU004_CHG_CTRL19             0x23
+#define S2MU004_CHG_CTRL20             0x24
+
+#define S2MU004_PWRSEL_CTRL0   0x72
+#define PWRSEL_CTRL0_SHIFT             7
+#define PWRSEL_CTRL0_WIDTH             1
+#define PWRSEL_CTRL0_MASK      MASK(PWRSEL_CTRL0_WIDTH, PWRSEL_CTRL0_SHIFT)
+
+#define EN_JIG_REG_AP_SHIFT            7
+#define EN_JIG_REG_AP_WIDTH            1
+#define EN_JIG_REG_AP_MASK     MASK(EN_JIG_REG_AP_WIDTH, EN_JIG_REG_AP_SHIFT)
+
+/* S2MU004_SC_INT_MASK */
+#define Poor_CHG_INT_SHIFT     1
+#define Poor_CHG_INT_MASK      BIT(Poor_CHG_INT_SHIFT)
+
+/* S2MU004_CHG_STATUS0 */
+#define FG_SOC_STATUS_SHIFT    0
+#define FG_SOC_STATUS_WIDTH    2
+#define FG_SOC_STATUS_MASK     MASK(FG_SOC_STATUS_WIDTH, FG_SOC_STATUS_SHIFT)
+
+#define WCIN_STATUS_SHIFT      2
+#define WCIN_STATUS_WIDTH      3
+#define WCIN_STATUS_MASK       MASK(WCIN_STATUS_WIDTH, WCIN_STATUS_SHIFT)
+
+#define WCIN_M_SHIFT   6
+#define WCIN_M_MASK            BIT(WCIN_M_SHIFT)
+
+#define CHGIN_STATUS_SHIFT     5
+#define CHGIN_STATUS_WIDTH     3
+#define CHGIN_STATUS_MASK      MASK(CHGIN_STATUS_WIDTH, CHGIN_STATUS_SHIFT)
+
+#define VBUS_OVP_MASK          0xE0
+#define VBUS_OVP_SHIFT         5
+
+/* S2MU004_CHG_STATUS1 */
+#define SELF_DISCHG_STATUS_SHIFT       7
+#define SELF_DISCHG_STATUS_MASK                BIT(SELF_DISCHG_STATUS_SHIFT)
+
+#define CHG_FAULT_STATUS_SHIFT         3
+#define CHG_FAULT_STATUS_WIDTH         4
+#define CHG_FAULT_STATUS_MASK          MASK(CHG_FAULT_STATUS_WIDTH,\
+                                        CHG_FAULT_STATUS_SHIFT)
+
+#define CHG_STATUS_PRE_CHARGE  1
+#define CHG_STATUS_FAST_CHARGE 2
+#define CHG_STATUS_WD_SUSPEND  3
+#define CHG_STATUS_WD_RST      4
+#define CHG_STATUS_TSD         5
+#define CHG_STATUS_TFB         6
+
+#define CHG_Restart_STATUS_SHIFT       2
+#define CHG_Restart_STATUS_MASK                BIT(CHG_Restart_STATUS_SHIFT)
+
+#define TOP_OFF_STATUS_SHIFT           1
+#define TOP_OFF_STATUS_MASK            BIT(TOP_OFF_STATUS_SHIFT)
+
+#define DONE_STATUS_SHIFT      0
+#define DONE_STATUS_MASK       BIT(DONE_STATUS_SHIFT)
+
+/* S2MU004_CHG_STATUS2 */
+#define OTG_STATUS_SHIFT       5
+#define OTG_STATUS_WIDTH       3
+#define OTG_STATUS_MASK                MASK(OTG_STATUS_WIDTH, OTG_STATUS_SHIFT)
+
+#define TX_STATUS_SHIFT                2
+#define TX_STATUS_WIDTH                3
+#define TX_STATUS_MASK         MASK(TX_STATUS_WIDTH, TX_STATUS_SHIFT)
+
+#define SYS_STATUS_SHIFT       2
+#define SYS_STATUS_WIDTH       3
+#define SYS_STATUS_MASK                MASK(SYS_STATUS_WIDTH, SYS_STATUS_SHIFT)
+
+/* S2MU004_CHG_STATUS3 */
+#define DET_BAT_STATUS_SHIFT   0
+#define DET_BAT_STATUS_MASK    BIT(DET_BAT_STATUS_SHIFT)
+
+#define BAT_STATUS_SHIFT       1
+#define BAT_STATUS_WIDTH       2
+#define BAT_STATUS_MASK                MASK(BAT_STATUS_WIDTH, BAT_STATUS_SHIFT)
+
+#define AICL_STATUS_SHIFT      4
+#define AICL_STATUS_WIDTH      2
+#define AICL_STATUS_MASK       MASK(AICL_STATUS_WIDTH, AICL_STATUS_SHIFT)
+
+#define ICR_STATUS_SHIFT       6
+#define ICR_STATUS_MASK                BIT(ICR_STATUS_SHIFT)
+
+#define IVR_STATUS_SHIFT       7
+#define IVR_STATUS_MASK                BIT(IVR_STATUS_SHIFT)
+
+/* S2MU004_CHG_CTRL0 */
+#define EN_CHG_SHIFT           7
+#define EN_CHG_MASK            BIT(EN_CHG_SHIFT)
+
+#define REG_MODE_SHIFT         0
+#define REG_MODE_WIDTH         4
+#define REG_MODE_MASK          MASK(REG_MODE_WIDTH, REG_MODE_SHIFT)
+
+#define CHARGER_OFF_MODE       0
+#define CHG_MODE               3
+#define BUCK_MODE              1
+#define OTG_BST_MODE           6
+
+/* S2MU004_CHG_CTRL1 */
+
+/* S2MU004_CHG_CTRL2 */
+#define INPUT_CURRENT_LIMIT_SHIFT      0
+#define INPUT_CURRENT_LIMIT_WIDTH      7
+#define INPUT_CURRENT_LIMIT_MASK       MASK(INPUT_CURRENT_LIMIT_WIDTH,\
+                                       INPUT_CURRENT_LIMIT_SHIFT)
+
+/* S2MU004_CHG_CTRL4 */
+#define OTG_OCP_SW_ON_SHIFT            5
+#define OTG_OCP_SW_ON_MASK             BIT(OTG_OCP_SW_ON_SHIFT)
+
+#define OTG_OCP_SW_OFF_SHIFT   4
+#define OTG_OCP_SW_OFF_MASK            BIT(OTG_OCP_SW_OFF_SHIFT)
+
+#define SET_OTG_OCP_SHIFT      2
+#define SET_OTG_OCP_WIDTH      2
+#define SET_OTG_OCP_MASK       MASK(SET_OTG_OCP_WIDTH, SET_OTG_OCP_SHIFT)
+
+/* S2MU004_CHG_CTRL5 */
+#define SET_CHG_2L_DROP_SHIFT  4
+#define SET_CHG_2L_DROP_WIDTH  2
+#define SET_CHG_2L_DROP_MASK   MASK(SET_CHG_2L_DROP_WIDTH,\
+                               SET_CHG_2L_DROP_SHIFT)
+
+#define SET_CHG_3L_DROP_SHIFT  6
+#define SET_CHG_3L_DROP_WIDTH  2
+#define SET_CHG_3L_DROP_MASK   MASK(SET_CHG_3L_DROP_WIDTH,\
+                                       SET_CHG_3L_DROP_SHIFT)
+
+/* S2MU004_CHG_CTRL6 */
+#define SET_VF_VBAT_SHIFT      0
+#define SET_VF_VBAT_WIDTH      6
+#define SET_VF_VBAT_MASK       MASK(SET_VF_VBAT_WIDTH, SET_VF_VBAT_SHIFT)
+
+/* S2MU004_CHG_CTRL7 */
+#define SET_VF_VBYP_SHIFT      5
+#define SET_VF_VBYP_WIDTH      2
+#define SET_VF_VBYP_MASK       MASK(SET_VF_VBYP_WIDTH, SET_VF_VBYP_SHIFT)
+#define SET_VSYS_SHIFT 0
+#define SET_VSYS_WIDTH 3
+#define SET_VSYS_MASK  MASK(SET_VSYS_WIDTH, SET_VSYS_SHIFT)
+
+/* S2MU004_CHG_CTRL8 */
+#define COOL_CHARGING_CURRENT_SHIFT    0
+#define COOL_CHARGING_CURRENT_WIDTH    7
+#define COOL_CHARGING_CURRENT_MASK     MASK(COOL_CHARGING_CURRENT_WIDTH,\
+                                       COOL_CHARGING_CURRENT_SHIFT)
+
+/* S2MU004_CHG_CTRL9 */
+#define FAST_CHARGING_CURRENT_SHIFT    0
+#define FAST_CHARGING_CURRENT_WIDTH    7
+#define FAST_CHARGING_CURRENT_MASK     MASK(FAST_CHARGING_CURRENT_WIDTH,\
+                                       FAST_CHARGING_CURRENT_SHIFT)
+
+/* S2MU004_CHG_CTRL11 */
+#define FIRST_TOPOFF_CURRENT_SHIFT     0
+#define FIRST_TOPOFF_CURRENT_WIDTH     4
+#define FIRST_TOPOFF_CURRENT_MASK      MASK(FIRST_TOPOFF_CURRENT_WIDTH,\
+                                       FIRST_TOPOFF_CURRENT_SHIFT)
+
+#define SECOND_TOPOFF_CURRENT_SHIFT    4
+#define SECOND_TOPOFF_CURRENT_WIDTH    4
+#define SECOND_TOPOFF_CURRENT_MASK     MASK(SECOND_TOPOFF_CURRENT_WIDTH,\
+                                       SECOND_TOPOFF_CURRENT_SHIFT)
+
+/* S2MU004_CHG_CTRL12 */
+#define SET_OSC_BUCK_SHIFT             0
+#define SET_OSC_BUCK_WIDTH             3
+#define SET_OSC_BUCK_MASK              MASK(SET_OSC_BUCK_WIDTH,\
+                                       SET_OSC_BUCK_SHIFT)
+
+#define SET_OSC_BUCK_3L_SHIFT          3
+#define SET_OSC_BUCK_3L_WIDTH          3
+#define SET_OSC_BUCK_3L_MASK           MASK(SET_OSC_BUCK_3L_WIDTH,\
+                                       SET_OSC_BUCK_3L_SHIFT)
+
+enum {
+       S2MU004_OSC_BUCK_FRQ_500kHz     = 0x0,
+       S2MU004_OSC_BUCK_FRQ_750kHz     = 0x1,
+       S2MU004_OSC_BUCK_FRQ_1MHz       = 0x2,
+       S2MU004_OSC_BUCK_FRQ_1P25MHz    = 0x3,
+       S2MU004_OSC_BUCK_FRQ_1P5MHz     = 0x4,
+       S2MU004_OSC_BUCK_FRQ_1P75MHz    = 0x5,
+       S2MU004_OSC_BUCK_FRQ_2MHz       = 0x6,
+       S2MU004_OSC_BUCK_FRQ_2P25MHz    = 0x7,
+};
+
+/* S2MU004_CHG_CTRL13 */
+#define SET_IVR_Recovery_SHIFT 5
+#define SET_IVR_Recovery_MASK  BIT(SET_IVR_Recovery_SHIFT)
+
+#define SET_EN_WDT_SHIFT 1
+#define SET_EN_WDT_MASK BIT(SET_EN_WDT_SHIFT)
+
+#define SET_EN_WDT_AP_RESET_SHIFT 0
+#define SET_EN_WDT_AP_RESET_MASK BIT(SET_EN_WDT_AP_RESET_SHIFT)
+
+/* S2MU004_CHG_CTRL14 */
+#define WDT_CLR_SHIFT 0
+#define WDT_CLR_MASK BIT(WDT_CLR_SHIFT)
+
+/* S2MU004_CHG_CTRL15 */
+#define SET_OSC_BST_SHIFT      5
+#define SET_OSC_BST_WIDTH      3
+#define SET_OSC_BST_MASK       MASK(SET_OSC_BST_WIDTH, SET_OSC_BST_SHIFT)
+
+/* S2MU004_CHG_CTRL16 */
+#define SET_TIME_CHG_SHIFT     3
+#define SET_TIME_CHG_WIDTH     3
+#define SET_TIME_CHG_MASK      MASK(SET_TIME_CHG_WIDTH, SET_TIME_CHG_SHIFT)
+
+/* S2MU004_CHG_CTRL17 */
+#define TOP_OFF_TIME_SHIFT    3
+#define TOP_OFF_TIME_WIDTH    3
+#define TOP_OFF_TIME_MASK    MASK(TOP_OFF_TIME_WIDTH, TOP_OFF_TIME_SHIFT)
+
+#define WDT_TIME_SHIFT        0
+#define WDT_TIME_WIDTH        3
+#define WDT_TIME_MASK        MASK(WDT_TIME_WIDTH, WDT_TIME_SHIFT)
+
+/* S2MU004_CHG_CTRL18 */
+#define CHGIN_ON_SHIFT         2
+#define CHGIN_ON_WIDTH         2
+#define CHGIN_ON_MASK          MASK(CHGIN_ON_WIDTH, CHGIN_ON_SHIFT)
+
+/* S2MU005_REG_SELFDIS_CFG1 */
+#define FC_SELF_DISCHG_SHIFT   3
+#define FC_SELF_DISCHG_MASK            BIT(FC_SELF_DISCHG_SHIFT)
+
+/* S2MU004_REG_SELFDIS_CFG3 */
+#define SELF_DISCHG_MODE_SHIFT 7
+#define SELF_DISCHG_MODE_MASK  BIT(SELF_DISCHG_MODE_SHIFT)
+
+#define FAKE_BAT_LEVEL          50
+
+enum {
+       CHIP_ID = 0,
+};
+
+ssize_t s2mu004_chg_show_attrs(struct device *dev,
+               struct device_attribute *attr, char *buf);
+
+ssize_t s2mu004_chg_store_attrs(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count);
+
+#define S2MU004_CHARGER_ATTR(_name)                            \
+{                                                                          \
+       .attr = {.name = #_name, .mode = 0664},     \
+       .show = s2mu004_chg_show_attrs,                     \
+       .store = s2mu004_chg_store_attrs,                       \
+}
+
+enum {
+       CHG_REG = 0,
+       CHG_DATA,
+       CHG_REGS,
+};
+
+enum {
+       S2MU004_TOPOFF_TIMER_500us      = 0x0,
+       S2MU004_TOPOFF_TIMER_5m         = 0x1,
+       S2MU004_TOPOFF_TIMER_10m        = 0x2,
+       S2MU004_TOPOFF_TIMER_30m        = 0x3,
+       S2MU004_TOPOFF_TIMER_50m        = 0x4,
+       S2MU004_TOPOFF_TIMER_70m        = 0x5,
+       S2MU004_TOPOFF_TIMER_90m        = 0x6,
+       S2MU004_TOPOFF_TIMER_DIS        = 0x7,
+};
+
+enum {
+       S2MU004_WDT_TIMER_40s   = 0x1,
+       S2MU004_WDT_TIMER_50s   = 0x2,
+       S2MU004_WDT_TIMER_60s   = 0x3,
+       S2MU004_WDT_TIMER_70s   = 0x4,
+       S2MU004_WDT_TIMER_80s   = 0x5,
+       S2MU004_WDT_TIMER_90s   = 0x6,
+       S2MU004_WDT_TIMER_100s  = 0x7,
+};
+
+enum {
+       S2MU004_FC_CHG_TIMER_4hr        = 0x1,
+       S2MU004_FC_CHG_TIMER_6hr        = 0x2,
+       S2MU004_FC_CHG_TIMER_8hr        = 0x3,
+       S2MU004_FC_CHG_TIMER_10hr       = 0x4,
+       S2MU004_FC_CHG_TIMER_12hr       = 0x5,
+       S2MU004_FC_CHG_TIMER_14hr       = 0x6,
+       S2MU004_FC_CHG_TIMER_16hr       = 0x7,
+};
+
+enum {
+       S2MU004_SET_OTG_OCP_500mA   = 0x0,
+       S2MU004_SET_OTG_OCP_900mA   = 0x1,
+       S2MU004_SET_OTG_OCP_1200mA  = 0x2,
+       S2MU004_SET_OTG_OCP_1500mA  = 0x3,
+};
+
+typedef struct s2mu004_charger_platform_data {
+       s2mu00x_charging_current_t *charging_current_table;
+       s2mu00x_charging_current_t *charging_current;
+       int chg_float_voltage;
+       char *charger_name;
+       char *fuelgauge_name;
+       bool chg_eoc_dualpath;
+       int recharge_vcell;
+       uint32_t is_1MHz_switching:1;
+       int chg_switching_freq;
+} s2mu004_charger_platform_data_t;
+
+
+struct s2mu004_charger_data {
+       struct i2c_client       *i2c;
+       struct device *dev;
+       struct s2mu004_platform_data *s2mu004_pdata;
+       struct delayed_work charger_work;
+       struct delayed_work otg_vbus_work;
+
+       struct workqueue_struct *charger_wqueue;
+       struct power_supply *psy_chg;
+       struct power_supply_desc psy_chg_desc;
+       struct power_supply *psy_otg;
+       struct power_supply_desc psy_otg_desc;
+
+       s2mu004_charger_platform_data_t *pdata;
+       int dev_id;
+       int input_current;
+       int charging_current;
+       int topoff_current;
+       int siop_level;
+       int cable_type;
+       int battery_cable_type;
+       bool is_charging;
+       struct mutex charger_mutex;
+       bool noti_check;
+
+       /* register programming */
+       int reg_addr;
+       int reg_data;
+
+       bool full_charged;
+       bool ovp;
+       bool otg_on;
+
+       int unhealth_cnt;
+       bool battery_valid;
+       int status;
+       int health;
+
+       struct delayed_work get_capacity;
+       struct delayed_work afc_current_down;
+       struct delayed_work polling_work;
+
+       /* s2mu004 */
+       int irq_det_bat;
+       int irq_chg;
+       int irq_chgin;
+       int irq_chg_fault;
+       int irq_vbus;
+       int irq_rst;
+       int irq_done;
+       int irq_sys;
+       int irq_event;
+
+       int charge_mode;
+
+#if defined(CONFIG_FUELGAUGE_S2MU004)
+       int voltage_now;
+       int voltage_avg;
+       int voltage_ocv;
+       unsigned int capacity;
+#endif
+
+#if defined(CONFIG_MUIC_NOTIFIER)
+       struct notifier_block cable_check;
+#endif
+};
+
+#endif /*S2MU004_CHARGER_H*/
diff --git a/include/linux/power/s2mu004_fuelgauge.h b/include/linux/power/s2mu004_fuelgauge.h
new file mode 100644 (file)
index 0000000..717909b
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * s2mu004_fuelgauge.h
+ * Samsung S2MU004 Fuel Gauge Header
+ *
+ * Copyright (C) 2015 Samsung Electronics, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __S2MU004_FUELGAUGE_H
+#define __S2MU004_FUELGAUGE_H __FILE__
+
+#if defined(ANDROID_ALARM_ACTIVATED)
+#include <linux/android_alarm.h>
+#endif
+
+#include <linux/wakelock.h>
+#include <linux/power/s2mu00x_battery.h>
+
+/* Slave address should be shifted to the right 1bit.
+ * R/W bit should NOT be included.
+ */
+
+#define S2MU004_REG_STATUS              0x00
+#define S2MU004_REG_IRQ                 0x02
+#define S2MU004_REG_RVBAT               0x04
+#define S2MU004_REG_RCUR_CC             0x06
+#define S2MU004_REG_RSOC                0x08
+#define S2MU004_REG_MONOUT              0x0A
+#define S2MU004_REG_MONOUT_SEL          0x0C
+#define S2MU004_REG_RBATCAP             0x0E
+#define S2MU004_REG_RZADJ               0x12
+#define S2MU004_REG_RBATZ0              0x16
+#define S2MU004_REG_RBATZ1              0x18
+#define S2MU004_REG_IRQ_LVL             0x1A
+#define S2MU004_REG_START               0x1E
+#define S2MU004_REG_CTRL0               0x25
+#define S2MU004_REG_FG_ID               0x48
+#define S2MU004_REG_COFFSET             0x5A
+
+enum {
+       CURRENT_MODE = 0,
+       LOW_SOC_VOLTAGE_MODE,
+       HIGH_SOC_VOLTAGE_MODE,
+       END_MODE,
+};
+
+struct sec_fg_info {
+       /* test print count */
+       int pr_cnt;
+
+       /* full charge comp */
+       /* struct delayed_work     full_comp_work; */
+       u32 previous_fullcap;
+       u32 previous_vffullcap;
+       /* low battery comp */
+       int low_batt_comp_flag;
+       /* low battery boot */
+       int low_batt_boot_flag;
+       bool is_low_batt_alarm;
+
+       /* battery info */
+       int soc;
+
+       /* copy from platform data DTS or update by shell script */
+       int battery_table1[88]; /* evt1 */
+       int battery_table2[22]; /* evt1 */
+       int battery_table3[88]; /* evt2 */
+       int battery_table4[22]; /* evt2 */
+       int soc_arr_evt1[22];
+       int ocv_arr_evt1[22];
+       int soc_arr_evt2[22];
+       int ocv_arr_evt2[22];
+       int soc_arr_val[22];
+       int ocv_arr_val[22];
+
+       int batcap[4];
+       int accum[2];
+
+       /* miscellaneous */
+       unsigned long fullcap_check_interval;
+       int full_check_flag;
+       bool is_first_check;
+};
+
+struct s2mu004_platform_data {
+       int capacity_max;
+       int capacity_max_margin;
+       int capacity_min;
+       int capacity_calculation_type;
+       int fuel_alert_soc;
+       int fuel_alert_vol;
+       int fullsocthr;
+       int fg_irq;
+       unsigned int capacity_full;
+
+       char *fuelgauge_name;
+
+       bool repeated_fuelalert;
+
+       struct sec_charging_current *charging_current;
+};
+
+struct s2mu004_fuelgauge_data {
+       struct device           *dev;
+       struct i2c_client       *i2c;
+       struct i2c_client       *pmic;
+       struct mutex            fuelgauge_mutex;
+       struct s2mu004_platform_data *pdata;
+       struct power_supply     *psy_fg;
+       struct power_supply_desc     psy_fg_desc;
+       /* struct delayed_work isr_work; */
+       int cable_type;
+       bool is_charging;
+       int mode;
+       int revision;
+
+       /* HW-dedicated fuel guage info structure
+        * used in individual fuel gauge file only
+        * (ex. dummy_fuelgauge.c)
+        */
+       struct sec_fg_info      info;
+       bool is_fuel_alerted;
+       struct wake_lock fuel_alert_wake_lock;
+
+       unsigned int capacity_old;      /* only for atomic calculation */
+       unsigned int capacity_max;      /* only for dynamic calculation */
+       unsigned int standard_capacity;
+
+       bool initial_update_of_soc;
+       bool sleep_initial_update_of_soc;
+       struct mutex fg_lock;
+       struct delayed_work isr_work;
+
+       /* register programming */
+       int reg_addr;
+       u8 reg_data[2];
+       u8 reg_OTP_4F;
+       u8 reg_OTP_4E;
+
+       unsigned int pre_soc;
+       int fg_irq;
+       int diff_soc;
+       int target_ocv;
+       int vm_soc;
+       bool cc_on;
+       u16 coffset_old;
+       bool coffset_flag;
+       bool probe_done;
+};
+#endif /* __S2MU004_FUELGAUGE_H */