[RAMEN9610-12039][9610] drivers: power, led: add s2mu106 drivers for code sync
authorKeunho Hwang <keunho.hwang@samsung.com>
Mon, 11 Feb 2019 12:37:36 +0000 (21:37 +0900)
committerCosmin Tanislav <demonsingur@gmail.com>
Mon, 22 Apr 2024 17:23:15 +0000 (20:23 +0300)
Add S2MU106 charger, fuelgauge, powermeter, FLED,
RGB LED, haptic driver for code sync.

Change-Id: Ib0dec6feed3aff93dfedbe8c15466218bab47ae5
Signed-off-by: Keunho Hwang <keunho.hwang@samsung.com>
18 files changed:
drivers/input/misc/Kconfig
drivers/input/misc/Makefile
drivers/input/misc/s2mu106_haptic.c [new file with mode: 0644]
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/leds-s2mu106-rgb.c [new file with mode: 0644]
drivers/leds/leds-s2mu106.c [new file with mode: 0644]
drivers/power/supply/Kconfig
drivers/power/supply/Makefile
drivers/power/supply/s2mu106_charger.c [new file with mode: 0755]
drivers/power/supply/s2mu106_fuelgauge.c [new file with mode: 0644]
drivers/power/supply/s2mu106_pmeter.c [new file with mode: 0755]
include/linux/input/s2mu106_haptic.h [new file with mode: 0644]
include/linux/leds-s2mu106-rgb.h [new file with mode: 0644]
include/linux/leds-s2mu106.h [new file with mode: 0644]
include/linux/power/s2mu106_charger.h [new file with mode: 0644]
include/linux/power/s2mu106_fuelgauge.h [new file with mode: 0644]
include/linux/power/s2mu106_pmeter.h [new file with mode: 0755]

index 3ad985ed8cd69caa92b4f78de862051b1b20e681..ab3415cbe0e5f8be3d675c9cba7995ae5e6af4f0 100644 (file)
@@ -860,4 +860,14 @@ config INPUT_HISI_POWERKEY
          To compile this driver as a module, choose M here: the
          module will be called hisi_powerkey.
 
+config INPUT_S2MU106_HAPTIC
+       tristate "s2mu106 haptic driver"
+       default n
+       depends on PWM
+       help
+        Say Y to include support for System LSI s2mu106
+        Haptic driver.
+        The Haptic Motor driver support both ERM and
+        LRA type actuators.
+
 endif
index 8da2b882c134140882ce9488cae08fddb3d52209..a7d52f4ff2f55f1de25212627f983fc675fa8091 100644 (file)
@@ -82,3 +82,4 @@ obj-$(CONFIG_INPUT_WM831X_ON)         += wm831x-on.o
 obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND)        += xen-kbdfront.o
 obj-$(CONFIG_INPUT_YEALINK)            += yealink.o
 obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR)   += ideapad_slidebar.o
+obj-$(CONFIG_INPUT_S2MU106_HAPTIC)     += s2mu106_haptic.o
diff --git a/drivers/input/misc/s2mu106_haptic.c b/drivers/input/misc/s2mu106_haptic.c
new file mode 100644 (file)
index 0000000..8575596
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ * haptic driver for s2mu106 - s2mu106_haptic.c
+ *
+ * Copyright (C) 2017 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 version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/pwm.h>
+#include <linux/input.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/samsung/s2mu106.h>
+#include <linux/input/s2mu106_haptic.h>
+#include <linux/sysfs.h>
+
+#define TEST_MODE_TIME 10000
+#define MAX_INTENSITY 100
+struct s2mu106_haptic_data *g_haptic;
+
+static void s2mu106_set_boost_voltage(struct s2mu106_haptic_data *haptic, int voltage)
+{
+       u8 data;
+       if (voltage <= 3150)
+               data = 0x00;
+       else if (voltage > 3150 && voltage <= 6300)
+               data = (voltage - 3150) / 50;
+       else
+               data = 0xFF;
+       pr_info("%s: boost voltage %d, 0x%02x\n", __func__, voltage, data);
+
+       s2mu106_update_reg(haptic->i2c, S2MU106_REG_HBST_CTRL1,
+                               data, HAPTIC_BOOST_VOLTAGE_MASK);
+}
+
+static  void s2mu106_set_intensity(struct s2mu106_haptic_data *haptic, int intensity)
+{
+       int data = 0x3FFFF;
+       int max = 0x7FFFF;
+       u8 val1, val2, val3;
+
+       if (intensity == MAX_INTENSITY)
+               data = max;
+       else if (intensity != 0) {
+               long long tmp;
+
+               tmp = (intensity * max) / 100;
+               data = (int)tmp;
+       } else
+               data = 0;
+
+       data &= 0x7FFFF;
+       val1 = data & 0x0000F;
+       val2 = (data & 0x00FF0) >> 4;
+       val3 = (data & 0x7F000) >> 12;
+
+       s2mu106_update_reg(haptic->i2c, S2MU106_REG_AMPCOEF1, val3, 0x7F);
+       s2mu106_write_reg(haptic->i2c, S2MU106_REG_AMPCOEF2, val2);
+       s2mu106_update_reg(haptic->i2c, S2MU106_REG_AMPCOEF3, val1 << 4, 0xF0);
+
+       pr_info("%s, intensity = %d, coef1 = 0x%2x, coef2 = 0x%2x, coef3 = 0x%2x\n",
+                               __func__, intensity, val1, val2, val3);
+}
+
+static void haptic_work(struct work_struct *work)
+{
+       struct s2mu106_haptic_data *haptic
+               = container_of(work, struct s2mu106_haptic_data, work);
+
+       pr_info("%s\n", __func__);
+       if (haptic->intensity > 0) {
+               if (haptic->running)
+                       return;
+               haptic->running = true;
+               pr_info("Motor Enable\n");
+               if (haptic->hap_mode == S2MU106_HAPTIC_ERM_I2C)
+                       s2mu106_write_reg(haptic->i2c, S2MU106_REG_HAPTIC_MODE, ERM_MODE_ON);
+               else
+                       pwm_enable(haptic->pwm);
+       } else {
+               if (!haptic->running)
+                       return;
+               haptic->running = false;
+               pr_info("Motor Disable\n");
+               if (haptic->hap_mode == S2MU106_HAPTIC_ERM_I2C)
+                       s2mu106_write_reg(haptic->i2c, S2MU106_REG_HAPTIC_MODE, ERM_MODE_OFF);
+               else
+                       pwm_disable(haptic->pwm);
+       }
+}
+
+static int s2mu106_haptic_play(struct input_dev *dev, void *data,
+                                      struct ff_effect *effect)
+{
+       struct s2mu106_haptic_data *haptic = input_get_drvdata(dev);
+
+       if (effect->u.rumble.strong_magnitude) {
+               haptic->intensity = effect->u.rumble.strong_magnitude;
+               s2mu106_set_intensity(haptic, haptic->intensity);
+       } else {
+               haptic->intensity = 0;
+       }
+
+       schedule_work(&haptic->work);
+       return 0;
+}
+
+/*
+static void s2mu106_haptic_open(struct input_dev *input)
+{
+//     struct s2mu106_haptic *haptic = input_get_drvdata(dev);
+}
+
+static void s2mu106_haptic_close(struct input_dev *input)
+{
+//     struct s2mu106_haptic *haptic = input_get_drvdata(dev);
+
+}
+*/
+static ssize_t intensity_store(struct device *dev,
+               struct device_attribute *devattr, const char *buf, size_t count)
+{
+       int intensity = 0, ret = 0;
+
+       ret = kstrtoint(buf, 0, &intensity);
+
+       if ((intensity < 0) || (intensity > MAX_INTENSITY)) {
+               dev_err(&g_haptic->i2c->dev, "Setting out of range intensity value\n");
+               return -EINVAL;
+       }
+
+       g_haptic->intensity = intensity;
+       s2mu106_set_intensity(g_haptic, g_haptic->intensity);
+
+       pr_debug("%s, intensity = %d\n", __func__, intensity);
+
+       return count;
+}
+
+static ssize_t intensity_show(struct device *dev,
+                                               struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "intensity: %u\n", g_haptic->intensity);
+}
+
+static DEVICE_ATTR(intensity, 0660, intensity_show, intensity_store);
+
+static ssize_t vib_enable_store(struct device *dev,
+                       struct device_attribute *devattr, const char *buf, size_t count)
+{
+       int enable = 0;
+       int ret;
+
+       ret = kstrtoint(buf, 0, &enable);
+
+       if (enable == 1)
+               schedule_work(&g_haptic->work);
+       else if (enable == 0) {
+               g_haptic->intensity = 0;
+               s2mu106_set_intensity(g_haptic, g_haptic->intensity);
+               schedule_work(&g_haptic->work);
+       } else {
+               pr_err("Invalid value!\n 0 : disable 1: enable\n");
+               return -EINVAL;
+       }
+
+       pr_debug("%s, VIB %s\n", __func__,
+                       ((enable == 1) ? "ENABLE" : "DISABLE"));
+
+       return count;
+}
+
+static ssize_t vib_enable_show(struct device *dev,
+                                                               struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "echo 1 > vib_enable\necho 0 > vib_enable\n");
+}
+
+static DEVICE_ATTR(vib_enable, 0660, vib_enable_show, vib_enable_store);
+
+#if defined(CONFIG_OF)
+static int s2mu106_haptic_parse_dt(struct device *dev,
+                       struct s2mu106_haptic_platform_data *pdata)
+{
+       struct device_node *np = of_find_node_by_name(NULL, "s2mu106-haptic");
+       u32 temp;
+       int ret;
+
+       pr_info("%s : start dt parsing\n", __func__);
+
+       if (np == NULL) {
+               pr_err("%s : error to get dt node\n", __func__);
+               goto err_parsing_dt;
+       }
+
+       /* 30.08kHz 99% duty */
+       ret = of_property_read_u32(np, "haptic,duty", &temp);
+       if (ret < 0)
+               pdata->duty = 32911;
+       else
+               pdata->duty = temp;
+
+       ret = of_property_read_u32(np, "haptic,period", &temp);
+       if (ret < 0)
+               pdata->period = 33244;
+       else
+               pdata->period = temp;
+
+       ret = of_property_read_u32(np, "haptic,pwm_id", &temp);
+       if (ret < 0) {
+               pr_err("%s : error to get dt node pwm_id\n", __func__);
+               goto err_parsing_dt;
+       } else
+               pdata->pwm_id = (u16)temp;
+
+       /* Haptic operation mode
+               0 : S2MU106_HAPTIC_ERM_I2C
+               1 : S2MU106_HAPTIC_ERM_GPIO
+               2 : S2MU106_HAPTIC_LRA
+               default : S2MU106_HAPTIC_LRA */
+       pdata->hap_mode = 2;
+       ret = of_property_read_u32(np, "haptic,operation_mode", &temp);
+       if (ret < 0) {
+               pr_err("%s : eror to get operation mode\n", __func__);
+               goto err_parsing_dt;
+       } else
+               pdata->hap_mode = temp;
+
+       /* Haptic boost setting */
+       pdata->hbst.en = (of_find_property(np, "haptic,hbst_en", NULL)) ? true : false;
+
+       pdata->hbst.automode =
+               (of_find_property(np, "haptic,hbst_automode", NULL)) ? true : false;
+
+       ret = of_property_read_u32(np, "haptic,boost_level", &temp);
+       if (ret < 0)
+               pdata->hbst.level = 5500;
+       else
+               pdata->hbst.level = temp;
+
+       /* parsing info */
+       pr_info("%s :operation_mode = %d,  period = %d, duty = %d, HBST_EN %s, HBST_AUTO_MODE %s\n", __func__,
+                       pdata->hap_mode,
+                       pdata->period, pdata->duty,
+                       pdata->hbst.en ? "enabled" : "disabled",
+                       pdata->hbst.automode ? "enabled" : "disabled");
+       pdata->init_hw = NULL;
+       pdata->motor_en = NULL;
+
+       return 0;
+
+err_parsing_dt:
+       return -1;
+}
+#endif
+
+static struct of_device_id s2mu106_haptic_match_table[] = {
+       { .compatible = "samsung,s2mu106-haptic",},
+       {},
+};
+
+static void s2mu106_haptic_initial(struct s2mu106_haptic_data *haptic)
+{
+       u8 data;
+       int ret;
+
+       haptic->hap_mode = haptic->pdata->hap_mode;
+
+       /* Haptic Boost initial setting */
+       if (haptic->pdata->hbst.en){
+               pr_info("%s : Haptic Boost Enable - Auto mode(%s)\n", __func__,
+                               haptic->pdata->hbst.automode ? "enabled" : "disabled");
+               /* Boost voltage level setting
+                       default : 5.5V */
+               s2mu106_set_boost_voltage(haptic, haptic->pdata->hbst.level);
+
+               if (haptic->pdata->hbst.automode) {
+                       s2mu106_update_reg(haptic->i2c, S2MU106_REG_HT_OTP0,
+                                               HBST_OK_MASK_EN, HBST_OK_MASK_EN);
+                       s2mu106_update_reg(haptic->i2c, S2MU106_REG_HBST_CTRL0,
+                                               0, SEL_HBST_HAPTIC_MASK);
+               } else {
+                       s2mu106_update_reg(haptic->i2c, S2MU106_REG_HT_OTP0,
+                                               0, HBST_OK_MASK_EN);
+                       s2mu106_update_reg(haptic->i2c, S2MU106_REG_HBST_CTRL0,
+                                               SEL_HBST_HAPTIC_MASK, SEL_HBST_HAPTIC_MASK);
+               }
+       } else {
+               pr_info("%s : HDVIN - Vsys HDVIN voltage : Min 3.5V\n", __func__);
+               s2mu106_update_reg(haptic->i2c, S2MU106_REG_HT_OTP2,
+                                                       0x00, VCEN_SEL_MASK);
+       }
+
+       /* Intensity setting to 0% */
+       s2mu106_set_intensity(haptic, haptic->intensity);
+       haptic->running = false;
+
+       /* mode setting */
+       switch (haptic->hap_mode) {
+       case S2MU106_HAPTIC_LRA:
+               data = LRA_MODE_EN;
+               pwm_config(haptic->pwm, haptic->pdata->duty,
+                               haptic->pdata->period);
+               s2mu106_update_reg(haptic->i2c, S2MU106_REG_OV_BK_OPTION,
+                                       LRA_MODE_SET_MASK, LRA_MODE_SET_MASK);
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_FILTERCOEF1, 0x7F);
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_FILTERCOEF2, 0x5A);
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_FILTERCOEF3, 0x02);
+               s2mu106_update_reg(haptic->i2c, S2MU106_REG_OV_WAVE_NUM, 0xF0, 0xF0);
+               break;
+       case S2MU106_HAPTIC_ERM_GPIO:
+               data = ERM_HDPWM_MODE_EN;
+               haptic->pdata->duty = 100;
+               haptic->pdata->period = 100;
+               pwm_config(haptic->pwm, haptic->pdata->duty,
+                               haptic->pdata->period);
+               break;
+       case S2MU106_HAPTIC_ERM_I2C:
+               data = ERM_MODE_OFF;
+               break;
+       default:
+               data = LRA_MODE_EN;
+               break;
+       }
+       s2mu106_write_reg(haptic->i2c, S2MU106_REG_HAPTIC_MODE, data);
+
+       if (haptic->hap_mode == S2MU106_HAPTIC_ERM_I2C ||
+               haptic->hap_mode == S2MU106_HAPTIC_ERM_GPIO) {
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_PERI_TAR1, 0x00);
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_PERI_TAR2, 0x00);
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_DUTY_TAR1, 0x00);
+               s2mu106_write_reg(haptic->i2c, S2MU106_REG_DUTY_TAR2, 0x01);
+       }
+       pr_info("%s, haptic operation mode = %d\n", __func__, haptic->hap_mode);
+
+       /* ON/OFF sysfs */
+       ret = device_create_file(haptic->dev, &dev_attr_intensity);
+       if (ret)
+               pr_err("%s : failed to create sysfs\n", __func__);
+
+       ret = device_create_file(haptic->dev, &dev_attr_vib_enable);
+       if (ret)
+               pr_err("%s : failed to create sysfs\n", __func__);
+
+}
+
+static int s2mu106_haptic_probe(struct platform_device *pdev)
+{
+       struct s2mu106_dev *s2mu106 = dev_get_drvdata(pdev->dev.parent);
+       struct s2mu106_haptic_data *haptic;
+
+       int ret = 0;
+       int error = 0;
+
+       pr_info("%s Start\n", __func__);
+
+       haptic = devm_kzalloc(&pdev->dev,
+                       sizeof(struct s2mu106_haptic_data), GFP_KERNEL);
+       if (!haptic) {
+               pr_err("%s: Failed to allocate memory\n", __func__);
+               return -ENOMEM;
+       }
+
+       haptic->dev = &pdev->dev;
+       haptic->i2c = s2mu106->haptic;
+
+       haptic->pdata = devm_kzalloc(&pdev->dev, sizeof(*(haptic->pdata)), GFP_KERNEL);
+       if (!haptic->pdata) {
+               dev_err(&pdev->dev, "Failed to allocate memory\n");
+               ret = -ENOMEM;
+               goto err_work_queue;
+       }
+
+       ret = s2mu106_haptic_parse_dt(&pdev->dev, haptic->pdata);
+       if (ret < 0)
+               goto err_work_queue;
+
+       haptic->workqueue = create_singlethread_workqueue("hap_work");
+       if (haptic->workqueue == NULL) {
+               error = -EFAULT;
+               pr_err("%s Failed to create workqueue\n", __func__);
+               goto err_work_queue;
+       }
+
+       INIT_WORK(&(haptic->work), haptic_work);
+       spin_lock_init(&(haptic->lock));
+       mutex_init(&haptic->mutex);
+
+       haptic->pwm = pwm_request(haptic->pdata->pwm_id, "vibrator");
+
+       if (haptic->pwm < 0) {
+               error = -EFAULT;
+               pr_err("%s : Failed to request pwm\n", __func__);
+               goto err_pwm_request;
+       }
+
+       pr_info("%s : request pwm, err num: %d Success\n", __func__, error);
+
+       /* Default strength of vibration 0% */
+       haptic->intensity = 0;
+
+       haptic->input_dev = devm_input_allocate_device(&pdev->dev);
+       if(!haptic->input_dev) {
+               pr_err("%s : Failed to allocate device\n", __func__);
+               goto err_create_device;
+       }
+       input_set_drvdata(haptic->input_dev, haptic);
+
+       haptic->input_dev->name = "s2mu106-haptic";
+       haptic->input_dev->dev.parent = &pdev->dev;
+//     haptic->input_dev->open = s2mu106_haptic_open;
+//     haptic->input_dev->close = s2mu106_haptic_close;
+
+       input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
+
+       ret = input_ff_create_memless(haptic->input_dev, NULL,
+                                               s2mu106_haptic_play);
+
+       ret = input_register_device(haptic->input_dev);
+       if (ret) {
+               pr_err("Failed to register input device\n");
+               goto err_create_device;
+       }
+
+       platform_set_drvdata(pdev, haptic);
+
+       s2mu106_haptic_initial(haptic);
+
+       g_haptic = haptic;
+       pr_info("%s Loaded\n", __func__);
+       return ret;
+
+err_create_device:
+       pwm_free(haptic->pwm);
+err_pwm_request:
+       destroy_workqueue(haptic->workqueue);
+err_work_queue:
+       kfree(haptic);
+       return error;
+}
+
+static int s2mu106_haptic_remove(struct platform_device *pdev)
+{
+       struct s2mu106_haptic_data *haptic = platform_get_drvdata(pdev);
+
+       pwm_free(haptic->pwm);
+       destroy_workqueue(haptic->workqueue);
+       device_remove_file(haptic->dev, &dev_attr_vib_enable);
+       device_remove_file(haptic->dev, &dev_attr_intensity);
+
+       kfree(haptic);
+       return 0;
+}
+
+static int s2mu106_haptic_suspend(struct device *dev)
+{
+       struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+       struct s2mu106_haptic_data *haptic = platform_get_drvdata(pdev);
+
+       pr_info("%s\n", __func__);
+       cancel_work_sync(&haptic->work);
+       return 0;
+}
+static int s2mu106_haptic_resume(struct device *dev)
+{
+       return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(s2mu106_haptic_pm_ops, s2mu106_haptic_suspend, s2mu106_haptic_resume);
+
+static struct platform_driver s2mu106_haptic_driver = {
+       .driver = {
+               .name   = "s2mu106-haptic",
+               .owner  = THIS_MODULE,
+               .pm     = &s2mu106_haptic_pm_ops,
+               .of_match_table = s2mu106_haptic_match_table,
+       },
+       .probe          = s2mu106_haptic_probe,
+       .remove         = s2mu106_haptic_remove,
+};
+
+static int __init s2mu106_haptic_init(void)
+{
+       pr_info("%s\n", __func__);
+       return platform_driver_register(&s2mu106_haptic_driver);
+}
+module_init(s2mu106_haptic_init);
+
+static void __exit s2mu106_haptic_exit(void)
+{
+       platform_driver_unregister(&s2mu106_haptic_driver);
+}
+module_exit(s2mu106_haptic_exit);
+
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Haptic driver for S2MU106");
index 54e69227d2fe104ff7600d6da6e358542717609c..7f7cc0043bb024b6601c2492a5b060752e330c01 100644 (file)
@@ -702,6 +702,20 @@ config LEDS_S2MPB02
        help
          This option enables support for the LEDs on the S2MPB02.
 
+config LEDS_S2MU106_FLASH
+       bool "FLASH LED support for the S2MU106"
+    depends on (MFD_S2MU106) && I2C
+       help
+         If you say yes here you will get support for
+         for the LEDS_S2MU106_FLASH led chip.
+
+config LEDS_S2MU106_RGB
+       bool "RGB LED support for the S2MU106"
+    depends on (MFD_S2MU106) && I2C
+       help
+         If you say yes here you will get support for
+         for the LEDS_S2MU106_RGB led chip.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
index 56919d7ec95718cb47d62ab86edccc2c7dbda848..d44203b9b0f347bd2e14d5a80e1f3ea9f87af93d 100644 (file)
@@ -74,6 +74,8 @@ obj-$(CONFIG_LEDS_MLXCPLD)            += leds-mlxcpld.o
 obj-$(CONFIG_LEDS_NIC78BX)             += leds-nic78bx.o
 obj-$(CONFIG_LEDS_MT6323)              += leds-mt6323.o
 obj-$(CONFIG_LEDS_S2MPB02)             += leds-s2mpb02.o
+obj-$(CONFIG_LEDS_S2MU106_FLASH)       += leds-s2mu106.o
+obj-$(CONFIG_LEDS_S2MU106_RGB) += leds-s2mu106-rgb.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)          += leds-dac124s085.o
diff --git a/drivers/leds/leds-s2mu106-rgb.c b/drivers/leds/leds-s2mu106-rgb.c
new file mode 100644 (file)
index 0000000..e0f1d74
--- /dev/null
@@ -0,0 +1,804 @@
+/*
+ * leds-s2mu106-rgb.c - LED class driver for S2MU106 RGB LEDs.
+ *
+ * Copyright (C) 2018 Samsung Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/leds.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/mfd/samsung/s2mu106.h>
+#include <linux/leds-s2mu106-rgb.h>
+#include <linux/platform_device.h>
+
+static void s2mu106_rgb_test_read(struct s2mu106_rgb_data *rgb)
+{
+       u8 data;
+       char str[1016] = {0,};
+       int i;
+       struct i2c_client *i2c = rgb->i2c;
+
+    for (i = 0x43; i <= 0x51; i++) {
+               s2mu106_read_reg(i2c, i, &data);
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+
+       s2mu106_read_reg(i2c, 0x52, &data);
+       pr_err("%s: %s0x52:0x%02x\n", __func__, str, data);
+}
+
+static int s2mu106_rgb_get_ramp_up(struct s2mu106_rgb_data *rgb, int led)
+{
+       u8 data;
+       int ret = -1;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       s2mu106_read_reg(rgb->i2c,
+               s2mu106_rgb_ramp_reg[led], &data);
+       ret = (data & S2MU106_RGB_LEDX_RAMP_UP_MASK) >> 4;
+       ret = (ret > 8) ? (ret - 8) * 200 + 800:
+                               ret * 100;
+
+       pr_info("%s: data = 0x%02x, %dms \n", __func__,
+               data, ret);
+
+       return ret;
+}
+
+/* 1ms unit*/
+static int s2mu106_rgb_set_ramp_up(struct s2mu106_rgb_data *rgb,
+                                                               int led, int time)
+{
+       u8 data = 0;
+       int set_time = 0;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       if (time > S2MU106_RGB_RAMP_MAX)
+               time = S2MU106_RGB_RAMP_MAX;
+       else if (time < 0)
+               time = 0;
+
+       data = (time > 800)?(time - 800)/200 + 8:
+               time / 100;
+
+       s2mu106_update_reg(rgb->i2c, s2mu106_rgb_ramp_reg[led],
+               data << 4, S2MU106_RGB_LEDX_RAMP_UP_MASK);
+
+       set_time = s2mu106_rgb_get_ramp_up(rgb, led);
+       pr_info("%s: time = %dms, set_time = %dms \n", __func__,
+               time, set_time);
+
+       return 0;
+}
+
+static int s2mu106_rgb_get_ramp_down(struct s2mu106_rgb_data *rgb, int led)
+{
+       u8 data;
+       int ret = -1;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       s2mu106_read_reg(rgb->i2c,
+               s2mu106_rgb_ramp_reg[led], &data);
+       ret = data & S2MU106_RGB_LEDX_RAMP_DOWN_MASK;
+       ret = (ret > 8) ? (ret - 8) * 200 + 800:
+                               ret * 100;
+
+       pr_info("%s: data = 0x%02x, %dms \n", __func__,
+               data, ret);
+
+       return ret;
+
+}
+
+static int s2mu106_rgb_set_ramp_down(struct s2mu106_rgb_data *rgb,
+                                                               int led, int time)
+{
+       u8 data = 0;
+       int set_time = 0;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       if (time > S2MU106_RGB_RAMP_MAX)
+               time = S2MU106_RGB_RAMP_MAX;
+       else if (time < 0)
+               time = 0;
+
+       data = (time > 800)?(time - 800)/200 + 8:
+               time / 100;
+
+       s2mu106_update_reg(rgb->i2c, s2mu106_rgb_ramp_reg[led],
+               data, S2MU106_RGB_LEDX_RAMP_DOWN_MASK);
+
+       set_time = s2mu106_rgb_get_ramp_down(rgb, led);
+       pr_info("%s: time = %dms, set_time = %dms \n", __func__,
+               time, set_time);
+
+       return 0;
+}
+
+/* 1ms unit */
+static int s2mu106_rgb_get_on_dur(struct s2mu106_rgb_data *rgb, int led)
+{
+       u8 data;
+       int ret = -1;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       s2mu106_read_reg(rgb->i2c, s2mu106_rgb_dur_reg[led],
+               &data);
+       data = (data & S2MU106_RGB_LEDX_ON_DUR_MASK) >> 4;
+       ret = (data < 5) ? data * 100 + 100 : (data - 4) * 250 + 500;
+
+       pr_info("%s: data = 0x%02x, %dms \n", __func__,
+               data, ret);
+
+       return ret;
+}
+
+static int s2mu106_rgb_set_on_dur(struct s2mu106_rgb_data *rgb,
+                                                               int led, int time)
+{
+       u8 data;
+       int set_time = 0;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       if (time > S2MU106_RGB_ON_MAX)
+               time = S2MU106_RGB_ON_MAX;
+       else if (time < 0)
+               time = 0;
+
+       data = (time > 500)?(time - 500) / 250 + 4:
+               (time - 100) / 100;
+
+       s2mu106_update_reg(rgb->i2c, s2mu106_rgb_dur_reg[led],
+               data << 4, S2MU106_RGB_LEDX_ON_DUR_MASK);
+
+       set_time = s2mu106_rgb_get_on_dur(rgb, led);
+       pr_info("%s: time = %dms, set_time = %dms\n", __func__,
+               time, set_time);
+
+       return 0;
+}
+
+static int s2mu106_rgb_get_off_dur(struct s2mu106_rgb_data *rgb, int led)
+{
+       u8 data;
+       u8 time_mode;
+       int ret = -1;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       s2mu106_read_reg(rgb->i2c, s2mu106_rgb_dur_reg[led],
+               &data);
+       s2mu106_read_reg(rgb->i2c, S2MU106_RGB_LED_CTRL0,
+               &time_mode);
+
+       data = (data & S2MU106_RGB_LEDX_OFF_DUR_MASK);
+       time_mode = !!(time_mode & s2mu106_rgb_off_dur_mode_mask[led]);
+       ret = s2mu106_off_time[time_mode][data];
+
+       pr_info("%s: time_mode = %d, data = 0x%02x, %dms \n", __func__,
+               time_mode, data, ret);
+
+       return ret;
+}
+
+static int s2mu106_rgb_set_off_dur(struct s2mu106_rgb_data *rgb,
+                                                               int led, int time)
+{
+       u8 data;
+       int max = rgb->off_dur_mode?S2MU106_RGB_OFF_MAX1:S2MU106_RGB_OFF_MAX0;
+       int diff = max;
+       int set_time = 0;
+       int i;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       if (time > max)
+               time = max;
+       else if (time < 0)
+               time = 0;
+
+       for (i = 0; i < S2MU106_RGB_ONOFF_TIME_NUM; i++) {
+               if (ABS(time - s2mu106_off_time[rgb->off_dur_mode][i]) < diff) {
+                       diff = ABS(time - s2mu106_off_time[rgb->off_dur_mode][i]);
+                       data = i;
+               }
+       }
+
+       pr_info("%s: mode = 0x%02x, data = 0x%02x\n", __func__,
+               rgb->off_dur_mode, data);
+
+       s2mu106_update_reg(rgb->i2c, s2mu106_rgb_dur_reg[led],
+               data, S2MU106_RGB_LEDX_OFF_DUR_MASK);
+
+       set_time = s2mu106_rgb_get_off_dur(rgb, led);
+       pr_info("%s: time = %dms, set_time = %dms\n", __func__,
+               time, set_time);
+
+       return 0;
+}
+
+/* 0.1mA unit*/
+static int s2mu106_rgb_get_curr(struct s2mu106_rgb_data *rgb, int led)
+{
+       u8 data;
+       u8 source = 0;
+       int ret = -1;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       source = s2mu106_rgb_curr_reg[led];
+
+       s2mu106_read_reg(rgb->i2c, source, &data);
+
+       pr_info("%s: data = 0x%02x, curr = %d.%d\n", __func__, data,
+               data/10, data%10);
+
+       ret = data;
+
+       return ret;
+}
+
+/* 0.1mA unit*/
+static int s2mu106_rgb_set_curr(struct s2mu106_rgb_data *rgb,
+                                                               int led, int curr)
+{
+       u8 data = 0;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       if (curr < 0)
+               data = 0;
+       else if (curr > S2MU106_RGB_CURR_MAX)
+               data = S2MU106_RGB_CURR_MAX;
+       else
+               data = curr;
+
+       pr_err("%s: led: %d, curr: %d.%dmA\n", __func__, led,
+               data/10, data%10);
+
+       s2mu106_write_reg(rgb->i2c,
+               s2mu106_rgb_curr_reg[led], data);
+
+       return 0;
+}
+
+static int s2mu106_rgb_set_mode(struct s2mu106_rgb_data *rgb,
+                                                               int led, int mode)
+{
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX) ||
+               (mode < 0) || (mode > S2MU106_RGB_MODE_MAX)) {
+                       pr_err("%s: Wrong led or mode.\n", __func__);
+                       return -EFAULT;
+       }
+
+       pr_err("%s: led: %d, mode: %d\n", __func__, led, mode);
+
+       s2mu106_update_reg(rgb->i2c, S2MU106_RGB_LED_EN,
+               mode << s2mu106_rgb_led_en_shift[led],
+               s2mu106_rgb_led_en_mask[led]);
+
+       return 0;
+}
+
+static int s2mu106_rgb_get_mode(struct s2mu106_rgb_data *rgb, int led)
+{
+       u8 led_en;
+       int ret = -1;
+
+       if ((led < 1) || (led > S2MU106_RGB_LED_MAX)) {
+               pr_info("%s: Wrong led!!\n", __func__);
+               return -1;
+       }
+
+       s2mu106_read_reg(rgb->i2c, S2MU106_RGB_LED_EN, &led_en);
+
+       pr_info("%s: S2MU106_RGB_LED_EN: 0x%02x\n", __func__, led_en);
+
+       ret = (led_en & s2mu106_rgb_led_en_mask[led])
+                                       >> s2mu106_rgb_led_en_shift[led];
+
+       return ret;
+}
+
+static void s2mu106_rgb_set_off_dur_mode(struct s2mu106_rgb_data *rgb,
+       int mode)
+{
+       int i, cnt = 0;
+    char str[200] = {0,};
+
+       s2mu106_update_reg(rgb->i2c, S2MU106_RGB_LED_CTRL0,
+               mode ? S2MU106_RGB_OFF_DUR_MODE_MASK:0,
+               S2MU106_RGB_OFF_DUR_MODE_MASK);
+       for (i = 0; i < S2MU106_RGB_ONOFF_TIME_NUM; i++) {
+               cnt += sprintf(str+strlen(str), "%dms, ",
+                       s2mu106_off_time[mode][i]);
+       }
+
+       pr_err("%s: mode: %d, time: %s\n", __func__, mode, str);
+}
+
+/* Control RGB LED1
+ * brightness: 0 -> off
+ * brightness: 1 ~ 255 -> on(constant mode),
+ *                        brightness * 0.1mA current
+ */
+static void s2mu106_rgb_set_brightness(struct led_classdev *led_cdev,
+               enum led_brightness brightness)
+{
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+
+       if (brightness < 0) {
+               pr_err("%s: Wrong brightness.\n", __func__);
+               return;
+       }
+
+       if (brightness == LED_OFF)
+               s2mu106_rgb_set_mode(rgb,
+                               S2MU106_RGB_BRIGHTNESS_LED, S2MU106_RGB_MODE_OFF);
+       else {
+               s2mu106_rgb_set_curr(rgb,
+                               S2MU106_RGB_BRIGHTNESS_LED, brightness);
+               s2mu106_rgb_set_mode(rgb,
+                               S2MU106_RGB_BRIGHTNESS_LED, S2MU106_RGB_MODE_CONST);
+       }
+
+       pr_info("%s: brightness = %d\n", __func__, brightness);
+
+       s2mu106_rgb_test_read(rgb);
+}
+
+static enum led_brightness s2mu106_rgb_get_brightness(
+               struct led_classdev *led_cdev)
+{
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int mode = -1;
+       int brightness = -1;
+
+       mode = s2mu106_rgb_get_mode(rgb, S2MU106_RGB_BRIGHTNESS_LED);
+
+       if (mode == S2MU106_RGB_MODE_OFF)
+               brightness = LED_OFF;
+       else
+               brightness = s2mu106_rgb_get_curr(rgb,
+                               S2MU106_RGB_BRIGHTNESS_LED);
+
+       pr_info("%s: mode = %s, brightness = %d\n", __func__,
+                       s2mu106_rgb_mode_string[mode], brightness);
+
+       return brightness;
+}
+
+static ssize_t rgb_mode_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int cnt = 0;
+       int mode = 0;
+       int i;
+    char str[1016] = {0,};
+
+       s2mu106_rgb_test_read(rgb);
+
+       for (i = 1; i <= S2MU106_RGB_LED_MAX; i++) {
+               mode = s2mu106_rgb_get_mode(rgb, i);
+               if (mode >= 0)
+                       cnt += sprintf(str+strlen(str), "LED%02d: %s, ", i,
+                               s2mu106_rgb_mode_string[mode]);
+       }
+
+       cnt += sprintf(str+strlen(str), "\n");
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t rgb_mode_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int led = -1;
+       int mode = -1;
+
+       sscanf(buf, "%d %d", &led, &mode);
+
+       if ((led <= 0) || (led > S2MU106_RGB_LED_MAX) ||
+               (mode < 0) || (mode > S2MU106_RGB_MODE_MAX)) {
+                       pr_err("%s: led: %d, mode: %d\n", __func__, led, mode);
+                       pr_err("%s: Wrong led or mode.\n", __func__);
+                       return -EFAULT;
+       }
+
+       s2mu106_rgb_set_mode(rgb, led, mode);
+       s2mu106_rgb_test_read(rgb);
+
+       return size;
+}
+
+static ssize_t rgb_curr_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int cnt = 0;
+       int curr = 0;
+       int i;
+    char str[1016] = {0,};
+
+       for (i = 1; i <= S2MU106_RGB_LED_MAX; i++) {
+               curr = s2mu106_rgb_get_curr(rgb, i);
+               if (curr >= 0)
+                       cnt += sprintf(str+strlen(str), "LED%02d: %d.%dmA, ",
+                               i, curr/10, curr%10);
+       }
+
+       cnt += sprintf(str+strlen(str), "\n");
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t rgb_curr_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int led = -1;
+       int curr = -1;
+
+       sscanf(buf, "%d %d", &led, &curr);
+
+       if ((led <= 0) || (led > S2MU106_RGB_LED_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       s2mu106_rgb_set_curr(rgb, led, curr);
+
+       s2mu106_rgb_test_read(rgb);
+
+       return size;
+}
+
+static ssize_t rgb_ramp_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int ramp_up, ramp_down;
+       int i, cnt = 0;
+    char str[1016] = {0,};
+
+       for (i = 1; i <= S2MU106_RGB_LED_MAX; i++) {
+               ramp_up = s2mu106_rgb_get_ramp_up(rgb, i);
+               ramp_down = s2mu106_rgb_get_ramp_down(rgb, i);
+               cnt += sprintf(str+strlen(str),
+                       "LED%d: ramp_up = %dms, ramp_down = %dms\n", i, ramp_up, ramp_down);
+       }
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t rgb_ramp_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int led = -1;
+       int ramp_up, ramp_down;
+
+       sscanf(buf, "%d %d %d", &led, &ramp_up, &ramp_down);
+
+       if ((led <= 0) || (led > S2MU106_RGB_LED_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       s2mu106_rgb_set_ramp_up(rgb, led, ramp_up);
+
+       s2mu106_rgb_set_ramp_down(rgb, led, ramp_down);
+
+       s2mu106_rgb_test_read(rgb);
+
+       return size;
+}
+
+static ssize_t rgb_onoff_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int on_dur, off_dur;
+       int i, cnt = 0;
+    char str[1016] = {0,};
+
+       for (i = 1; i <= S2MU106_RGB_LED_MAX; i++) {
+               on_dur = s2mu106_rgb_get_on_dur(rgb, i);
+               off_dur = s2mu106_rgb_get_off_dur(rgb, i);
+               cnt += sprintf(str+strlen(str),
+                       "LED%d: on_dur = %dms, off_dur = %dms\n", i, on_dur, off_dur);
+       }
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t rgb_onoff_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_rgb_data *rgb =
+               container_of(led_cdev, struct s2mu106_rgb_data, cdev);
+       int led = -1;
+       int on_dur, off_dur;
+
+       sscanf(buf, "%d %d %d", &led, &on_dur, &off_dur);
+
+       if ((led <= 0) || (led > S2MU106_RGB_LED_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       s2mu106_rgb_set_on_dur(rgb, led, on_dur);
+
+       s2mu106_rgb_set_off_dur(rgb, led, off_dur);
+
+       s2mu106_rgb_test_read(rgb);
+
+       return size;
+}
+
+static DEVICE_ATTR(rgb_mode, 0644, rgb_mode_show, rgb_mode_store);
+static DEVICE_ATTR(rgb_curr, 0644, rgb_curr_show, rgb_curr_store);
+static DEVICE_ATTR(rgb_ramp, 0644, rgb_ramp_show, rgb_ramp_store);
+static DEVICE_ATTR(rgb_onoff, 0644, rgb_onoff_show, rgb_onoff_store);
+
+static struct attribute *s2mu106_rgb_attrs[] = {
+       &dev_attr_rgb_mode.attr,
+       &dev_attr_rgb_curr.attr,
+       &dev_attr_rgb_ramp.attr,
+       &dev_attr_rgb_onoff.attr,
+    NULL
+};
+ATTRIBUTE_GROUPS(s2mu106_rgb);
+
+static void s2mu106_rgb_init(struct s2mu106_rgb_data *rgb)
+{
+       pr_info("%s: s2mu106_rgb init start\n", __func__);
+
+       rgb->off_dur_mode = rgb->pdata->def_off_dur_mode;
+       s2mu106_rgb_set_off_dur_mode(rgb, rgb->off_dur_mode);
+
+       /* Set LED1 for test */
+       s2mu106_rgb_set_on_dur(rgb, 1, 1000);
+       s2mu106_rgb_set_off_dur(rgb, 1, 500);
+       s2mu106_rgb_set_ramp_up(rgb, 1, 500);
+       s2mu106_rgb_set_ramp_down(rgb, 1, 1000);
+
+       s2mu106_rgb_test_read(rgb);
+}
+
+#if defined(CONFIG_OF)
+static int s2mu106_led_dt_parse_pdata(struct device *dev,
+                               struct s2mu106_rgb_platform_data *pdata)
+{
+       struct device_node *led_np, *np, *c_np;
+       int ret;
+       u32 temp;
+       u32 index;
+
+       led_np = dev->parent->of_node;
+
+       if (!led_np) {
+               pr_err("<%s> could not find led sub-node led_np\n", __func__);
+               return -ENODEV;
+       }
+
+       np = of_find_node_by_name(led_np, "rgb_led");
+       if (!np) {
+               pr_err("%s : could not find led sub-node np\n", __func__);
+               return -EINVAL;
+       }
+
+       ret = of_property_read_u32(np, "default_current",
+                       &pdata->default_current);
+       if (ret < 0)
+               pr_err("%s : could not find default_current\n", __func__);
+
+       ret = of_property_read_u32(np, "max_current",
+                       &pdata->max_current);
+       if (ret < 0)
+               pr_err("%s : could not find max_current\n", __func__);
+
+       ret = of_property_read_u32(np, "default_off_dur_mode",
+                       &pdata->def_off_dur_mode);
+       if (ret < 0) {
+               pr_err("%s : could not find def_onoff_mode\n", __func__);
+               pdata->def_off_dur_mode = 0;
+       }
+
+       pdata->led_num = of_get_child_count(np);
+
+       if (pdata->led_num > S2MU106_RGB_LED_MAX)
+               pdata->led_num = S2MU106_RGB_LED_MAX;
+
+       pdata->led = devm_kzalloc(dev,
+               sizeof(struct s2mu106_rgb_led) * pdata->led_num, GFP_KERNEL);
+
+       for_each_child_of_node(np, c_np) {
+               ret = of_property_read_u32(c_np, "id", &temp);
+               if (ret < 0)
+                       goto dt_err;
+               index = temp;
+
+               pr_info("%s: temp = %d, index = %d\n",
+                       __func__, temp, index);
+
+               if (index < S2MU106_RGB_LED_MAX) {
+                       pdata->led[index].id = index;
+
+                       ret = of_property_read_u32_index(np, "current", index,
+                                       &pdata->led[index].curr);
+                       if (ret < 0) {
+                               pr_err("%s : could not find current for led%d\n",
+                                       __func__, pdata->led[index].id);
+                               pdata->led[index].curr = pdata->default_current;
+                       }
+               }
+       }
+       return 0;
+dt_err:
+       pr_err("%s: DT parsing finish. ret = %d\n", __func__, ret);
+       return ret;
+}
+#endif /* CONFIG_OF */
+
+static int s2mu106_rgb_led_probe(struct platform_device *pdev)
+{
+       int ret = 0;
+       struct s2mu106_dev *s2mu106 = dev_get_drvdata(pdev->dev.parent);
+       struct s2mu106_rgb_data *rgb_data;
+       char name[20];
+
+       pr_info("%s: s2mu106_rgb start\n", __func__);
+
+       if (!s2mu106) {
+               dev_err(&pdev->dev, "drvdata->dev.parent not supplied\n");
+               return -ENODEV;
+       }
+
+       rgb_data = devm_kzalloc(&pdev->dev,
+               sizeof(struct s2mu106_rgb_data), GFP_KERNEL);
+       if (!rgb_data) {
+               pr_err("%s: failed to allocate driver data\n", __func__);
+               return -ENOMEM;
+       }
+
+       rgb_data->dev = &pdev->dev;
+       rgb_data->i2c = s2mu106->i2c;
+       rgb_data->pdata = devm_kzalloc(&pdev->dev,
+               sizeof(*(rgb_data->pdata)), GFP_KERNEL);
+       if (!rgb_data->pdata) {
+               pr_err("%s: failed to allocate platform data\n", __func__);
+               return -ENOMEM;
+       }
+
+       if (s2mu106->dev->of_node) {
+               ret = s2mu106_led_dt_parse_pdata(&pdev->dev, rgb_data->pdata);
+               if (ret < 0) {
+                       pr_err("%s: not found leds dt! ret=%d\n",
+                               __func__, ret);
+                       return -1;
+               }
+       }
+
+       platform_set_drvdata(pdev, rgb_data);
+
+       s2mu106_rgb_init(rgb_data);
+
+    snprintf(name, sizeof(name), "charging");
+    rgb_data->cdev.name = name;
+       rgb_data->cdev.groups = s2mu106_rgb_groups;
+       rgb_data->cdev.brightness_get = s2mu106_rgb_get_brightness;
+       rgb_data->cdev.brightness_set = s2mu106_rgb_set_brightness;
+
+       ret = devm_led_classdev_register(&pdev->dev, &rgb_data->cdev);
+       if (ret < 0) {
+               pr_err("%s: unable to register LED class dev\n", __func__);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int s2mu106_rgb_led_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static struct platform_driver s2mu106_rgb_led_driver = {
+       .driver = {
+               .name  = "leds-s2mu106-rgb",
+               .owner = THIS_MODULE,
+               },
+       .probe  = s2mu106_rgb_led_probe,
+       .remove = s2mu106_rgb_led_remove,
+};
+
+static int __init s2mu106_rgb_led_driver_init(void)
+{
+       return platform_driver_register(&s2mu106_rgb_led_driver);
+}
+module_init(s2mu106_rgb_led_driver_init);
+
+static void __exit s2mu106_rgb_led_driver_exit(void)
+{
+       platform_driver_unregister(&s2mu106_rgb_led_driver);
+}
+module_exit(s2mu106_rgb_led_driver_exit);
+
+MODULE_AUTHOR("Keunho Hwang <keunho.hwang@samsung.com>");
+MODULE_DESCRIPTION("SAMSUNG s2mu106 RGB LED Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-s2mu106.c b/drivers/leds/leds-s2mu106.c
new file mode 100644 (file)
index 0000000..4eb8747
--- /dev/null
@@ -0,0 +1,869 @@
+/*
+ * leds-s2mu106.c - LED class driver for S2MU106 FLASH LEDs.
+ *
+ * Copyright (C) 2018 Samsung Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/mfd/samsung/s2mu106.h>
+#include <linux/leds-s2mu106.h>
+#include <linux/platform_device.h>
+
+struct s2mu106_fled_data *g_fled_data;
+
+static char *s2mu106_fled_mode_string[] = {
+       "OFF",
+       "TORCH",
+       "FLASH",
+};
+
+/* IC current limit */
+static int s2mu106_fled_torch_curr_max[] = {
+       0, 320, 320, 320
+};
+
+/* IC current limit */
+static int s2mu106_fled_flash_curr_max[] = {
+       0, 1600, 1600, 500
+};
+
+#if DEBUG_TEST_READ
+static void s2mu106_fled_test_read(struct s2mu106_fled_data *fled)
+{
+    u8 data;
+    char str[1016] = {0,};
+    int i;
+       struct i2c_client *i2c = fled->i2c;
+
+    for (i = 0x0B; i <= 0x0C; i++) {
+        s2mu106_read_reg(i2c, i, &data);
+        sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+    }
+       for (i = 0x14; i <= 0x15; i++) {
+               s2mu106_read_reg(i2c, i, &data);
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+
+       for (i = 0x53; i <= 0x5A; i++) {
+               s2mu106_read_reg(i2c, i, &data);
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+
+    s2mu106_read_reg(i2c, 0x5B, &data);
+    pr_err("%s: %s0x5B:0x%02x\n", __func__, str, data);
+
+       memset(str,0,strlen(str));
+
+       for (i = 0x5C; i <= 0x62; i++) {
+               s2mu106_read_reg(i2c, i, &data);
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+
+    s2mu106_read_reg(i2c, 0x63, &data);
+    pr_err("%s: %s0x63:0x%02x\n", __func__, str, data);
+}
+#endif
+
+static int s2mu106_fled_get_flash_curr(struct s2mu106_fled_data *fled, int chan)
+{
+       int curr = -1;
+       u8 data;
+       u8 dest;
+
+       if ((chan < 1) || (chan > S2MU106_CH_MAX)) {
+               pr_info("%s: Wrong channel!!\n", __func__);
+               return -1;
+       }
+
+       switch(chan) {
+               case 1:
+                       dest = S2MU106_FLED_CH1_CTRL0;
+                       break;
+               case 2:
+                       dest = S2MU106_FLED_CH2_CTRL0;
+                       break;
+               case 3:
+                       dest = S2MU106_FLED_CH3_CTRL0;
+                       break;
+               default:
+                       return curr;
+                       break;
+       }
+
+       s2mu106_read_reg(fled->i2c, dest, &data);
+
+       data = data & S2MU106_CHX_FLASH_IOUT;
+       curr = (data * 50) + 50;
+
+       pr_info("%s: CH%02d flash curr. = %dmA\n", __func__,
+               chan, curr);
+
+       return curr;
+}
+
+static int s2mu106_fled_set_flash_curr(struct s2mu106_fled_data *fled,
+       int chan, int curr)
+{
+       int ret = -1;
+       u8 data;
+       u8 dest;
+       int curr_set;
+
+       if ((chan < 1) || (chan > S2MU106_CH_MAX)) {
+               pr_info("%s: Wrong channel!!\n", __func__);
+               return -1;
+       }
+
+       switch(chan) {
+               case 1:
+                       dest = S2MU106_FLED_CH1_CTRL0;
+                       break;
+               case 2:
+                       dest = S2MU106_FLED_CH2_CTRL0;
+                       break;
+               case 3:
+                       dest = S2MU106_FLED_CH3_CTRL0;
+                       break;
+               default:
+                       return ret;
+                       break;
+       }
+
+       if (curr < 50)
+               curr = 50;
+       else if (curr > s2mu106_fled_flash_curr_max[chan])
+               curr = s2mu106_fled_flash_curr_max[chan];
+
+       data = (curr - 50)/50;
+
+       s2mu106_update_reg(fled->i2c, dest, data, S2MU106_CHX_FLASH_IOUT);
+
+       curr_set = s2mu106_fled_get_flash_curr(fled, chan);
+
+       pr_info("%s: curr: %d, curr_set: %d\n", __func__,
+               curr, curr_set);
+
+       return ret;
+}
+
+static int s2mu106_fled_get_torch_curr(struct s2mu106_fled_data *fled,
+       int chan)
+{
+       int curr = -1;
+       u8 data;
+       u8 dest;
+
+       if ((chan < 1) || (chan > S2MU106_CH_MAX)) {
+               pr_info("%s: Wrong channel!!\n", __func__);
+               return -1;
+       }
+
+       switch(chan) {
+               case 1:
+                       dest = S2MU106_FLED_CH1_CTRL1;
+                       break;
+               case 2:
+                       dest = S2MU106_FLED_CH2_CTRL1;
+                       break;
+               case 3:
+                       dest = S2MU106_FLED_CH3_CTRL1;
+                       break;
+               default:
+                       return curr;
+                       break;
+       }
+
+       s2mu106_read_reg(fled->i2c, dest, &data);
+
+       data = data & S2MU106_CHX_TORCH_IOUT;
+       curr = data * 10 + 10;
+
+       pr_info("%s: CH%02d torch curr. = %dmA\n", __func__,
+               chan, curr);
+
+       return curr;
+}
+
+static int s2mu106_fled_set_torch_curr(struct s2mu106_fled_data *fled,
+       int chan, int curr)
+{
+       int ret = -1;
+       u8 data;
+       u8 dest;
+       int curr_set;
+
+       if ((chan < 1) || (chan > S2MU106_CH_MAX)) {
+               pr_info("%s: Wrong channel!!\n", __func__);
+               return -1;
+       }
+
+       switch(chan) {
+               case 1:
+                       dest = S2MU106_FLED_CH1_CTRL1;
+                       break;
+               case 2:
+                       dest = S2MU106_FLED_CH2_CTRL1;
+                       break;
+               case 3:
+                       dest = S2MU106_FLED_CH3_CTRL1;
+                       break;
+               default:
+                       return ret;
+                       break;
+       }
+
+       if (curr < 10)
+               curr = 10;
+       else if (curr > s2mu106_fled_torch_curr_max[chan])
+               curr = s2mu106_fled_torch_curr_max[chan];
+
+       data = (curr - 10)/10;
+
+       s2mu106_update_reg(fled->i2c, dest, data, S2MU106_CHX_TORCH_IOUT);
+
+       curr_set = s2mu106_fled_get_torch_curr(fled, chan);
+
+       pr_info("%s: curr: %d, curr_set: %d\n", __func__,
+               curr, curr_set);
+
+       ret = 0;
+
+       return ret;
+
+}
+
+static int s2mu106_fled_get_mode(struct s2mu106_fled_data *fled, int chan)
+{
+       u8 status;
+       int ret = -1;
+
+       s2mu106_read_reg(fled->i2c, S2MU106_FLED_STATUS1, &status);
+
+       pr_info("%s: S2MU106_FLED_STATUS1: 0x%02x\n", __func__, status);
+
+       if ((chan < 1) || (chan > S2MU106_CH_MAX)) {
+               pr_info("%s: Wrong channel!!\n", __func__);
+               return -1;
+       }
+
+       switch(chan) {
+               case 1:
+                       if (status & S2MU106_CH1_FLASH_ON)
+                               ret = S2MU106_FLED_MODE_FLASH;
+                       else if (status & S2MU106_CH1_TORCH_ON)
+                               ret = S2MU106_FLED_MODE_TORCH;
+                       else
+                               ret = S2MU106_FLED_MODE_OFF;
+                       break;
+               case 2:
+                       if (status & S2MU106_CH2_FLASH_ON)
+                               ret = S2MU106_FLED_MODE_FLASH;
+                       else if (status & S2MU106_CH2_TORCH_ON)
+                               ret = S2MU106_FLED_MODE_TORCH;
+                       else
+                               ret = S2MU106_FLED_MODE_OFF;
+                       break;
+               case 3:
+                       if (status & S2MU106_CH3_FLASH_ON)
+                               ret = S2MU106_FLED_MODE_FLASH;
+                       else if (status & S2MU106_CH3_TORCH_ON)
+                               ret = S2MU106_FLED_MODE_TORCH;
+                       else
+                               ret = S2MU106_FLED_MODE_OFF;
+                       break;
+               default:
+                       break;
+       }
+       return ret;
+}
+
+static int s2mu106_fled_set_mode(struct s2mu106_fled_data *fled,
+                                                               int chan, int mode)
+{
+       u8 dest = 0, bit = 0, mask = 0, status = 0;
+
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX) ||
+               (mode < 0) || (mode > S2MU106_FLED_MODE_MAX)) {
+                       pr_err("%s: Wrong channel or mode.\n", __func__);
+                       return -EFAULT;
+       }
+
+       pr_err("%s: channel: %d, mode: %d\n", __func__, chan, mode);
+
+       /* 0b000: OFF, 0b101: i2c bit control(on) */
+       switch(mode) {
+               case S2MU106_FLED_MODE_OFF:
+                       mask = S2MU106_CHX_FLASH_FLED_EN |
+                                       S2MU106_CHX_TORCH_FLED_EN;
+                       bit = 0;
+                       break;
+               case S2MU106_FLED_MODE_FLASH:
+                       mask = S2MU106_CHX_FLASH_FLED_EN;
+                       bit = S2MU106_FLED_EN << 3;
+                       break;
+               case S2MU106_FLED_MODE_TORCH:
+                       mask = S2MU106_CHX_TORCH_FLED_EN;
+                       bit = S2MU106_FLED_EN;
+                       break;
+               default:
+                       return -EFAULT;
+                       break;
+       }
+
+       switch(chan) {
+               case 1:
+                       dest = S2MU106_FLED_CTRL1;
+                       break;
+               case 2:
+                       dest = S2MU106_FLED_CTRL2;
+                       break;
+               case 3:
+                       dest = S2MU106_FLED_CTRL3;
+                       break;
+               default:
+                       return -EFAULT;
+                       break;
+       }
+
+       /* Need to set EN_FLED_PRE bit before mode change */
+       if (mode != S2MU106_FLED_MODE_OFF)
+               s2mu106_update_reg(fled->i2c, S2MU106_FLED_CTRL0,
+                       S2MU106_EN_FLED_PRE, S2MU106_EN_FLED_PRE);
+       else {
+               /* If no LED is on, clear EN_FLED_PRE */
+               s2mu106_read_reg(fled->i2c, S2MU106_FLED_STATUS1, &status);
+               if (!(status & S2MU106_FLED_ON_CHECK))
+                       s2mu106_update_reg(fled->i2c, S2MU106_FLED_CTRL0,
+                                       0, S2MU106_EN_FLED_PRE);
+       }
+       s2mu106_update_reg(fled->i2c, dest, bit, mask);
+
+       return 0;
+}
+
+int s2mu106_mode_change_cam_to_leds(enum cam_flash_mode cam_mode)
+{
+       int mode = -1;
+
+       switch(cam_mode) {
+               case CAM_FLASH_MODE_OFF:
+                       mode = S2MU106_FLED_MODE_OFF;
+                       break;
+               case CAM_FLASH_MODE_SINGLE:
+                       mode = S2MU106_FLED_MODE_FLASH;
+                       break;
+               case CAM_FLASH_MODE_TORCH:
+                       mode = S2MU106_FLED_MODE_TORCH;
+                       break;
+               default:
+                       mode = S2MU106_FLED_MODE_OFF;
+                       break;
+       }
+
+       return mode;
+}
+
+int s2mu106_fled_set_mode_ctrl(int chan, enum cam_flash_mode cam_mode)
+{
+       struct s2mu106_fled_data *fled = g_fled_data;
+       int mode = -1;
+
+       mode = s2mu106_mode_change_cam_to_leds(cam_mode);
+
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX) ||
+               (mode < 0) || (mode >= S2MU106_FLED_MODE_MAX)) {
+                       pr_err("%s: channel: %d, mode: %d\n", __func__, chan, mode);
+                       pr_err("%s: Wrong channel or mode.\n", __func__);
+                       return -1;
+       }
+
+       s2mu106_fled_set_mode(fled, chan, mode);
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       return 0;
+}
+
+int s2mu106_fled_set_curr(int chan, enum cam_flash_mode cam_mode, int curr)
+{
+       struct s2mu106_fled_data *fled = g_fled_data;
+       int mode = -1;
+
+       mode = s2mu106_mode_change_cam_to_leds(cam_mode);
+
+       /* Check channel */
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       switch (mode){
+               case S2MU106_FLED_MODE_TORCH:
+                       /* Set curr. */
+                       s2mu106_fled_set_torch_curr(fled, chan, curr);
+                       break;
+               case S2MU106_FLED_MODE_FLASH:
+                       /* Set curr. */
+                       s2mu106_fled_set_flash_curr(fled, chan, curr);
+                       break;
+               default:
+                       return -1;
+       }
+       /* Test read */
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       return 0;
+}
+
+int s2mu106_fled_get_curr(int chan, enum cam_flash_mode cam_mode)
+{
+       struct s2mu106_fled_data *fled = g_fled_data;
+       int mode = -1;
+       int curr = 0;
+
+       mode = s2mu106_mode_change_cam_to_leds(cam_mode);
+
+       /* Check channel */
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       switch (mode){
+               case S2MU106_FLED_MODE_TORCH:
+                       curr = s2mu106_fled_get_torch_curr(fled, chan);
+                       break;
+               case S2MU106_FLED_MODE_FLASH:
+                       curr = s2mu106_fled_get_flash_curr(fled, chan);
+                       break;
+               default:
+                       return -1;
+       }
+       /* Test read */
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       return curr;
+}
+
+static ssize_t fled_flash_curr_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_fled_data *fled =
+               container_of(led_cdev, struct s2mu106_fled_data, cdev);
+       int cnt = 0;
+       int curr = 0;
+       int i;
+    char str[1016] = {0,};
+
+       /* Read curr. */
+       for (i = 1; i <= S2MU106_CH_MAX; i++) {
+               curr = s2mu106_fled_get_flash_curr(fled, i);
+               pr_info("%s: channel: %d, curr: %dmA\n", __func__, i, curr);
+               if (curr >= 0)
+                       cnt += sprintf(str+strlen(str), "CH%02d: %dmA, ", i, curr);
+       }
+
+       cnt += sprintf(str+strlen(str), "\n");
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t fled_flash_curr_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_fled_data *fled =
+               container_of(led_cdev, struct s2mu106_fled_data, cdev);
+       int chan = -1;
+       int curr = -1;
+
+       sscanf(buf, "%d %d", &chan, &curr);
+
+       /* Check channel */
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       /* Set curr. */
+       s2mu106_fled_set_flash_curr(fled, chan, curr);
+
+       /* Test read */
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       return size;
+}
+
+
+static ssize_t fled_torch_curr_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_fled_data *fled =
+               container_of(led_cdev, struct s2mu106_fled_data, cdev);
+       int cnt = 0;
+       int curr = 0;
+       int i;
+    char str[1016] = {0,};
+
+       /* Read curr. */
+       for (i = 1; i <= S2MU106_CH_MAX; i++) {
+               curr = s2mu106_fled_get_torch_curr(fled, i);
+               pr_info("%s: channel: %d, curr: %dmA\n", __func__, i, curr);
+               if (curr >= 0)
+                       cnt += sprintf(str+strlen(str), "CH%02d: %dmA, ", i, curr);
+       }
+
+       cnt += sprintf(str+strlen(str), "\n");
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t fled_torch_curr_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_fled_data *fled =
+               container_of(led_cdev, struct s2mu106_fled_data, cdev);
+       int chan = -1;
+       int curr = -1;
+
+       sscanf(buf, "%d %d", &chan, &curr);
+
+       /* Check channel */
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX)) {
+                       pr_err("%s: Wrong channel.\n", __func__);
+                       return -EFAULT;
+       }
+
+       /* Set curr. */
+       s2mu106_fled_set_torch_curr(fled, chan, curr);
+
+       /* Test read */
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       return size;
+}
+
+static ssize_t fled_mode_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_fled_data *fled =
+               container_of(led_cdev, struct s2mu106_fled_data, cdev);
+       int cnt = 0;
+       int mode = 0;
+       int i;
+    char str[1016] = {0,};
+
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       for (i = 1; i <= S2MU106_CH_MAX; i++) {
+               mode = s2mu106_fled_get_mode(fled, i);
+               if (mode >= 0)
+                       cnt += sprintf(str+strlen(str), "CH%02d: %s, ", i,
+                               s2mu106_fled_mode_string[mode]);
+       }
+
+       cnt += sprintf(str+strlen(str), "\n");
+
+       strcpy(buf, str);
+
+    return cnt;
+}
+
+static ssize_t fled_mode_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+    struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct s2mu106_fled_data *fled =
+               container_of(led_cdev, struct s2mu106_fled_data, cdev);
+       int chan = -1;
+       int mode = -1;
+
+       sscanf(buf, "%d %d", &chan, &mode);
+
+       if ((chan <= 0) || (chan > S2MU106_CH_MAX) ||
+               (mode < 0) || (mode >= S2MU106_FLED_MODE_MAX)) {
+                       pr_err("%s: channel: %d, mode: %d\n", __func__, chan, mode);
+                       pr_err("%s: Wrong channel or mode.\n", __func__);
+                       return -EFAULT;
+       }
+
+       s2mu106_fled_set_mode(fled, chan, mode);
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+
+       return size;
+}
+
+static DEVICE_ATTR(fled_mode, 0644, fled_mode_show, fled_mode_store);
+static DEVICE_ATTR(fled_flash_curr, 0644, fled_flash_curr_show, fled_flash_curr_store);
+static DEVICE_ATTR(fled_torch_curr, 0644, fled_torch_curr_show, fled_torch_curr_store);
+
+static struct attribute *s2mu106_fled_attrs[] = {
+       &dev_attr_fled_mode.attr,
+       &dev_attr_fled_flash_curr.attr,
+       &dev_attr_fled_torch_curr.attr,
+    NULL
+};
+ATTRIBUTE_GROUPS(s2mu106_fled);
+
+static void s2mu106_fled_init(struct s2mu106_fled_data *fled)
+{
+       int i;
+       struct i2c_client *i2c = fled->i2c;
+       u8 data;
+
+       pr_info("%s: s2mu106_fled init start\n", __func__);
+
+       for (i = 1; i <= S2MU106_CH_MAX; i++) {
+               s2mu106_fled_set_flash_curr(fled, i, fled->pdata->default_current);
+               s2mu106_fled_set_torch_curr(fled, i, fled->pdata->default_current);
+       }
+       s2mu106_write_reg(i2c, 0xDF, 0x28);
+
+       s2mu106_read_reg(i2c, S2MU106_FLED_PMIC_ID, &data);
+       fled->rev_id = data & S2MU106_FLED_REV_NO;
+       pr_info("%s: PMIC_ID = 0x%02x, Rev. No. = %d\n", __func__,
+                       data, fled->rev_id);
+
+       if (fled->rev_id >= 1) {
+               /* Set Auto source change for flash mode boosting
+                * VBUS_DET(0x66[5] = 1), T_VBUS_DET_MASK_B(0x67[6] = 1)
+                */
+               s2mu106_update_reg(i2c, 0x66, 1 << 5, 1 << 5);
+               s2mu106_update_reg(i2c, 0x67, 1 << 6, 1 << 6);
+       }
+
+#if DEBUG_TEST_READ
+       s2mu106_fled_test_read(fled);
+#endif
+}
+
+#if defined(CONFIG_OF)
+static int s2mu106_led_dt_parse_pdata(struct device *dev,
+                               struct s2mu106_fled_platform_data *pdata)
+{
+       struct device_node *led_np, *np, *c_np;
+       int ret;
+       u32 temp;
+       u32 index;
+
+       led_np = dev->parent->of_node;
+
+       if (!led_np) {
+               pr_err("<%s> could not find led sub-node led_np\n", __func__);
+               return -ENODEV;
+       }
+
+       np = of_find_node_by_name(led_np, "flash_led");
+       if (!np) {
+               pr_err("%s : could not find led sub-node np\n", __func__);
+               return -EINVAL;
+       }
+
+       ret = of_property_read_u32(np, "default_current",
+                       &pdata->default_current);
+       if (ret < 0)
+               pr_err("%s : could not find default_current\n", __func__);
+
+       ret = of_property_read_u32(np, "max_current",
+                       &pdata->max_current);
+       if (ret < 0)
+               pr_err("%s : could not find max_current\n", __func__);
+
+       ret = of_property_read_u32(np, "default_timer",
+                       &pdata->default_timer);
+       if (ret < 0)
+               pr_err("%s : could not find default_timer\n", __func__);
+
+
+#if FLED_EN
+       ret = pdata->fled-en1-pin = of_get_named_gpio(np, "fled-en1-gpio", 0);
+       if (ret < 0) {
+               pr_err("%s : can't get fled-en1-pin\n", __func__);
+               return ret;
+       }
+
+       ret = pdata->fled-en2-pin = of_get_named_gpio(np, "fled-en2-gpio", 0);
+       if (ret < 0) {
+               pr_err("%s : can't get fled-en2-pin\n", __func__);
+               return ret;
+       }
+
+       ret = pdata->fled-en3-pin = of_get_named_gpio(np, "fled-en3-gpio", 0);
+       if (ret < 0) {
+               pr_err("%s : can't get fled-en3-pin\n", __func__);
+               return ret;
+       }
+
+       ret = pdata->fled-en4-pin = of_get_named_gpio(np, "fled-en4-gpio", 0);
+       if (ret < 0) {
+               pr_err("%s : can't get fled-en4-pin\n", __func__);
+               return ret;
+       }
+#endif
+
+       pdata->chan_num = of_get_child_count(np);
+
+       if (pdata->chan_num > S2MU106_CH_MAX)
+               pdata->chan_num = S2MU106_CH_MAX;
+
+       pdata->channel = devm_kzalloc(dev,
+               sizeof(struct s2mu106_fled_chan) * pdata->chan_num, GFP_KERNEL);
+
+       for_each_child_of_node(np, c_np) {
+               ret = of_property_read_u32(c_np, "id", &temp);
+               if (ret < 0)
+                       goto dt_err;
+               index = temp;
+
+               pr_info("%s: temp = %d, index = %d\n",
+                       __func__, temp, index);
+
+               if (index < S2MU106_CH_MAX) {
+                       pdata->channel[index].id = index;
+
+                       ret = of_property_read_u32_index(np, "current", index,
+                                       &pdata->channel[index].curr);
+                       if (ret < 0) {
+                               pr_err("%s : could not find current for channel%d\n",
+                                       __func__, pdata->channel[index].id);
+                               pdata->channel[index].curr = pdata->default_current;
+                       }
+
+                       ret = of_property_read_u32_index(np, "timer", index,
+                                       &pdata->channel[index].timer);
+                       if (ret < 0) {
+                               pr_err("%s : could not find timer for channel%d\n",
+                                       __func__, pdata->channel[index].id);
+                               pdata->channel[index].timer = pdata->default_timer;
+                       }
+               }
+       }
+       return 0;
+dt_err:
+       pr_err("%s: DT parsing finish. ret = %d\n", __func__, ret);
+       return ret;
+}
+#endif /* CONFIG_OF */
+
+static int s2mu106_led_probe(struct platform_device *pdev)
+{
+       int ret = 0;
+       struct s2mu106_dev *s2mu106 = dev_get_drvdata(pdev->dev.parent);
+       struct s2mu106_fled_data *fled_data;
+       char name[20];
+
+       pr_info("%s: s2mu106_fled start\n", __func__);
+
+       if (!s2mu106) {
+               dev_err(&pdev->dev, "drvdata->dev.parent not supplied\n");
+               return -ENODEV;
+       }
+
+       fled_data = devm_kzalloc(&pdev->dev,
+               sizeof(struct s2mu106_fled_data), GFP_KERNEL);
+       if (!fled_data) {
+               pr_err("%s: failed to allocate driver data\n", __func__);
+               return -ENOMEM;
+       }
+
+       fled_data->dev = &pdev->dev;
+       fled_data->i2c = s2mu106->i2c;
+       fled_data->pdata = devm_kzalloc(&pdev->dev,
+               sizeof(*(fled_data->pdata)), GFP_KERNEL);
+       if (!fled_data->pdata) {
+               pr_err("%s: failed to allocate platform data\n", __func__);
+               return -ENOMEM;
+       }
+
+       if (s2mu106->dev->of_node) {
+               ret = s2mu106_led_dt_parse_pdata(&pdev->dev, fled_data->pdata);
+               if (ret < 0) {
+                       pr_err("%s: not found leds dt! ret=%d\n",
+                               __func__, ret);
+                       return -1;
+               }
+       }
+
+       platform_set_drvdata(pdev, fled_data);
+
+       s2mu106_fled_init(fled_data);
+
+       /* Store fled_data for EXPORT_SYMBOL */
+       g_fled_data = fled_data;
+
+       snprintf(name, sizeof(name), "fled-s2mu106");
+       fled_data->cdev.name = name;
+       fled_data->cdev.groups = s2mu106_fled_groups;
+
+       ret = devm_led_classdev_register(&pdev->dev, &fled_data->cdev);
+       if (ret < 0) {
+               pr_err("%s: unable to register LED class dev\n", __func__);
+               return ret;
+       }
+
+       pr_info("%s: s2mu106_fled loaded\n", __func__);
+       return 0;
+}
+
+static int s2mu106_led_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static struct platform_driver s2mu106_led_driver = {
+       .driver = {
+               .name  = "leds-s2mu106",
+               .owner = THIS_MODULE,
+               },
+       .probe  = s2mu106_led_probe,
+       .remove = s2mu106_led_remove,
+};
+
+static int __init s2mu106_led_driver_init(void)
+{
+       return platform_driver_register(&s2mu106_led_driver);
+}
+module_init(s2mu106_led_driver_init);
+
+static void __exit s2mu106_led_driver_exit(void)
+{
+       platform_driver_unregister(&s2mu106_led_driver);
+}
+module_exit(s2mu106_led_driver_exit);
+
+MODULE_AUTHOR("Keunho Hwang <keunho.hwang@samsung.com>");
+MODULE_DESCRIPTION("SAMSUNG s2mu106 flash LED Driver");
+MODULE_LICENSE("GPL");
index 6e92101faac6906bc4d02e2c9da52552fad326c5..7a8fd96862c0de80d7075cdfb2aca00d429d0097 100644 (file)
@@ -633,4 +633,20 @@ config BATTERY_S2MU00X
        help
          Say Y here to enable support for S2MU00x battery driver.
 
+config FUELGAUGE_S2MU106
+    tristate "S2MU106 fuelgauge driver"
+    default n
+    depends on I2C
+    help
+      say y to include support
+      for s.lsi S2MU106 fuelgauge driver.
+
+config CHARGER_S2MU106
+    tristate "S2MU106 charger support"
+    depends on (MFD_S2MU106) && I2C
+    help
+      Say Y here to enable support for the S2MU106 charger
+      S2MU106 incluse pmic, led driver.
+      You have to define MFD_S2MU106
+
 endif # POWER_SUPPLY
index 5007316540f90af6c991ba27a4e04a2edb4704d1..04ea023b3a461b3c066a69f5e2e5d49ebdb54187 100644 (file)
@@ -84,4 +84,6 @@ 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
+obj-$(CONFIG_FUELGAUGE_S2MU106) += s2mu106_fuelgauge.o
+obj-$(CONFIG_CHARGER_S2MU106)   += s2mu106_charger.o s2mu106_pmeter.o
 obj-$(CONFIG_BATTERY_S2MU00X)   += s2mu00x_battery.o
diff --git a/drivers/power/supply/s2mu106_charger.c b/drivers/power/supply/s2mu106_charger.c
new file mode 100755 (executable)
index 0000000..2905739
--- /dev/null
@@ -0,0 +1,1123 @@
+/*
+ * s2mu106_charger.c - S2MU106 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/s2mu106.h>
+#include <linux/power/s2mu106_charger.h>
+#include <linux/version.h>
+
+static char *s2mu106_supplied_to[] = {
+       "battery",
+};
+
+static enum power_supply_property s2mu106_charger_props[] = {
+};
+
+static enum power_supply_property s2mu106_otg_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int s2mu106_get_charging_health(struct s2mu106_charger_data *charger);
+
+static void s2mu106_test_read(struct i2c_client *i2c)
+{
+       u8 data;
+       char str[1016] = {0,};
+       int i;
+
+       for (i = 0x0A; i <= 0x24; i++) {
+               s2mu106_read_reg(i2c, i, &data);
+
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
+       }
+
+       s2mu106_read_reg(i2c, 0x33, &data);
+       sprintf(str+strlen(str), "0x33:0x%02x, ", data);
+
+       s2mu106_read_reg(i2c, 0x7A, &data);
+       pr_err("%s: %s0x7A:0x%02x\n", __func__, str, data);
+}
+
+static int s2mu106_charger_otg_control(
+               struct s2mu106_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);
+               s2mu106_test_read(charger->i2c);
+               return 0;
+       }
+
+       if (charger->otg_on == enable)
+               return 0;
+
+       mutex_lock(&charger->charger_mutex);
+       if (!enable) {
+               s2mu106_update_reg(charger->i2c,
+                               S2MU106_CHG_CTRL0, CHG_MODE, REG_MODE_MASK);
+               s2mu106_update_reg(charger->i2c, 0xAE, 0x80, 0xF0);
+       } else {
+               s2mu106_update_reg(charger->i2c,
+                               S2MU106_CHG_CTRL3,
+                               S2MU106_SET_OTG_OCP_1500mA << SET_OTG_OCP_SHIFT,
+                               SET_OTG_OCP_MASK);
+               msleep(30);
+               s2mu106_update_reg(charger->i2c, 0xAE, 0x00, 0xF0);
+               s2mu106_update_reg(charger->i2c,
+                               S2MU106_CHG_CTRL0, OTG_BST_MODE, REG_MODE_MASK);
+               charger->cable_type = POWER_SUPPLY_TYPE_OTG;
+       }
+       charger->otg_on = enable;
+       mutex_unlock(&charger->charger_mutex);
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS2, &chg_sts2);
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_CTRL0, &chg_ctrl0);
+       s2mu106_read_reg(charger->i2c, 0xAE, &temp);
+       pr_info("%s S2MU106_CHG_STATUS2: 0x%x\n", __func__, chg_sts2);
+       pr_info("%s S2MU106_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 s2mu106_enable_charger_switch(
+       struct s2mu106_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__);
+               s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL0, CHG_MODE, REG_MODE_MASK);
+
+               /* timer fault set 16hr(max) */
+               s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL13,
+                               S2MU106_FC_CHG_TIMER_16hr << SET_TIME_FC_CHG_SHIFT,
+                               SET_TIME_FC_CHG_MASK);
+
+       } else {
+               pr_info("[DEBUG] %s: turn off charger\n", __func__);
+
+               s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL0, BUCK_MODE, REG_MODE_MASK);
+       }
+}
+
+static void s2mu106_set_buck(
+       struct s2mu106_charger_data *charger, int enable) {
+
+       if (enable) {
+               pr_info("[DEBUG]%s: set buck on\n", __func__);
+               s2mu106_enable_charger_switch(charger, charger->is_charging);
+       } else {
+               pr_info("[DEBUG]%s: set buck off (charger off mode)\n", __func__);
+
+               s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL0, CHARGER_OFF_MODE, REG_MODE_MASK);
+       }
+}
+
+static void s2mu106_set_regulation_voltage(
+               struct s2mu106_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) / 5;
+       else
+               data = 0x7f;
+
+       s2mu106_update_reg(charger->i2c,
+                       S2MU106_CHG_CTRL5, data << SET_VF_VBAT_SHIFT, SET_VF_VBAT_MASK);
+}
+
+static int s2mu106_get_regulation_voltage(struct s2mu106_charger_data *charger)
+{
+       u8 reg_data = 0;
+       int float_voltage;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_CTRL5, &reg_data);
+       reg_data &= 0x7F;
+       float_voltage = reg_data * 5 + 3900;
+       pr_debug("%s: battery cv reg : 0x%x, float voltage val : %d\n",
+                       __func__, reg_data, float_voltage);
+
+       return float_voltage;
+}
+
+static void s2mu106_set_input_current_limit(
+               struct s2mu106_charger_data *charger, int charging_current)
+{
+       u8 data;
+
+       if (charging_current <= 100)
+               data = 0x02;
+       else if (charging_current > 100 && charging_current <= 3000)
+               data = (charging_current - 50) / 25;
+       else
+               data = 0x62;
+
+       s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL1,
+                       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
+       s2mu106_test_read(charger->i2c);
+#endif
+}
+
+static int s2mu106_get_input_current_limit(struct s2mu106_charger_data *charger)
+{
+       u8 data;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_CTRL1, &data);
+       if (data < 0)
+               return data;
+
+       data = data & INPUT_CURRENT_LIMIT_MASK;
+       if (data > 0x76) {
+               pr_err("%s: Invalid current limit in register\n", __func__);
+               data = 0x76;
+       }
+       return  data * 25 + 50;
+}
+
+//TO DO need to set wcin current
+
+static void s2mu106_set_fast_charging_current(
+               struct s2mu106_charger_data *charger, int charging_current)
+{
+       u8 data;
+
+       if (charging_current <= 100)
+               data = 0x01;
+       else if (charging_current > 100 && charging_current <= 3200)
+               data = (charging_current / 50) - 1;
+       else
+               data = 0x3D;
+
+       s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL7,
+                       data << FAST_CHARGING_CURRENT_SHIFT, FAST_CHARGING_CURRENT_MASK);
+
+       pr_info("[DEBUG]%s: current  %d, 0x%02x\n", __func__, charging_current, data);
+
+#if EN_TEST_READ
+       s2mu106_test_read(charger->i2c);
+#endif
+}
+
+static int s2mu106_get_fast_charging_current(
+               struct s2mu106_charger_data *charger)
+{
+       u8 data;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_CTRL7, &data);
+       if (data < 0)
+               return data;
+
+       data = data & FAST_CHARGING_CURRENT_MASK;
+
+       if (data > 0x3F) {
+               pr_err("%s: Invalid fast charging current in register\n", __func__);
+               data = 0x3F;
+       }
+       return (data + 1) * 50;
+}
+
+static void s2mu106_set_topoff_current(
+               struct s2mu106_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:
+               s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL10,
+                               data << FIRST_TOPOFF_CURRENT_SHIFT, FIRST_TOPOFF_CURRENT_MASK);
+               break;
+       case 2:
+               s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL10,
+                               data << SECOND_TOPOFF_CURRENT_SHIFT, SECOND_TOPOFF_CURRENT_MASK);
+               break;
+       default:
+               break;
+       }
+}
+
+static int s2mu106_get_topoff_setting(
+               struct s2mu106_charger_data *charger)
+{
+       u8 data;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_CTRL10, &data);
+       if (data < 0)
+               return data;
+
+       data = data & FIRST_TOPOFF_CURRENT_MASK;
+
+       if (data > 0x0F)
+               data = 0x0F;
+       return data * 25 + 100;
+}
+
+static bool s2mu106_chg_init(struct s2mu106_charger_data *charger)
+{
+       /* Set default regulation voltage 4.35v
+       s2mu106_update_reg(charger->i2c,
+                       S2MU106_CHG_CTRL5, 0x5A << SET_VF_VBAT_SHIFT, SET_VF_VBAT_MASK);
+       */
+       s2mu106_update_reg(charger->i2c, 0x8b, 0x00, 0x01 << 4);
+
+       /* Set battery OCP 6A */
+       s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL9,
+                       S2MU106_SET_BAT_OCP_6000mA, BAT_OCP_MASK);
+
+       /* Type-C reset off */
+       s2mu106_update_reg(charger->i2c, 0xEC, 0x00, 0x80);
+
+       /* MRSTB 1s set */
+       s2mu106_write_reg(charger->i2c, 0xE5, 0x08);
+
+       return true;
+}
+
+static int s2mu106_get_charging_status(
+               struct s2mu106_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 = s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS0, &chg_sts0);
+       ret = s2mu106_read_reg(charger->i2c, S2MU106_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
+       s2mu106_test_read(charger->i2c);
+#endif
+       return status;
+}
+
+static int s2mu106_get_charge_type(struct s2mu106_charger_data *charger)
+{
+       int status = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+       u8 ret;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS3, &ret);
+       if (ret < 0)
+               pr_err("%s fail\n", __func__);
+
+       switch ((ret & BAT_STATUS_MASK) >> BAT_STATUS_SHIFT) {
+       case 0x6:
+               status = POWER_SUPPLY_CHARGE_TYPE_FAST;
+               break;
+       case 0x2:
+       case 0x3:
+               /* pre-charge mode */
+               status = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+               break;
+       }
+       return status;
+}
+
+static bool s2mu106_get_batt_present(struct s2mu106_charger_data *charger)
+{
+       u8 ret;
+
+       /* Enable charger CLK for BAT_ID check */
+       s2mu106_update_reg(charger->i2c, 0xF1, 0x01, 0x01);
+       msleep(30);
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS3, &ret);
+       if (ret < 0)
+               return false;
+
+       s2mu106_update_reg(charger->i2c, 0xF1, 0x00, 0x01);
+
+       return (ret & DET_BAT_STATUS_MASK) ? true : false;
+}
+
+static void s2mu106_wdt_clear(struct s2mu106_charger_data *charger)
+{
+       u8 reg_data, chg_fault_status;
+
+       /* watchdog kick */
+       s2mu106_update_reg(charger->i2c, S2MU106_CHG_CTRL12,
+                       0x1 << WDT_CLR_SHIFT, WDT_CLR_MASK);
+
+       s2mu106_read_reg(charger->i2c, S2MU106_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__);
+                       s2mu106_enable_charger_switch(charger, false);
+                       s2mu106_enable_charger_switch(charger, true);
+               }
+       }
+}
+
+static int s2mu106_get_charging_health(struct s2mu106_charger_data *charger)
+{
+
+       u8 ret;
+       union power_supply_propval value;
+       struct power_supply *psy;
+
+       if (charger->is_charging)
+               s2mu106_wdt_clear(charger);
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS0, &ret);
+       pr_info("[DEBUG] %s: S2MU106_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
+       s2mu106_test_read(charger->i2c);
+#endif
+}
+
+static int s2mu106_chg_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       int chg_curr, aicr;
+       struct s2mu106_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 = s2mu106_get_charging_status(charger);
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               val->intval = s2mu106_get_charging_health(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               val->intval = s2mu106_get_input_current_limit(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               if (charger->charging_current) {
+                       aicr = s2mu106_get_input_current_limit(charger);
+                       chg_curr = s2mu106_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 = s2mu106_get_fast_charging_current(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_FULL:
+               val->intval = s2mu106_get_topoff_setting(charger);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               val->intval = s2mu106_get_charge_type(charger);
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+               val->intval = s2mu106_get_regulation_voltage(charger);
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = s2mu106_get_batt_present(charger);
+               break;
+       case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+               val->intval = charger->is_charging;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mu106_chg_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct s2mu106_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 {
+                               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;
+
+                       s2mu106_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)
+                       s2mu106_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;
+               s2mu106_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;
+               s2mu106_set_regulation_voltage(charger,
+                               charger->pdata->chg_float_voltage);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL:
+               s2mu106_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)
+                               s2mu106_enable_charger_switch(charger, charger->is_charging);
+                       else
+                               s2mu106_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_CALIBRATE:
+               pr_info("%s: %s factory image support mode\n", __func__,
+                               (val->intval == S2MU00X_BAT_FAC_MODE_VBUS) ? "VBUS": "VBat");
+               if (val->intval == S2MU00X_BAT_FAC_MODE_VBUS) {
+                       s2mu106_enable_charger_switch(charger, false);
+                       s2mu106_set_input_current_limit(charger, 2000);
+                       s2mu106_set_fast_charging_current(charger, 2000);
+               } else {
+                       s2mu106_set_buck(charger, false);
+               }
+               break;
+       case POWER_SUPPLY_PROP_FUELGAUGE_RESET:
+               s2mu106_update_reg(charger->i2c, 0xE3, 0x03 << 6, 0x03 << 6);
+               msleep(1000);
+               s2mu106_update_reg(charger->i2c, 0xE3, 0x00 << 6, 0x03 << 6);
+               msleep(50);
+               pr_info("%s: reset fuelgauge when surge occur!\n", __func__);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mu106_otg_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct s2mu106_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:
+               s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS2, &reg);
+               pr_info("%s: S2MU106_CHG_STATUS2 : 0x%X\n", __func__, reg);
+               if ((reg & 0xC0) == 0x80)
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               s2mu106_read_reg(charger->i2c, S2MU106_CHG_CTRL0, &reg);
+               pr_info("%s: S2MU106_CHG_CTRL0 : 0x%X\n", __func__, reg);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int s2mu106_otg_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct s2mu106_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 s2mu106_charger_otg_vbus_work(struct work_struct *work)
+{
+       struct s2mu106_charger_data *charger = container_of(work,
+                       struct s2mu106_charger_data,
+                       otg_vbus_work.work);
+
+       s2mu106_write_reg(charger->i2c, S2MU106_CHG_CTRL11, 0x16);
+}
+
+#if EN_BAT_DET_IRQ
+/* s2mu106 interrupt service routine */
+static irqreturn_t s2mu106_det_bat_isr(int irq, void *data)
+{
+       struct s2mu106_charger_data *charger = data;
+       u8 val;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS3, &val);
+       if ((val & DET_BAT_STATUS_MASK) == 0) {
+               s2mu106_enable_charger_switch(charger, 0);
+               pr_err("charger-off if battery removed\n");
+       }
+       return IRQ_HANDLED;
+}
+#endif
+
+static irqreturn_t s2mu106_done_isr(int irq, void *data)
+{
+       struct s2mu106_charger_data *charger = data;
+       u8 val;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_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 s2mu106_chg_isr(int irq, void *data)
+{
+       struct s2mu106_charger_data *charger = data;
+       union power_supply_propval value;
+       u8 val;
+
+       value.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS0, &val);
+       pr_info("%s , %02x\n", __func__, val);
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t s2mu106_event_isr(int irq, void *data)
+{
+       struct s2mu106_charger_data *charger = data;
+       union power_supply_propval value;
+       struct power_supply *psy;
+       u8 val;
+       u8 fault;
+       int ret = 0;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS0, &val);
+       pr_info("%s , %02x\n", __func__, val);
+
+       fault = (val & CHG_FAULT_STATUS_MASK) >> CHG_FAULT_STATUS_SHIFT;
+
+       if (fault == CHG_STATUS_WD_SUSPEND || fault == CHG_STATUS_WD_RST) {
+               value.intval = 1;
+               pr_info("%s, reset USBPD\n", __func__);
+
+               psy = power_supply_get_by_name("s2mu106-usbpd");
+               if (!psy)
+                       return -EINVAL;
+
+               ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_USBPD_RESET, &value);
+               if (ret < 0)
+                       pr_err("%s: Fail to execute property\n", __func__);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t s2mu106_ovp_isr(int irq, void *data)
+{
+       struct s2mu106_charger_data *charger = data;
+       u8 val;
+
+       s2mu106_read_reg(charger->i2c, S2MU106_CHG_STATUS0, &val);
+       pr_info("%s ovp %02x\n", __func__, val);
+
+       return IRQ_HANDLED;
+}
+
+static int s2mu106_charger_parse_dt(struct device *dev,
+               struct s2mu106_charger_platform_data *pdata)
+{
+       struct device_node *np = of_find_node_by_name(NULL, "s2mu106-charger");
+       int ret = 0;
+
+       if (!np) {
+               pr_err("%s np NULL(s2mu106-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 s2mu106 pdata */
+static const struct of_device_id s2mu106_charger_match_table[] = {
+       { .compatible = "samsung,s2mu106-charger",},
+       {},
+};
+
+static int s2mu106_charger_probe(struct platform_device *pdev)
+{
+       struct s2mu106_dev *s2mu106 = dev_get_drvdata(pdev->dev.parent);
+       struct s2mu106_platform_data *pdata = dev_get_platdata(s2mu106->dev);
+       struct s2mu106_charger_data *charger;
+       struct power_supply_config psy_cfg = {};
+       int ret = 0;
+
+       pr_info("%s:[BATT] S2MU106 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 = s2mu106->i2c;
+
+       charger->pdata = devm_kzalloc(&pdev->dev, sizeof(*(charger->pdata)),
+                       GFP_KERNEL);
+       if (!charger->pdata) {
+               ret = -ENOMEM;
+               goto err_parse_dt_nomem;
+       }
+       ret = s2mu106_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 = "s2mu106-charger";
+       if (charger->pdata->fuelgauge_name == NULL)
+               charger->pdata->fuelgauge_name = "s2mu106-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   = s2mu106_chg_get_property;
+       charger->psy_chg_desc.set_property   = s2mu106_chg_set_property;
+       charger->psy_chg_desc.properties     = s2mu106_charger_props;
+       charger->psy_chg_desc.num_properties = ARRAY_SIZE(s2mu106_charger_props);
+
+       charger->psy_otg_desc.name           = "otg";
+       charger->psy_otg_desc.type           = POWER_SUPPLY_TYPE_UNKNOWN;
+       charger->psy_otg_desc.get_property   = s2mu106_otg_get_property;
+       charger->psy_otg_desc.set_property   = s2mu106_otg_set_property;
+       charger->psy_otg_desc.properties     = s2mu106_otg_props;
+       charger->psy_otg_desc.num_properties = ARRAY_SIZE(s2mu106_otg_props);
+
+       s2mu106_chg_init(charger);
+       charger->input_current = s2mu106_get_input_current_limit(charger);
+       charger->charging_current = s2mu106_get_fast_charging_current(charger);
+
+       psy_cfg.drv_data = charger;
+       psy_cfg.supplied_to = s2mu106_supplied_to;
+       psy_cfg.num_supplicants = ARRAY_SIZE(s2mu106_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 + S2MU106_CHG1_IRQ_SYS;
+       ret = request_threaded_irq(charger->irq_sys, NULL,
+                       s2mu106_ovp_isr, 0, "sys-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu106->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 + S2MU106_CHG2_IRQ_DET_BAT;
+       ret = request_threaded_irq(charger->irq_det_bat, NULL,
+                       s2mu106_det_bat_isr, 0, "det_bat-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu106->dev, "%s: Fail to request DET_BAT in IRQ: %d: %d\n",
+                               __func__, charger->irq_det_bat, ret);
+               goto err_reg_irq;
+       }
+#endif
+
+#if EN_CHG1_IRQ_CHGIN
+       charger->irq_chgin = pdata->irq_base + S2MU106_CHG1_IRQ_CHGIN;
+       ret = request_threaded_irq(charger->irq_chgin, NULL,
+                       s2mu106_chg_isr, 0, "chgin-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu106->dev, "%s: Fail to request CHGIN in IRQ: %d: %d\n",
+                               __func__, charger->irq_chgin, ret);
+               goto err_reg_irq;
+       }
+#endif
+
+       charger->irq_rst = pdata->irq_base + S2MU106_CHG1_IRQ_CHG_RSTART;
+       ret = request_threaded_irq(charger->irq_rst, NULL,
+                       s2mu106_chg_isr, 0, "restart-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu106->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 + S2MU106_CHG1_IRQ_DONE;
+       ret = request_threaded_irq(charger->irq_done, NULL,
+                       s2mu106_done_isr, 0, "done-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu106->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 + S2MU106_CHG1_IRQ_CHG_Fault;
+       ret = request_threaded_irq(charger->irq_chg_fault, NULL,
+                       s2mu106_event_isr, 0, "chg_fault-irq", charger);
+       if (ret < 0) {
+               dev_err(s2mu106->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, s2mu106_charger_otg_vbus_work);
+
+       /* Do max charging by freq. change, when duty is max */
+       s2mu106_update_reg(charger->i2c, 0x7A, 0x1 << 4, 0x1 << 4);
+#if EN_TEST_READ
+       s2mu106_test_read(charger->i2c);
+#endif
+       pr_info("%s:[BATT] S2MU106 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 s2mu106_charger_remove(struct platform_device *pdev)
+{
+       struct s2mu106_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 s2mu106_charger_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int s2mu106_charger_resume(struct device *dev)
+{
+       return 0;
+}
+#else
+#define s2mu106_charger_suspend NULL
+#define s2mu106_charger_resume NULL
+#endif
+
+static void s2mu106_charger_shutdown(struct device *dev)
+{
+       pr_info("%s: S2MU106 Charger driver shutdown\n", __func__);
+}
+
+static SIMPLE_DEV_PM_OPS(s2mu106_charger_pm_ops, s2mu106_charger_suspend,
+               s2mu106_charger_resume);
+
+static struct platform_driver s2mu106_charger_driver = {
+       .driver         = {
+               .name   = "s2mu106-charger",
+               .owner  = THIS_MODULE,
+               .of_match_table = s2mu106_charger_match_table,
+               .pm     = &s2mu106_charger_pm_ops,
+               .shutdown   =   s2mu106_charger_shutdown,
+       },
+       .probe          = s2mu106_charger_probe,
+       .remove     = s2mu106_charger_remove,
+};
+
+static int __init s2mu106_charger_init(void)
+{
+       int ret = 0;
+
+       ret = platform_driver_register(&s2mu106_charger_driver);
+
+       return ret;
+}
+module_init(s2mu106_charger_init);
+
+static void __exit s2mu106_charger_exit(void)
+{
+       platform_driver_unregister(&s2mu106_charger_driver);
+}
+module_exit(s2mu106_charger_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_DESCRIPTION("Charger driver for S2MU106");
diff --git a/drivers/power/supply/s2mu106_fuelgauge.c b/drivers/power/supply/s2mu106_fuelgauge.c
new file mode 100644 (file)
index 0000000..d9fc2e2
--- /dev/null
@@ -0,0 +1,1838 @@
+/*
+ * s2mu106_fuelgauge.c - S2MU106 Fuel Gauge Driver
+ *
+ * Copyright (C) 2018 Samsung Electronics, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#define SINGLE_BYTE    1
+#define TABLE_SIZE     22
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/kernel.h>
+
+#include <linux/power/s2mu106_fuelgauge.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_data/ntc_thermistor.h>
+
+static enum power_supply_property s2mu106_fuelgauge_props[] = {
+};
+
+static int s2mu106_get_vbat(struct s2mu106_fuelgauge_data *fuelgauge);
+static int s2mu106_get_ocv(struct s2mu106_fuelgauge_data *fuelgauge);
+static int s2mu106_get_current(struct s2mu106_fuelgauge_data *fuelgauge);
+static int s2mu106_get_avgcurrent(struct s2mu106_fuelgauge_data *fuelgauge);
+static int s2mu106_get_avgvbat(struct s2mu106_fuelgauge_data *fuelgauge);
+static bool batt_temp_flag = false;
+
+static int s2mu106_read_reg_byte(struct i2c_client *client, int reg, void *data)
+{
+       int ret = 0;
+       int cnt = 0;
+
+       ret = i2c_smbus_read_byte_data(client, reg);
+       if (ret < 0) {
+               while (ret < 0 && cnt < 5) {
+                       ret = i2c_smbus_read_byte_data(client, reg);
+                       cnt++;
+                       dev_err(&client->dev,
+                                       "%s: I2C read Incorrect! reg:0x%x, data:0x%x, cnt:%d\n",
+                                       __func__, reg, *(u8 *)data, cnt);
+               }
+               if (cnt == 5)
+                       dev_err(&client->dev,
+                               "%s: I2C read Failed reg:0x%x, data:0x%x\n",
+                               __func__, reg, *(u8 *)data);
+       }
+       *(u8 *)data = (u8)ret;
+
+       return ret;
+}
+
+static int s2mu106_write_and_verify_reg_byte(struct i2c_client *client, int reg, u8 data)
+{
+       int ret, i = 0;
+       int i2c_corrupted_cnt = 0;
+       u8 temp = 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);
+       }
+
+       /* Skip non-writable registers */
+       if ((reg == 0xee) || (reg == 0xef) || (reg == 0xf2) || (reg == 0xf3) ||
+               (reg == 0x0C) || (reg == 0x1e) || (reg == 0x1f) || (reg == 0x27)) {
+               return ret;
+       }
+
+       s2mu106_read_reg_byte(client, reg, &temp);
+       while ((temp != data) && (i2c_corrupted_cnt < 5)) {
+               dev_err(&client->dev,
+                       "%s: I2C write Incorrect! REG: 0x%x Expected: 0x%x Real-Value: 0x%x\n",
+                       __func__, reg, data, temp);
+               ret = i2c_smbus_write_byte_data(client, reg, data);
+               s2mu106_read_reg_byte(client, reg, &temp);
+               i2c_corrupted_cnt++;
+       }
+
+       if (i2c_corrupted_cnt == 5)
+               dev_err(&client->dev,
+                       "%s: I2C write failed REG: 0x%x Expected: 0x%x\n",
+                       __func__, reg, data);
+
+       return ret;
+}
+
+static int s2mu106_write_reg(struct i2c_client *client, int reg, u8 *buf)
+{
+#if SINGLE_BYTE
+       int ret = 0;
+       s2mu106_write_and_verify_reg_byte(client, reg, buf[0]);
+       s2mu106_write_and_verify_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 s2mu106_read_reg(struct i2c_client *client, int reg, u8 *buf)
+{
+
+#if SINGLE_BYTE
+       int ret = 0;
+       u8 data1 = 0, data2 = 0;
+       s2mu106_read_reg_byte(client, reg, &data1);
+       s2mu106_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 s2mu106_fg_test_read(struct i2c_client *client)
+{
+       static int reg_list[] = {
+               0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0E, 0x0F,
+               0x10, 0x11, 0x14, 0x1A, 0x1B, 0x1E, 0x1F, 0x24, 0x25, 0x26,
+               0x27, 0x28, 0x29, 0x40, 0x41, 0x43, 0x44, 0x45, 0x48, 0x4A,
+               0x4B, 0x50, 0x51, 0x52, 0x53, 0x58, 0x59, 0x5A, 0x5B, 0x5C,
+               0x67
+       };
+       u8 data = 0;
+       char str[1016] = {0,};
+       int i = 0, reg_list_size = 0;
+
+       reg_list_size = ARRAY_SIZE(reg_list);
+       for (i = 0; i < reg_list_size; i++) {
+               s2mu106_read_reg_byte(client, reg_list[i], &data);
+               sprintf(str+strlen(str), "0x%02x:0x%02x, ", reg_list[i], data);
+       }
+
+       /* print buffer */
+       pr_info("[FG]%s: %s\n", __func__, str);
+}
+
+static void s2mu106_reset_fg(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       int i;
+       u8 temp = 0;
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       /* step 0: [Surge test] initialize register of FG */
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x0E, fuelgauge->info.batcap[0]);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x0F, fuelgauge->info.batcap[1]);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x10, fuelgauge->info.batcap[2]);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x11, fuelgauge->info.batcap[3]);
+
+       /* After battery capacity update, set BATCAP_OCV_EN(0x0C[6]=1) */
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x0C, &temp);
+       temp |= 0x40;
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x0C, temp);
+
+       for (i = 0x92; i <= 0xe9; i++)
+               s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, i, fuelgauge->info.battery_table3[i - 0x92]);
+       for (i = 0xea; i <= 0xff; i++)
+               s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, i, fuelgauge->info.battery_table4[i - 0xea]);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x14, 0x67);
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x45, &temp);
+       temp &= 0xF0;
+       temp |= fuelgauge->info.accum[0];
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x45, temp);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x44, fuelgauge->info.accum[1]);
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x4B, &temp);
+       temp &= 0x8F;
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4B, temp);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4A, 0x10);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x40, 0x08);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x41, 0x04);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x5C, 0x1A);
+
+       /* Dumpdone. Re-calculate SOC */
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+       mdelay(300);
+
+       /* Update battery parameter version */
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_FG_ID, &temp);
+       temp &= 0xF0;
+       temp |= (fuelgauge->info.battery_param_ver & 0x0F);
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_FG_ID, temp);
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_FG_ID, &temp);
+
+       pr_info("%s: S2MU106_REG_FG_ID = 0x%02x, data ver. = 0x%x\n", __func__,
+                       temp, fuelgauge->info.battery_param_ver);
+
+       /* If it was voltage mode, recover it */
+       if (fuelgauge->mode == HIGH_SOC_VOLTAGE_MODE) {
+               s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4A, 0xFF);
+               s2mu106_read_reg_byte(fuelgauge->i2c, 0x4B, &temp);
+               temp |= 0x70;
+               s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4B, temp);
+       }
+
+       mutex_unlock(&fuelgauge->fg_lock);
+
+       pr_info("%s: Reset FG completed\n", __func__);
+}
+
+static int s2mu106_fix_rawsoc_reset_fg(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       int ret = 0, ui_soc = 0, f_soc = 0;
+       u8 data;
+       struct power_supply *psy;
+       union power_supply_propval value;
+
+       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__);
+       dev_info(&fuelgauge->i2c->dev, "%s: UI SOC = %d\n", __func__, value.intval);
+
+       ui_soc = value.intval;
+
+       f_soc = (ui_soc << 8) / 100;
+
+       if (f_soc > 0xFF)
+               f_soc = 0xFF;
+
+       f_soc |= 0x1;
+
+       data = (u8)f_soc;
+
+       /* Set rawsoc fix & enable */
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x29, data);
+
+       s2mu106_reset_fg(fuelgauge);
+
+       /* Disable rawsoc fix */
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x29, 0x00);
+
+       pr_info("%s: Finish\n", __func__);
+
+       return ret;
+}
+
+static void s2mu106_init_regs(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 temp = 0;
+       pr_info("%s: s2mu106 fuelgauge initialize\n", __func__);
+
+       /* Save register values for surge check */
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x53, &temp);
+       fuelgauge->reg_OTP_53 = temp;
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x52, &temp);
+       fuelgauge->reg_OTP_52 = temp;
+
+       /* Disable VM3_flag_EN */
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_VM, &temp);
+       temp = temp & 0xFB;
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_VM, temp);
+}
+
+static void s2mu106_alert_init(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+
+       /* VBAT Threshold setting: 3.55V */
+       data[0] = 0x00 & 0x0f;
+
+       /* SOC Threshold setting */
+       data[0] = data[0] | (fuelgauge->pdata->fuel_alert_soc << 4);
+
+       data[1] = 0x00;
+       s2mu106_write_reg(fuelgauge->i2c, S2MU106_REG_IRQ_LVL, data);
+}
+
+static int s2mu106_set_temperature(struct s2mu106_fuelgauge_data *fuelgauge,
+                       int temperature)
+{
+       /*
+        * s2mu106 include temperature sensor so,
+        * do not need to set temperature value.
+        */
+       return temperature;
+}
+
+static int s2mu106_get_temperature(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment;
+       int temperature = 0;
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_MONOUT_SEL, 0x18);
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_MONOUT, data) < 0)
+               goto err;
+/*     pr_info("%s temp data = 0x%x 0x%x\n", __func__, data[0], data[1]); */
+
+       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;
+
+       pr_info("%s: temperature (%d)\n", __func__, temperature);
+
+       return temperature*100;
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -ERANGE;
+}
+
+#if (TEMP_COMPEN)
+static bool s2mu106_get_vm_status(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data = 0;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_STATUS, &data);
+
+       return (data & (1 << 6)) ? true : false;
+}
+
+static int s2mu106_get_comp_socr(int temperature, int avg_curr)
+{
+       int comp_socr = 0;
+       int t_socr = 0;
+       int i_socr = (-222) * avg_curr;
+
+       if (temperature <= -10)
+               t_socr = ((-223) * temperature + 6500) / 1000;
+       else if (temperature <= 200)
+               t_socr = ((-30) * temperature + 6500) / 1000;
+
+       comp_socr = ((t_socr + 1) * i_socr) / 100000;
+
+       comp_socr = comp_socr - (comp_socr % 5);
+
+       if (comp_socr > 80)
+               comp_socr = 80;
+       else if (comp_socr < 0)
+               comp_socr = 0;
+
+       pr_info("%s: SOCr = %d, T_SOCr = %d, I_SOCr = %d\n", __func__,
+               comp_socr, t_socr, i_socr / 100000);
+
+       return comp_socr;
+}
+
+static int s2mu106_get_soc_map(struct s2mu106_fuelgauge_data *fuelgauge,
+               bool bat_charging, int comp_socr)
+{
+       int soc_map = 0;
+
+       if (bat_charging) {
+               if (fuelgauge->soc0i >= 9950)
+                       soc_map = 10000;
+               else
+                       soc_map =
+                               ((10040 - fuelgauge->socni) * (fuelgauge->rsoc - fuelgauge->soc0i)) /
+                               (10000 - fuelgauge->soc0i) + fuelgauge->socni;
+       } else {
+               if (fuelgauge->soc0i < ((100 * comp_socr) + 50))
+                       soc_map = 0;
+               else
+                       soc_map =
+                               (fuelgauge->socni * (fuelgauge->rsoc - fuelgauge->soc0i)) /
+                               (fuelgauge->soc0i - (100 * comp_socr)) + fuelgauge->socni;
+       }
+
+       if (soc_map > 10000)
+               soc_map = 10000;
+       else if (soc_map < 0)
+               soc_map = 0;
+
+       return soc_map;
+}
+
+static void s2mu106_temperature_compensation(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       bool flag_mapping = false;
+       int soc_map = 0;
+       int ui_soc = 0;
+       u8 data[2];
+
+       fuelgauge->comp_socr =
+               s2mu106_get_comp_socr(fuelgauge->temperature, fuelgauge->avg_curr);
+
+       if (fuelgauge->init_start) {
+               flag_mapping = true;
+               fuelgauge->pre_comp_socr = fuelgauge->comp_socr;
+               fuelgauge->pre_vm_status = fuelgauge->vm_status;
+       }
+
+       if ((fuelgauge->pre_comp_socr != fuelgauge->comp_socr) ||
+                       (fuelgauge->pre_bat_charging != fuelgauge->bat_charging) ||
+                       (fuelgauge->pre_vm_status != fuelgauge->vm_status) ||
+                       (fuelgauge->pre_is_charging != fuelgauge->is_charging))
+               flag_mapping = true;
+
+       if (flag_mapping == true) {
+               if (fuelgauge->init_start) {
+                       s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_RSOC_R, data);
+
+                       if (data[1] == 0) {
+                               ui_soc = (data[1] << 8) | (data[0]);
+
+                               pr_info("%s: init, use saved UI SOC(%d)"
+                                               " data[1] = 0x%02x, data[0] = 0x%02x\n",
+                                               __func__, ui_soc, data[1], data[0]);
+
+                               fuelgauge->ui_soc = ui_soc;
+
+                               /* UI SOC unit is 1% */
+                               ui_soc = ui_soc * 100;
+
+                               fuelgauge->socni = ui_soc;
+                               fuelgauge->soc0i = fuelgauge->rsoc;
+                       } else {
+                               pr_info("%s: init, UI SOC is not saved\n",
+                                               __func__);
+
+                               fuelgauge->socni = fuelgauge->rsoc;
+                               fuelgauge->soc0i = fuelgauge->rsoc;
+                       }
+               } else {
+                       /* After mapping, SOC_R is maintained.
+                          If mapping occurs continuously, SOC is not changed.
+                          So SOC_R need to be updated before mapping.
+                        */
+                       fuelgauge->soc_r = s2mu106_get_soc_map(fuelgauge,
+                                       fuelgauge->pre_bat_charging, fuelgauge->pre_comp_socr);
+
+                       fuelgauge->socni = fuelgauge->soc_r;
+                       fuelgauge->soc0i = fuelgauge->rsoc;
+               }
+       }
+
+       soc_map =  s2mu106_get_soc_map(fuelgauge,
+                       fuelgauge->bat_charging, fuelgauge->comp_socr);
+
+#if !defined(INC_OK_EN)
+       /* Use is_charging flag for prevent SOC increase when not charging */
+       if ((fuelgauge->is_charging == false) && (soc_map > fuelgauge->soc_r)) {
+               if(fuelgauge->init_start)
+                       fuelgauge->soc_r = soc_map;
+               else
+                       pr_info("%s: Not charging, do not reflect SOC increase. soc_map = %d, soc_r = %d\n",
+                                       __func__, soc_map, fuelgauge->soc_r);
+       }
+       else
+               fuelgauge->soc_r = soc_map;
+#else
+       fuelgauge->soc_r = soc_map;
+#endif
+
+       if (fuelgauge->vm_status && (fuelgauge->soc_r > fuelgauge->rsoc) &&
+                       (fuelgauge->temperature <= fuelgauge->low_temp_limit))
+               fuelgauge->soc_r = fuelgauge->rsoc;
+
+#if !(BATCAP_LEARN)
+       pr_info("%s: SOC_M = %d, Chg_stat = %d, VM = %d, flag_mapping = %d, avgCURR = %d, avgTEMP = %d, "
+                       "SOCni = %d, SOC0i = %d, SOCr = %d, SOC_R = %d\n",
+                       __func__, fuelgauge->rsoc, fuelgauge->bat_charging, fuelgauge->vm_status,
+                       flag_mapping, fuelgauge->avg_curr, fuelgauge->temperature,
+                       fuelgauge->socni, fuelgauge->soc0i, fuelgauge->comp_socr, fuelgauge->soc_r);
+#endif
+       fuelgauge->init_start = 0;
+       fuelgauge->pre_comp_socr = fuelgauge->comp_socr;
+       fuelgauge->pre_vm_status = fuelgauge->vm_status;
+       fuelgauge->pre_is_charging = fuelgauge->is_charging;
+       fuelgauge->pre_bat_charging = fuelgauge->bat_charging;
+
+       /* Save UI SOC for maintain SOC, after low temperature reset */
+       data[0] = fuelgauge->ui_soc;
+       data[1] = 0;
+       s2mu106_write_reg(fuelgauge->i2c, S2MU106_REG_RSOC_R, data);
+
+       /* TODO: Print UI SOC & saved value for debugging */
+       s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_RSOC_R, data);
+       ui_soc = (data[1] << 8) | (data[0]);
+       pr_info("%s: saved UI SOC = %d, data[1] = 0x%02x, data[0] = 0x%02x\n",
+                       __func__, ui_soc, data[1], data[0]);
+}
+#endif
+
+#if (BATCAP_LEARN)
+static int s2mu106_get_batcap_ocv(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u32 batcap_ocv = 0;
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_RBATCAP, data) < 0)
+               return -EINVAL;
+
+       dev_dbg(&fuelgauge->i2c->dev, "%s: data0 (%d) data1 (%d) \n", __func__, data[0], data[1]);
+       batcap_ocv = (data[0] + (data[1] << 8)) >> 2;
+
+       return batcap_ocv;
+}
+
+static int s2mu106_get_cycle(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment, cycle;
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_MONOUT_SEL, 0x27);
+
+       mdelay(50);
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_MONOUT, data) < 0)
+               goto err;
+       compliment = (data[1] << 8) | (data[0]);
+
+       cycle = compliment;
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_MONOUT_SEL, 0x10);
+
+       mutex_unlock(&fuelgauge->fg_lock);
+
+       return cycle;
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -EINVAL;
+}
+
+void s2mu106_batcap_learning(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       int bat_w = 0;
+       u8 data[2], temp;
+       int range = (BAT_L_CON[5] == 0) ? 900:800;
+       int gap_cap = 0;
+
+       gap_cap = (fuelgauge->capcc * 1000) / fuelgauge->batcap_ocv;
+
+       if ((gap_cap > range) && (gap_cap < 1100)) {
+               if (BAT_L_CON[6])
+                       bat_w = ((fuelgauge->batcap_ocv * 75) + (fuelgauge->capcc * 25)) / 100;
+               else
+                       bat_w = ((fuelgauge->batcap_ocv * 90) + (fuelgauge->capcc * 10)) / 100;
+
+               if (BAT_L_CON[7]) {
+                       fuelgauge->batcap_ocv_fin = bat_w;
+                       bat_w = bat_w << 2;
+                       data[1] = (u8)((bat_w >> 8) & 0x00ff);
+                       data[0] = (u8)(bat_w & 0x00ff);
+
+                       mutex_lock(&fuelgauge->fg_lock);
+
+                       s2mu106_write_reg(fuelgauge->i2c, S2MU106_REG_RBATCAP, data);
+                       /* After battery capacity update, set BATCAP_OCV_EN(0x0C[6]=1) */
+                       s2mu106_read_reg_byte(fuelgauge->i2c, 0x0C, &temp);
+                       temp |= 0x40;
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x0C, temp);
+
+                       mutex_unlock(&fuelgauge->fg_lock);
+               }
+       }
+
+       pr_info("%s: gap_cap = %d, capcc = %d, batcap_ocv = %d, bat_w = %d\n",
+                       __func__, gap_cap, fuelgauge->capcc, fuelgauge->batcap_ocv, bat_w);
+}
+
+static int s2mu106_get_cap_cc(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data1 = 0, data0 = 0;
+       int cap_cc = 0;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_CAPCC + 1, &data1);
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_CAPCC, &data0);
+       cap_cc = (data1 << 8) | data0;
+       if (cap_cc & (1 << 15)) {
+               cap_cc = (~cap_cc) + 1;
+               cap_cc = cap_cc / 2;
+               cap_cc = cap_cc * (-1);
+       }
+       else
+               cap_cc /= 2;
+
+       return cap_cc;
+}
+
+static int s2mu106_get_soh(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data1 = 0, data0 = 0;
+       int original = 0, ret = -1;
+       int batcap_ocv = s2mu106_get_batcap_ocv(fuelgauge);
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_BATCAP + 1, &data1);
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_BATCAP, &data0);
+       original = (data1 << 8) | data0;
+
+       if (original != 0) {
+               ret = (batcap_ocv * 100) / original;
+
+               if (ret > 100)
+                       ret = 100;
+       } else
+               ret = 100;
+
+       pr_info("%s: original batcap = %d, new_batcap = %d, soh = %d\n",
+                       __func__, original, batcap_ocv, ret);
+
+       return ret;
+}
+#endif
+
+#if (BATCAP_LEARN) || (TEMP_COMPEN)
+static bool s2mu106_get_bat_charging(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data = 0;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_STATUS, &data);
+
+       return (data & (1 << 5)) ? true : false;
+}
+#endif
+
+#if (BATCAP_LEARN) && (TEMP_COMPEN)
+static int s2mu106_get_fullcharge_cap(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       int ret = -1;
+       int batcap_ocv = s2mu106_get_batcap_ocv(fuelgauge);
+
+       ret = ((100 - fuelgauge->comp_socr) * batcap_ocv) / 100;
+
+       return ret;
+}
+
+static int s2mu106_get_remaining_cap(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       int ret = -1;
+       int fcc = s2mu106_get_fullcharge_cap(fuelgauge);
+
+       ret = (fuelgauge->soc_r) * fcc / 10000;
+
+       pr_info("%s: fcc = %d, remaining_cap = %d\n", __func__, fcc, ret);
+
+       return ret;
+}
+#endif
+
+static int s2mu106_get_rawsoc(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2], temp;
+       u16 compliment;
+       u8 por_state = 0;
+       u8 reg_1E = 0;
+       u8 reg_OTP_52 = 0, reg_OTP_53 = 0;
+#if defined(CONFIG_CHARGER_S2MU106)
+       bool charging_enabled = false;
+#endif
+       int ret = 0;
+       struct power_supply *psy;
+       union power_supply_propval value;
+       int float_voltage = 0;
+       int avg_current = 0, avg_vbat = 0, vbat = 0, curr = 0;
+       u8 fg_mode_reg = 0;
+#if (BATCAP_LEARN)
+       int BATCAP_L_VBAT;
+#endif
+
+       psy = power_supply_get_by_name("battery");
+       if (!psy)
+               return -EINVAL;
+       /* Get UI SOC from battery driver */
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value);
+       if (ret < 0) {
+               pr_err("%s: Fail to execute property.\n", __func__);
+               value.intval = 0;
+       }
+       fuelgauge->ui_soc = value.intval;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x1F, &por_state);
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x53, &reg_OTP_53);
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x52, &reg_OTP_52);
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x1E, &reg_1E);
+       dev_err(&fuelgauge->i2c->dev, "%s: OTP 52(%02x) 53(%02x), current 52(%02x) 53(%02x), "
+                       "0x1F(%02x), 0x1E(%02x)\n", __func__, fuelgauge->reg_OTP_52, fuelgauge->reg_OTP_53,
+                       reg_OTP_52, reg_OTP_53, por_state, reg_1E);
+
+       if (((por_state != 0x00) || (reg_1E != 0x03)) || (fuelgauge->probe_done == true &&
+               (fuelgauge->reg_OTP_52 != reg_OTP_52 || fuelgauge->reg_OTP_53 != reg_OTP_53))) {
+               /* check charging enable */
+#if defined(CONFIG_CHARGER_S2MU106)
+               psy = power_supply_get_by_name("s2mu106-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("s2mu106-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__);
+#endif
+
+               if (fuelgauge->reg_OTP_52 != reg_OTP_52 || fuelgauge->reg_OTP_53 != reg_OTP_53) {
+#if defined(CONFIG_CHARGER_S2MU106)
+                       psy = power_supply_get_by_name("s2mu106-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__);
+#endif
+
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x1F, 0x40);
+                       msleep(50);
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x1F, 0x01);
+
+                       s2mu106_read_reg_byte(fuelgauge->i2c, 0x53, &reg_OTP_53);
+                       s2mu106_read_reg_byte(fuelgauge->i2c, 0x52, &reg_OTP_52);
+
+                       dev_err(&fuelgauge->i2c->dev, "1st reset after %s: OTP 52(%02x) 53(%02x) "
+                                       "current 52(%02x) 53(%02x)\n", __func__,
+                                       fuelgauge->reg_OTP_52, fuelgauge->reg_OTP_53, reg_OTP_52, reg_OTP_53);
+
+                       if (fuelgauge->reg_OTP_52 != reg_OTP_52 || fuelgauge->reg_OTP_53 != reg_OTP_53) {
+#if defined(CONFIG_CHARGER_S2MU106)
+                               psy = power_supply_get_by_name("s2mu106-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__);
+#endif
+                               s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x1F, 0x40);
+                               msleep(50);
+                               s2mu106_write_and_verify_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__);
+               if(fuelgauge->ui_soc == 0)
+                       s2mu106_reset_fg(fuelgauge);
+               else
+                       s2mu106_fix_rawsoc_reset_fg(fuelgauge);
+
+               por_state = 0x00;
+               s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x1F, por_state);
+
+#if defined(CONFIG_CHARGER_S2MU106)
+               /* Recover charger status after f.g reset */
+               if (charging_enabled) {
+                       value.intval = S2MU00X_BAT_CHG_MODE_CHARGING;
+
+                       psy = power_supply_get_by_name("s2mu106-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__);
+               }
+#endif
+       }
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_RSOC, 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 */
+               fuelgauge->rsoc = ((~compliment) & 0xFFFF) + 1;
+               fuelgauge->rsoc = (fuelgauge->rsoc * (-10000)) / (0x1 << 14);
+       } else {
+               fuelgauge->rsoc = compliment & 0x7FFF;
+               fuelgauge->rsoc = ((fuelgauge->rsoc * 10000) / (0x1 << 14));
+       }
+
+       avg_current = s2mu106_get_avgcurrent(fuelgauge);
+       avg_vbat = s2mu106_get_avgvbat(fuelgauge);
+       vbat = s2mu106_get_vbat(fuelgauge);
+       curr = s2mu106_get_current(fuelgauge);
+
+#if defined(USE_EXTERNAL_TEMP)
+       /* If you want to use temperature sensed by other IC,
+        * change the battery driver so that F.G driver can
+        * get the value.
+        */
+       psy = power_supply_get_by_name("battery");
+       if (!psy)
+               return -EINVAL;
+       /* Get temperature from battery driver */
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+       fuelgauge->temperature = value.intval;
+#else
+       fuelgauge->temperature = s2mu106_get_temperature(fuelgauge);
+#endif
+
+
+#if (BATCAP_LEARN) || (TEMP_COMPEN)
+       fuelgauge->bat_charging = s2mu106_get_bat_charging(fuelgauge);
+#endif
+
+#if (TEMP_COMPEN)
+       fuelgauge->vm_status = s2mu106_get_vm_status(fuelgauge);
+       fuelgauge->avg_curr = avg_current;
+       s2mu106_temperature_compensation(fuelgauge);
+
+       dev_info(&fuelgauge->i2c->dev, "%s: current_soc (%d), compen_soc (%d), "
+                       "previous_soc (%d), FG_mode(%s)\n",
+                       __func__, fuelgauge->rsoc, fuelgauge->soc_r,
+                       fuelgauge->info.soc, mode_to_str[fuelgauge->mode]);
+
+       fuelgauge->info.soc = fuelgauge->soc_r;
+#else
+       dev_info(&fuelgauge->i2c->dev, "%s: current_soc (%d), previous_soc (%d), FG_mode(%s)\n",
+                __func__, fuelgauge->rsoc, fuelgauge->info.soc, mode_to_str[fuelgauge->mode]);
+
+       fuelgauge->info.soc = fuelgauge->rsoc;
+#endif
+
+#if defined(CONFIG_CHARGER_S2MU106)
+       psy = power_supply_get_by_name("s2mu106-charger");
+       if (!psy)
+               return -EINVAL;
+       ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &value);
+       if (ret < 0)
+               pr_err("%s: Fail to execute property\n", __func__);
+       float_voltage = value.intval;
+#else
+       float_voltage = 4350;
+#endif
+
+       float_voltage = (float_voltage * 996) / 1000;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x4A, &fg_mode_reg);
+
+       dev_info(&fuelgauge->i2c->dev, "%s: UI SOC = %d, is_charging = %d, avg_vbat = %d, "
+               "float_voltage = %d, avg_current = %d, 0x4A = 0x%02x\n", __func__,
+               fuelgauge->ui_soc, fuelgauge->is_charging, avg_vbat,
+               float_voltage, avg_current, fg_mode_reg);
+
+       if ((fuelgauge->is_charging == true) &&
+               ((value.intval >= 98) || ((avg_vbat > float_voltage) && (avg_current < 500)))) {
+               if (fuelgauge->mode == CURRENT_MODE) { /* switch to VOLTAGE_MODE */
+                       fuelgauge->mode = HIGH_SOC_VOLTAGE_MODE;
+
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4A, 0xFF);
+                       s2mu106_read_reg_byte(fuelgauge->i2c, 0x4B, &temp);
+                       temp |= 0x70;
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4B, temp);
+
+                       dev_info(&fuelgauge->i2c->dev, "%s: FG is in high soc voltage mode\n", __func__);
+               }
+       } else if (avg_current < -50) {
+               if (fuelgauge->mode == HIGH_SOC_VOLTAGE_MODE) {
+                       fuelgauge->mode = CURRENT_MODE;
+
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4A, 0x10);
+                       s2mu106_read_reg_byte(fuelgauge->i2c, 0x4B, &temp);
+                       temp &= 0x8F;
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x4B, temp);
+
+                       dev_info(&fuelgauge->i2c->dev, "%s: FG is in current mode\n", __func__);
+               }
+       }
+
+#if (BATCAP_LEARN)
+       fuelgauge->capcc = s2mu106_get_cap_cc(fuelgauge);
+       fuelgauge->batcap_ocv = s2mu106_get_batcap_ocv(fuelgauge); // CC mode capacity
+       fuelgauge->cycle = s2mu106_get_cycle(fuelgauge);
+       BATCAP_L_VBAT = (BAT_L_CON[1] == 0) ? 4200:4100;
+
+       if (fuelgauge->temperature >= 200) {
+               if (fuelgauge->learn_start == false) {
+                       if ((fuelgauge->rsoc < 1000) && (fuelgauge->cycle >= BAT_L_CON[0]))
+                               fuelgauge->learn_start = true;
+               } else {
+                       if ((fuelgauge->cond1_ok == false) && (fuelgauge->bat_charging == false))
+                               goto batcap_learn_init;
+
+                       if (fuelgauge->cond1_ok == false) {
+                               if (fuelgauge->c1_count >= BAT_L_CON[2]) {
+                                       fuelgauge->cond1_ok = true;
+                                       fuelgauge->c1_count = 0;
+                               } else {
+                                       if ((vbat >= BATCAP_L_VBAT) && (avg_current < BAT_L_CON[4]) &&
+                                                       (fuelgauge->rsoc >= 9700)) {
+                                               fuelgauge->c1_count++;
+                                       } else {
+                                               fuelgauge->c1_count = 0;
+                                       }
+                               }
+                       } else {
+                               if (fuelgauge->c2_count >= BAT_L_CON[3]) {
+                                       s2mu106_batcap_learning(fuelgauge);
+                                       goto batcap_learn_init;
+                               } else {
+                                       if ((vbat >= (BATCAP_L_VBAT - 100)) && (avg_current > -30) &&
+                                                       (avg_current < 30) && (fuelgauge->rsoc >= 9800)) {
+                                               fuelgauge->c2_count++;
+                                       } else {
+                                               fuelgauge->c2_count = 0;
+
+                                               if (avg_current <= -30)
+                                                       goto batcap_learn_init;
+                                       }
+                               }
+                       }
+               }
+       } else {
+batcap_learn_init:
+               fuelgauge->learn_start = false;
+               fuelgauge->cond1_ok  = false;
+               fuelgauge->c1_count = 0;
+               fuelgauge->c2_count = 0;
+       }
+#endif
+
+#if (TEMP_COMPEN) && (BATCAP_LEARN)
+       fuelgauge->soh = s2mu106_get_soh(fuelgauge);
+       fuelgauge->capcc = s2mu106_get_cap_cc(fuelgauge);
+       fuelgauge->fcc = s2mu106_get_fullcharge_cap(fuelgauge);
+       fuelgauge->rmc = s2mu106_get_remaining_cap(fuelgauge);
+
+       pr_info("%s: SOC_M = %d, Chg_stat = %d, VM = %d, avbVBAT = %d, avgCURR = %d, avgTEMP = %d, "
+                       "SOCni = %d, SOC0i = %d, SOCr = %d, SOC_R = %d, "
+                       "Learning_start = %d, C1_count = %d/%d, C2_count = %d/%d, "
+                       "BATCAP_OCV_new = %d, SOH = %d, CAP_CC = %d, FCC = %d, RM = %d\n",
+                       __func__,
+                       fuelgauge->rsoc, fuelgauge->bat_charging, fuelgauge->vm_status, avg_vbat, avg_current, fuelgauge->temperature,
+                       fuelgauge->socni, fuelgauge->soc0i, fuelgauge->comp_socr, fuelgauge->soc_r,
+                       fuelgauge->learn_start, fuelgauge->c1_count, BAT_L_CON[2], fuelgauge->c2_count, BAT_L_CON[3],
+                       fuelgauge->batcap_ocv_fin, fuelgauge->soh, fuelgauge->capcc, fuelgauge->fcc, fuelgauge->rmc);
+#endif
+
+       /* Low voltage W/A, make 0% */
+       if ((avg_vbat < 3450) && (avg_current < -50) && (fuelgauge->rsoc > 100)) {
+               if (fuelgauge->temperature > fuelgauge->low_temp_limit) {
+                       dev_info(&fuelgauge->i2c->dev, "%s: Low voltage WA. Make rawsoc 0\n", __func__);
+
+                       s2mu106_read_reg_byte(fuelgauge->i2c, 0x25, &temp);
+                       temp &= 0xF0;
+                       temp |= 0x04;
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x25, temp);
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x24, 0x01);
+
+                       /* Dumpdone. Re-calculate SOC */
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x1E, 0x0F);
+                       mdelay(300);
+
+                       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x24, 0x00);
+
+                       /* Make report SOC 0% */
+                       fuelgauge->info.soc = 0;
+#if (TEMP_COMPEN)
+                       fuelgauge->soc_r = 0;
+#endif
+               } else {
+                       dev_info(&fuelgauge->i2c->dev, "%s: Low voltage WA. Make UI SOC 0\n", __func__);
+
+                       /* Make report SOC 0% */
+                       fuelgauge->info.soc = 0;
+#if (TEMP_COMPEN)
+                       fuelgauge->soc_r = 0;
+#endif
+               }
+       }
+
+#if (TEMP_COMPEN)
+       /* Maintain UI SOC if battery is relaxing */
+       if (((fuelgauge->temperature < fuelgauge->low_temp_limit) &&
+                               (fuelgauge->soc_r == 0) && (fuelgauge->rsoc > 500)) &&
+                       (((avg_current > -60) && (avg_current < 50)) || ((curr > -100) && (curr < 50)))) {
+               fuelgauge->soc_r = fuelgauge->ui_soc * 100;
+               fuelgauge->info.soc = fuelgauge->soc_r;
+               fuelgauge->init_start = 1;
+
+               dev_info(&fuelgauge->i2c->dev,
+                               "%s:  Maintain UI SOC if battery is relaxing SOC_R = %d, info.soc = %d\n",
+                               __func__, fuelgauge->soc_r, fuelgauge->info.soc);
+       }
+#endif
+
+       /* S2MU106 FG debug */
+       s2mu106_fg_test_read(fuelgauge->i2c);
+
+       return min(fuelgauge->info.soc, 10000);
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -EINVAL;
+}
+
+static int s2mu106_get_current(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment;
+       int curr = 0;
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_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;
+}
+
+static int s2mu106_get_ocv(struct s2mu106_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 s2mu106_get_avgcurrent(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment;
+       int curr = 0;
+
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_MONOUT_SEL, 0x17);
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_MONOUT, data) < 0)
+               goto err;
+       compliment = (data[1] << 8) | (data[0]);
+       dev_dbg(&fuelgauge->i2c->dev, "%s: MONOUT(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;
+       }
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_MONOUT_SEL, 0x10);
+
+       mutex_unlock(&fuelgauge->fg_lock);
+
+       dev_info(&fuelgauge->i2c->dev, "%s: avg current (%d)mA\n", __func__, curr);
+
+       return curr;
+
+err:
+       mutex_unlock(&fuelgauge->fg_lock);
+       return -EINVAL;
+}
+
+static int s2mu106_get_vbat(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u32 vbat = 0;
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_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;
+
+       dev_info(&fuelgauge->i2c->dev, "%s: vbat (%d)\n", __func__, vbat);
+
+       return vbat;
+}
+
+static int s2mu106_get_avgvbat(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       u8 data[2];
+       u16 compliment, avg_vbat;
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, 0x40, 0x08);
+       mutex_lock(&fuelgauge->fg_lock);
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_REG_MONOUT_SEL, 0x16);
+
+       mdelay(50);
+
+       if (s2mu106_read_reg(fuelgauge->i2c, S2MU106_REG_MONOUT, data) < 0)
+               goto err;
+       compliment = (data[1] << 8) | (data[0]);
+
+       avg_vbat = (compliment * 1000) >> 12;
+
+       s2mu106_write_and_verify_reg_byte(fuelgauge->i2c, S2MU106_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;
+}
+
+bool s2mu106_fuelgauge_fuelalert_init(struct i2c_client *client, int soc)
+{
+       struct s2mu106_fuelgauge_data *fuelgauge = i2c_get_clientdata(client);
+       u8 data[2];
+
+       fuelgauge->is_fuel_alerted = false;
+
+       /* 1. Set s2mu106 alert configuration. */
+       s2mu106_alert_init(fuelgauge);
+
+       if (s2mu106_read_reg(client, S2MU106_REG_IRQ, data) < 0)
+               return -1;
+
+       /*Enable VBAT, SOC */
+       data[1] &= 0xfc;
+
+       /*Disable IDLE_ST, INIT)ST */
+       data[1] |= 0x0c;
+
+       s2mu106_write_reg(client, S2MU106_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 s2mu106_fg_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct s2mu106_fuelgauge_data *fuelgauge =
+                                       power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               return -ENODATA;
+       case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+               /* Remaining capacity unit is uAh */
+               val->intval = fuelgauge->rmc * 1000;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               val->intval = fuelgauge->fcc;
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_NOW:
+               break;
+               /* Cell voltage (VCELL, mV) */
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = s2mu106_get_vbat(fuelgauge);
+               break;
+               /* Additional Voltage Information (mV) */
+       case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+               switch (val->intval) {
+               case S2MU00X_BATTERY_VOLTAGE_AVERAGE:
+                       val->intval = s2mu106_get_avgvbat(fuelgauge);
+                       break;
+               case S2MU00X_BATTERY_VOLTAGE_OCV:
+                       val->intval = s2mu106_get_ocv(fuelgauge);
+                       break;
+               }
+               break;
+               /* Current (mA) */
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               if (val->intval == S2MU00X_BATTERY_CURRENT_UA)
+                       val->intval = s2mu106_get_current(fuelgauge) * 1000;
+               else
+                       val->intval = s2mu106_get_current(fuelgauge);
+               break;
+               /* Average Current (mA) */
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               if (val->intval == S2MU00X_BATTERY_CURRENT_UA)
+                       val->intval = s2mu106_get_avgcurrent(fuelgauge) * 1000;
+               else
+                       val->intval = s2mu106_get_avgcurrent(fuelgauge);
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               val->intval = s2mu106_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);
+                       s2mu106_fuelgauge_fuelalert_init(fuelgauge->i2c,
+                                       fuelgauge->pdata->fuel_alert_soc);
+               }
+               break;
+       /* Battery Temperature */
+       case POWER_SUPPLY_PROP_TEMP:
+       /* Target Temperature */
+       case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+               if (batt_temp_flag) {
+                       val->intval = BATT_TEMP_CONSTANT;
+                       pr_err("set battery temp is unchanged, batt_temp_flag = %d\n", batt_temp_flag);
+                       break;
+               }
+               val->intval = ntc_show_batt_temp();
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_TEMP:
+               val->intval = s2mu106_get_temperature(fuelgauge);
+               break;
+       case POWER_SUPPLY_PROP_SCOPE:
+               val->intval = fuelgauge->mode;
+               break;
+       case POWER_SUPPLY_PROP_SOH:
+#if (BATCAP_LEARN)
+               fuelgauge->soh = s2mu106_get_soh(fuelgauge);
+               val->intval = fuelgauge->soh;
+#else
+               /* If battery capacity learning is not enabled,
+                * return SOH is 100%
+                */
+               val->intval = 100;
+#endif
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mu106_fg_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct s2mu106_fuelgauge_data *fuelgauge =
+                               power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               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:
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+       case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+               s2mu106_set_temperature(fuelgauge, val->intval);
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_EMPTY:
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_AVG:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void s2mu106_fg_isr_work(struct work_struct *work)
+{
+       struct s2mu106_fuelgauge_data *fuelgauge =
+               container_of(work, struct s2mu106_fuelgauge_data, isr_work.work);
+       u8 fg_alert_status = 0;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_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(SOC) is very Low!\n", __func__);
+
+       if (fg_alert_status & 0x02) {
+               int voltage = s2mu106_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 : SOC or Voltage is Good!\n", __func__);
+               wake_unlock(&fuelgauge->fuel_alert_wake_lock);
+       }
+}
+
+static irqreturn_t s2mu106_fg_irq_thread(int irq, void *irq_data)
+{
+       struct s2mu106_fuelgauge_data *fuelgauge = irq_data;
+       u8 fg_irq = 0;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_IRQ, &fg_irq);
+       dev_info(&fuelgauge->i2c->dev, "%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;
+}
+
+static int s2mu106_fuelgauge_get_cell_id(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       /* TODO: Get battery profile index */
+
+       return 2;
+}
+
+static void s2mu106_fuelgauge_select_table(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       fuelgauge->info.battery_profile_index = s2mu106_fuelgauge_get_cell_id(fuelgauge);
+
+       if (fuelgauge->info.battery_profile_index == 1) {
+               fuelgauge->info.battery_table3 = fuelgauge->info.battery_table3_cell1;
+               fuelgauge->info.battery_table4 = fuelgauge->info.battery_table4_cell1;
+               fuelgauge->info.soc_arr_val = fuelgauge->info.soc_arr_val_cell1;
+               fuelgauge->info.ocv_arr_val = fuelgauge->info.ocv_arr_val_cell1;
+               fuelgauge->info.batcap = fuelgauge->info.batcap_cell1;
+               fuelgauge->info.accum = fuelgauge->info.accum_cell1;
+               fuelgauge->info.battery_param_ver =
+                       fuelgauge->info.battery_param_ver_cell1;
+       } else {
+               fuelgauge->info.battery_table3 = fuelgauge->info.battery_table3_cell2;
+               fuelgauge->info.battery_table4 = fuelgauge->info.battery_table4_cell2;
+               fuelgauge->info.soc_arr_val = fuelgauge->info.soc_arr_val_cell2;
+               fuelgauge->info.ocv_arr_val = fuelgauge->info.ocv_arr_val_cell2;
+               fuelgauge->info.batcap = fuelgauge->info.batcap_cell2;
+               fuelgauge->info.accum = fuelgauge->info.accum_cell2;
+               fuelgauge->info.battery_param_ver =
+                       fuelgauge->info.battery_param_ver_cell2;
+       }
+
+       pr_info("%s: Use cell %d, Ver.: %d, table3: 0x%02x, table4: 0x%02x, soc_arr_val: 0d%d, "
+                       "ocv_arr_val: 0d%d, batcap: 0x%02x, accum: 0x%02x\n",
+                       __func__, fuelgauge->info.battery_profile_index, fuelgauge->info.battery_param_ver,
+                       fuelgauge->info.battery_table3[0], fuelgauge->info.battery_table4[0],
+                       fuelgauge->info.soc_arr_val[0], fuelgauge->info.ocv_arr_val[0],
+                       fuelgauge->info.batcap[0], fuelgauge->info.accum[0]);
+}
+
+#ifdef CONFIG_OF
+static int s2mu106_fuelgauge_parse_dt(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       struct device_node *np = of_find_node_by_name(NULL, "s2mu106-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,fuel_alert_vol",
+                               &fuelgauge->pdata->fuel_alert_vol);
+               if (ret < 0) {
+                       fuelgauge->pdata->fuel_alert_vol = 3300;
+                       pr_err("%s Default value of fuel_alert_vol : %d\n",
+                                       __func__, fuelgauge->pdata->fuel_alert_vol);
+               }
+
+               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);
+
+               np = of_find_node_by_name(NULL, "battery");
+               if (!np) {
+                       pr_err("%s np NULL\n", __func__);
+               } else {
+                       ret = of_property_read_string(np,
+                                       "battery,fuelgauge_name",
+                                       (char const **)&fuelgauge->pdata->fuelgauge_name);
+               }
+
+               /* get battery node */
+               np = of_find_node_by_name(NULL, "battery");
+               if (!np) {
+                       pr_err("%s battery node NULL\n", __func__);
+               } else {
+                       /* get cell1 battery data */
+                       ret = of_property_read_u32_array(np, "battery,battery_table3_cell1", fuelgauge->info.battery_table3_cell1, 88);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,battery_table3_cell1\n", __func__);
+                       ret = of_property_read_u32_array(np, "battery,battery_table4_cell1", fuelgauge->info.battery_table4_cell1, 22);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,battery_table4_cell1\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,batcap_cell1", fuelgauge->info.batcap_cell1, 4);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,batcap_cell1\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,soc_arr_val_cell1", fuelgauge->info.soc_arr_val_cell1, 22);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,soc_arr_val_cell1\n", __func__);
+                       ret = of_property_read_u32_array(np, "battery,ocv_arr_val_cell1", fuelgauge->info.ocv_arr_val_cell1, 22);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,ocv_arr_val_cell1\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,accum_cell1", fuelgauge->info.accum_cell1, 2);
+                       if (ret < 0) {
+                               fuelgauge->info.accum_cell1[1]=0x00; // REG 0x44
+                               fuelgauge->info.accum_cell1[0]=0x08; // REG 0x45
+                               pr_err("%s There is no cell1 accumulative rate in DT. Use default value(0x800)\n", __func__);
+                       }
+
+                       ret = of_property_read_u32(np, "battery,battery_param_ver_cell1",
+                                       &fuelgauge->info.battery_param_ver_cell1);
+                       if (ret < 0)
+                               pr_err("%s There is no cell1 battery parameter version\n", __func__);
+
+                       /* get cell2 battery data */
+                       ret = of_property_read_u32_array(np, "battery,battery_table3_cell2", fuelgauge->info.battery_table3_cell2, 88);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,battery_table3_cell2\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,battery_table4_cell2", fuelgauge->info.battery_table4_cell2, 22);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,battery_table4_cell2\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,batcap_cell2", fuelgauge->info.batcap_cell2, 4);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,batcap_cell2\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,soc_arr_val_cell2", fuelgauge->info.soc_arr_val_cell2, 22);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,soc_arr_val_cell2\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,ocv_arr_val_cell2", fuelgauge->info.ocv_arr_val_cell2, 22);
+                       if (ret < 0)
+                               pr_err("%s error reading battery,ocv_arr_val_cell2\n", __func__);
+
+                       ret = of_property_read_u32_array(np, "battery,accum_cell2", fuelgauge->info.accum_cell2, 2);
+                       if (ret < 0) {
+                               fuelgauge->info.accum_cell2[1]=0x00; // REG 0x44
+                               fuelgauge->info.accum_cell2[0]=0x08; // REG 0x45
+                               pr_err("%s There is no cell2 accumulative rate in DT. Use default value(0x800)\n", __func__);
+                       }
+
+                       ret = of_property_read_u32(np, "battery,battery_param_ver_cell2",
+                                       &fuelgauge->info.battery_param_ver_cell2);
+                       if (ret < 0)
+                               pr_err("%s There is no cell2 battery parameter version\n", __func__);
+
+                       ret = of_property_read_u32(np, "battery,low_temp_limit",
+                                       &fuelgauge->low_temp_limit);
+                       if (ret < 0) {
+                               pr_err("%s There is no low temperature limit. Use default(100)\n",
+                                               __func__);
+                               fuelgauge->low_temp_limit = 100;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static struct of_device_id s2mu106_fuelgauge_match_table[] = {
+       { .compatible = "samsung,s2mu106-fuelgauge",},
+       {},
+};
+#else
+static int s2mu106_fuelgauge_parse_dt(struct s2mu106_fuelgauge_data *fuelgauge)
+{
+       return -ENOSYS;
+}
+
+#define s2mu106_fuelgauge_match_table NULL
+#endif /* CONFIG_OF */
+
+static const struct power_supply_desc s2mu106_fuelgauge_power_supply_desc = {
+       .name = "s2mu106-fuelgauge",
+       .type = POWER_SUPPLY_TYPE_UNKNOWN,
+       .properties = s2mu106_fuelgauge_props,
+       .num_properties = ARRAY_SIZE(s2mu106_fuelgauge_props),
+       .get_property = s2mu106_fg_get_property,
+       .set_property = s2mu106_fg_set_property,
+};
+
+static int s2mu106_fuelgauge_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       struct s2mu106_fuelgauge_data *fuelgauge;
+       int raw_soc_val;
+       struct power_supply_config fuelgauge_cfg = {};
+       int ret = 0;
+       u8 temp = 0;
+
+       pr_info("%s: S2MU106 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) {
+                       dev_err(&client->dev, "Failed to allocate memory\n");
+                       ret = -ENOMEM;
+                       goto err_parse_dt_nomem;
+               }
+               ret = s2mu106_fuelgauge_parse_dt(fuelgauge);
+               if (ret < 0)
+                       goto err_parse_dt;
+       } else {
+               fuelgauge->pdata = client->dev.platform_data;
+       }
+
+       s2mu106_fuelgauge_select_table(fuelgauge);
+
+       i2c_set_clientdata(client, fuelgauge);
+
+       if (fuelgauge->pdata->fuelgauge_name == NULL)
+               fuelgauge->pdata->fuelgauge_name = "s2mu106-fuelgauge";
+
+       fuelgauge_cfg.drv_data = fuelgauge;
+
+       fuelgauge->revision = 0;
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x48, &temp);
+       fuelgauge->revision = (temp & 0xF0) >> 4;
+
+       pr_info("%s: S2MU106 Fuelgauge revision: 0x%x, reg 0x48 = 0x%x\n",
+                       __func__, fuelgauge->revision, temp);
+
+       fuelgauge->info.soc = 0;
+
+       raw_soc_val = s2mu106_get_rawsoc(fuelgauge);
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, 0x4A, &temp);
+       pr_info("%s: 0x4A = 0x%02x, rawsoc = %d\n", __func__, temp, raw_soc_val);
+       if (temp == 0x10)
+               fuelgauge->mode = CURRENT_MODE;
+       else if (temp == 0xFF) {
+               fuelgauge->mode = HIGH_SOC_VOLTAGE_MODE;
+       }
+
+#if (TEMP_COMPEN)
+       fuelgauge->init_start = 1;
+#endif
+#if (BATCAP_LEARN)
+       fuelgauge->learn_start = false;
+       fuelgauge->cond1_ok = false;
+       fuelgauge->c1_count = 0;
+       fuelgauge->c2_count = 0;
+#endif
+
+       s2mu106_init_regs(fuelgauge);
+
+       fuelgauge->psy_fg = power_supply_register(
+               &client->dev, &s2mu106_fuelgauge_power_supply_desc, &fuelgauge_cfg);
+       if (!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) {
+               s2mu106_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, s2mu106_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, s2mu106_fg_irq_thread,
+                                               IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING
+                                               | 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;
+                       }
+               }
+       }
+
+#if (TEMP_COMPEN) || (BATCAP_LEARN)
+       fuelgauge->bat_charging = false;
+#endif
+       fuelgauge->probe_done = true;
+
+       s2mu106_read_reg_byte(fuelgauge->i2c, S2MU106_REG_FG_ID, &temp);
+       pr_info("%s: parameter ver. in IC: 0x%02x, in kernel: 0x%02x\n", __func__,
+                       temp & 0x0F, fuelgauge->info.battery_param_ver);
+
+       pr_info("%s: S2MU106 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 s2mu106_fuelgauge_id[] = {
+       {"s2mu106-fuelgauge", 0},
+       {}
+};
+
+static void s2mu106_fuelgauge_shutdown(struct i2c_client *client)
+{
+
+}
+
+static int s2mu106_fuelgauge_remove(struct i2c_client *client)
+{
+       struct s2mu106_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 s2mu106_fuelgauge_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int s2mu106_fuelgauge_resume(struct device *dev)
+{
+       return 0;
+}
+#else
+#define s2mu106_fuelgauge_suspend NULL
+#define s2mu106_fuelgauge_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(s2mu106_fuelgauge_pm_ops, s2mu106_fuelgauge_suspend,
+               s2mu106_fuelgauge_resume);
+
+static struct i2c_driver s2mu106_fuelgauge_driver = {
+       .driver = {
+               .name = "s2mu106-fuelgauge",
+               .owner = THIS_MODULE,
+               .pm = &s2mu106_fuelgauge_pm_ops,
+               .of_match_table = s2mu106_fuelgauge_match_table,
+       },
+       .probe = s2mu106_fuelgauge_probe,
+       .remove = s2mu106_fuelgauge_remove,
+       .shutdown = s2mu106_fuelgauge_shutdown,
+       .id_table = s2mu106_fuelgauge_id,
+};
+
+static int __init s2mu106_fuelgauge_init(void)
+{
+       pr_info("%s: S2MU106 Fuelgauge Init\n", __func__);
+       return i2c_add_driver(&s2mu106_fuelgauge_driver);
+}
+
+static void __exit s2mu106_fuelgauge_exit(void)
+{
+       i2c_del_driver(&s2mu106_fuelgauge_driver);
+}
+module_init(s2mu106_fuelgauge_init);
+module_exit(s2mu106_fuelgauge_exit);
+static int __init batt_temp_set(char *str)
+{
+       if (strncmp("true", str, 4) == 0)
+               batt_temp_flag = true;
+       else {
+               batt_temp_flag = false;
+       }
+       return 0;
+} early_param("androidboot.batt_temp", batt_temp_set);
+
+MODULE_DESCRIPTION("Samsung S2MU106 Fuel Gauge Driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/s2mu106_pmeter.c b/drivers/power/supply/s2mu106_pmeter.c
new file mode 100755 (executable)
index 0000000..8fd6ccb
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * s2mu106_pmeter.c - S2MU106 Power Meter 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/s2mu106.h>
+#include <linux/power/s2mu106_pmeter.h>
+#include <linux/version.h>
+
+
+struct s2mu106_pmeter_data *g_pmeter;
+int s2mu106_powermeter_get_vchg_voltage(void)
+{
+       struct s2mu106_pmeter_data *pmeter;
+       u8 ret;
+       u8 v_12 = 0, v_13 = 0;
+       int chg_voltage = 0;
+
+       pmeter = g_pmeter;
+
+       s2mu106_read_reg(pmeter->i2c, 0x12, &v_12);
+       s2mu106_read_reg(pmeter->i2c, 0x13, &v_13);
+
+       pr_info("%s: S2MU106_POWERMETER 0x57: 0x%x 0x12:0x%x 0x13:0x%x\n", __func__, ret, v_12, v_13);
+       chg_voltage = ((v_12 << 4) + (v_13 >> 4)) * 5;  //mV
+
+       pr_info("%s: S2MU106_POWERMETER  chg_voltage: %d mV\n", __func__, chg_voltage);
+       return chg_voltage;
+
+}
+
+int s2mu106_powermeter_get_vchg_current(void)
+{
+       struct s2mu106_pmeter_data *pmeter;
+       u8 v_20 = 0, v_21 = 0;
+       int chg_current = 0;
+
+       pmeter = g_pmeter;
+
+       s2mu106_read_reg(pmeter->i2c, 0x20, &v_20);
+       s2mu106_read_reg(pmeter->i2c, 0x21, &v_21);
+
+       chg_current = ((v_20 << 4) + (v_21 >> 4));      //mA
+
+       pr_info("%s: S2MU106_POWERMETER  chg_current: %d mA\n", __func__, chg_current);
+       return chg_current;
+
+}
+
+static const struct of_device_id s2mu106_pmeter_match_table[] = {
+       { .compatible = "samsung,s2mu106-pmeter",},
+       {},
+};
+
+static int s2mu106_pm_enable(struct s2mu106_pmeter_data *pmeter)
+{
+       u8 data1;
+
+       s2mu106_read_reg(pmeter->i2c, 0x5F, &data1);
+       data1 |= 0x80;
+       s2mu106_write_reg(pmeter->i2c, 0x5F, data1);
+       return 0;
+}
+
+static int s2mu106_pmeter_probe(struct platform_device *pdev)
+{
+       struct s2mu106_dev *s2mu106 = dev_get_drvdata(pdev->dev.parent);
+       struct s2mu106_pmeter_data *pmeter;
+       int ret = 0;
+
+       pr_info("%s:[BATT] S2MU106 Power meter driver probe\n", __func__);
+       pmeter = kzalloc(sizeof(*pmeter), GFP_KERNEL);
+       if (!pmeter)
+               return -ENOMEM;
+
+       pmeter->dev = &pdev->dev;
+       pmeter->i2c = s2mu106->muic; // share the i2c slave address with MUIC
+
+       platform_set_drvdata(pdev, pmeter);
+
+       g_pmeter = pmeter;
+       pr_info("%s:[BATT] S2MU106 pmeter driver loaded OK\n", __func__);
+
+       s2mu106_pm_enable(pmeter);
+
+       return ret;
+}
+
+static int s2mu106_pmeter_remove(struct platform_device *pdev)
+{
+       struct s2mu106_pmeter_data *pmeter =
+               platform_get_drvdata(pdev);
+
+       kfree(pmeter);
+       return 0;
+}
+
+#if defined CONFIG_PM
+static int s2mu106_pmeter_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int s2mu106_pmeter_resume(struct device *dev)
+{
+       return 0;
+}
+#else
+#define s2mu106_pmeter_suspend NULL
+#define s2mu106_pmeter_resume NULL
+#endif
+
+static void s2mu106_pmeter_shutdown(struct device *dev)
+{
+       pr_info("%s: S2MU106 PowerMeter driver shutdown\n", __func__);
+}
+
+static SIMPLE_DEV_PM_OPS(s2mu106_pmeter_pm_ops, s2mu106_pmeter_suspend,
+               s2mu106_pmeter_resume);
+
+static struct platform_driver s2mu106_pmeter_driver = {
+       .driver         = {
+               .name   = "s2mu106-powermeter",
+               .owner  = THIS_MODULE,
+               .of_match_table = s2mu106_pmeter_match_table,
+               .pm     = &s2mu106_pmeter_pm_ops,
+               .shutdown   =   s2mu106_pmeter_shutdown,
+       },
+       .probe          = s2mu106_pmeter_probe,
+       .remove     = s2mu106_pmeter_remove,
+};
+
+static int __init s2mu106_pmeter_init(void)
+{
+       int ret = 0;
+
+       ret = platform_driver_register(&s2mu106_pmeter_driver);
+
+       return ret;
+}
+module_init(s2mu106_pmeter_init);
+
+static void __exit s2mu106_pmeter_exit(void)
+{
+       platform_driver_unregister(&s2mu106_pmeter_driver);
+}
+module_exit(s2mu106_pmeter_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_DESCRIPTION("PowerMeter driver for S2MU106");
diff --git a/include/linux/input/s2mu106_haptic.h b/include/linux/input/s2mu106_haptic.h
new file mode 100644 (file)
index 0000000..f6011d0
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * s2mu106_haptic.h
+ * Samsung S2MU106 Fuel Gauge Header
+ *
+ * Copyright (C) 2018 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 __S2MU106_HAPTIC_H
+#define __S2MU106_HAPTIC_H __FILE__
+
+#define S2MU106_REG_HAPTIC_INT         0x00
+#define S2MU106_REG_HBST_INT           0x01
+#define S2MU106_REG_HAPTIC_INT_MASK    0x02
+#define S2MU106_REG_HBST_INT_MASK      0x03
+#define S2MU106_REG_HBST_STATUS1       0x04
+#define S2MU106_REG_PERI_TAR1          0x05
+#define S2MU106_REG_PERI_TAR2          0x06
+#define S2MU106_REG_DUTY_TAR1          0x07
+#define S2MU106_REG_DUTY_TAR2          0x08
+#define S2MU106_REG_HAPTIC_MODE                0x09
+#define S2MU106_REG_OV_BK_OPTION       0x0A
+#define S2MU106_REG_OV_WAVE_NUM                0x0B
+#define S2MU106_REG_OV_AMP             0x0C
+#define S2MU106_REG_PWM_CNT_NUM                0x10
+#define S2MU106_REG_FILTERCOEF1                0x13
+#define S2MU106_REG_FILTERCOEF2                0x14
+#define S2MU106_REG_FILTERCOEF3                0x15
+#define S2MU106_REG_IMPCONF1           0x16
+#define S2MU106_REG_IMPCONF2           0x17
+#define S2MU106_REG_IMPCONF3           0x18
+#define S2MU106_REG_AMPCOEF1           0x19
+#define S2MU106_REG_AMPCOEF2           0x1A
+#define S2MU106_REG_AMPCOEF3           0x1B
+#define S2MU106_REG_HT_OTP0            0x20
+#define S2MU106_REG_HT_OTP2            0x22
+#define S2MU106_REG_HBST_CTRL0         0x2B
+#define S2MU106_REG_HBST_CTRL1         0x2C
+
+/* S2MU106_REG_HBST_CTRL1 */
+#define HAPTIC_BOOST_VOLTAGE_MASK      0x3F
+
+/* S2MU106_REG_HT_OTP0 */
+#define HBST_OK_MASK_EN                        0x02
+
+/* S2MU106_REG_HT_OTP0 */
+#define VCEN_SEL_MASK                  0xC0
+
+/* S2MU106_REG_HBST_CTRL0 */
+#define SEL_HBST_HAPTIC_MASK           0x02
+
+/* S2MU106_REG_OV_BK_OPTION */
+#define LRA_MODE_SET_MASK              1 << 7
+
+/* S2MU106_REG_HAPTIC_MODE */
+#define LRA_MODE_EN                    0x20
+#define ERM_HDPWM_MODE_EN              0x41
+#define ERM_MODE_ON                    0x01
+#define ERM_MODE_OFF                   0x00
+
+enum s2mu106_haptic_operation_type {
+       S2MU106_HAPTIC_ERM_I2C,
+       S2MU106_HAPTIC_ERM_GPIO,
+       S2MU106_HAPTIC_LRA,
+};
+
+enum s2mu106_haptic_pulse_mode {
+       S2MU106_EXTERNAL_MODE,
+       S2MU106_INTERNAL_MODE,
+};
+
+struct s2mu106_haptic_boost {
+       /* haptic boost */
+       bool en;
+       bool automode;
+       int level;
+};
+
+struct s2mu106_haptic_platform_data {
+       u16 max_timeout;
+       u32 duty;
+       u32 period;
+       u32 max_duty;
+       u16 reg2;
+
+       unsigned int pwm_id;
+
+       /* haptic drive mode */
+       enum s2mu106_haptic_operation_type hap_mode;
+
+       /* haptic boost */
+       struct s2mu106_haptic_boost hbst;
+
+       void (*init_hw)(void);
+       void (*motor_en)(bool);
+};
+
+struct s2mu106_haptic_data {
+       struct s2mu106_dev *s2mu106;
+       struct i2c_client *i2c;
+       struct s2mu106_haptic_platform_data *pdata;
+       struct device *dev;
+       struct input_dev *input_dev;
+
+       enum s2mu106_haptic_operation_type hap_mode;
+       u32 intensity;
+
+       struct pwm_device *pwm;
+       struct mutex mutex;
+       struct work_struct work;
+       struct workqueue_struct *workqueue;
+       spinlock_t lock;
+
+       bool running;
+};
+
+#endif /* __S2MU106_HAPTIC_H */
diff --git a/include/linux/leds-s2mu106-rgb.h b/include/linux/leds-s2mu106-rgb.h
new file mode 100644 (file)
index 0000000..dc80e41
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * leds-s2mu106-rgb.h - RGB-led driver for Samsung S2MU106
+ *
+ * Copyright (C) 2018 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.
+ *
+ */
+#ifndef __LEDS_S2MU106_RGB_H__
+#define __LEDS_S2MU106_RGB_H__
+
+#define MASK(width, shift)     (((0x1 << (width)) - 1) << shift)
+#define ABS(x) (((x)<0) ? -(x) : (x))
+
+#define S2MU106_RGB_BRIGHTNESS_LED 1
+#define S2MU106_RGB_LED_MAX    4
+
+#define S2MU106_RGB_CURR_MAX   255 // 0.1mA unit
+#define S2MU106_RGB_RAMP_MAX   2200 // 1ms unit
+#define S2MU106_RGB_ON_MAX     3250 // 1ms unit
+#define S2MU106_RGB_OFF_MAX0   12000 // 1ms unit
+#define S2MU106_RGB_OFF_MAX1   3250 // 1ms unit
+
+#define S2MU106_RGB_LED_EN     0x43
+#define S2MU106_RGB_LED1_CURR  0x44
+#define S2MU106_RGB_LED2_CURR  0x45
+#define S2MU106_RGB_LED3_CURR  0x46
+#define S2MU106_RGB_LED4_CURR  0x47
+#define S2MU106_RGB_LED1_RAMP  0x48
+#define S2MU106_RGB_LED1_DUR   0x49
+#define S2MU106_RGB_LED2_RAMP  0x4A
+#define S2MU106_RGB_LED2_DUR   0x4B
+#define S2MU106_RGB_LED3_RAMP  0x4C
+#define S2MU106_RGB_LED3_DUR   0x4D
+#define S2MU106_RGB_LED4_RAMP  0x4E
+#define S2MU106_RGB_LED4_DUR   0x4F
+#define S2MU106_RGB_LED_CTRL0  0x51
+#define S2MU106_RGB_LED_CTRL1  0x52
+
+#define S2MU106_RGB_LEDX_RAMP_UP_MASK  MASK(4,4)
+#define S2MU106_RGB_LEDX_RAMP_DOWN_MASK        MASK(4,0)
+
+#define S2MU106_RGB_LEDX_ON_DUR_MASK   MASK(4,4)
+#define S2MU106_RGB_LEDX_OFF_DUR_MASK  MASK(4,0)
+
+#define S2MU106_RGB_OFF_DUR_MODE_MASK  MASK(4,4)
+#define S2MU106_RGB_OFF_DUR_MODE_NUM   2
+#define S2MU106_RGB_ONOFF_TIME_NUM     16
+
+int s2mu106_off_time[S2MU106_RGB_OFF_DUR_MODE_NUM]
+                                       [S2MU106_RGB_ONOFF_TIME_NUM] = {
+       {0, 500, 1000, 1500, 2000,
+       2500, 3000, 3500, 4000, 4500,
+       5000, 6000, 7000, 8000, 10000,
+       12000},
+       {100, 200, 300, 400, 500,
+       750, 1000, 1250, 1500, 1750,
+       2000, 2250, 2500, 2750, 3000,
+       3250},
+};
+
+u8 s2mu106_rgb_off_dur_mode_mask[] = {
+       0,      // not used
+       MASK(1,7),
+       MASK(1,6),
+       MASK(1,5),
+       MASK(1,4),
+};
+
+u8 s2mu106_rgb_dur_reg[] = {
+       S2MU106_RGB_LED1_DUR,   // not used
+       S2MU106_RGB_LED1_DUR,
+       S2MU106_RGB_LED2_DUR,
+       S2MU106_RGB_LED3_DUR,
+       S2MU106_RGB_LED4_DUR,
+};
+
+u8 s2mu106_rgb_ramp_reg[] = {
+       S2MU106_RGB_LED1_RAMP,  // not used
+       S2MU106_RGB_LED1_RAMP,
+       S2MU106_RGB_LED2_RAMP,
+       S2MU106_RGB_LED3_RAMP,
+       S2MU106_RGB_LED4_RAMP,
+};
+
+u8 s2mu106_rgb_curr_reg[] = {
+       S2MU106_RGB_LED1_CURR,  // not used
+       S2MU106_RGB_LED1_CURR,
+       S2MU106_RGB_LED2_CURR,
+       S2MU106_RGB_LED3_CURR,
+       S2MU106_RGB_LED4_CURR,
+};
+
+u8 s2mu106_rgb_led_en_mask[] = {
+       0,      //      not used
+       MASK(2,6),
+       MASK(2,4),
+       MASK(2,2),
+       MASK(2,0),
+};
+
+u8 s2mu106_rgb_led_en_shift[] = {
+       0,      //      not used
+       6,
+       4,
+       2,
+       0,
+};
+
+#define S2MU106_RGB_MODE_OFF   0x00
+#define S2MU106_RGB_MODE_CONST 0x01
+#define S2MU106_RGB_MODE_TOGGLE        0x02
+#define S2MU106_RGB_MODE_MAX   0x02
+
+char *s2mu106_rgb_mode_string[] = {
+       "OFF",
+       "CONST",
+       "TOGGLE",
+};
+
+struct s2mu106_rgb_led {
+       int id;
+       u32 curr;
+       u32 timer;
+       u8 mode;
+};
+
+struct s2mu106_rgb_platform_data {
+       struct s2mu106_rgb_led *led;
+       int led_num;
+
+       u32 default_current;
+       u32 max_current;
+       int def_off_dur_mode;
+};
+
+struct s2mu106_rgb_data {
+       struct s2mu106_rgb_platform_data *pdata;
+       struct s2mu106_rgb_led led[S2MU106_RGB_LED_MAX];
+       struct led_classdev cdev;
+       struct device *dev;
+
+       int off_dur_mode;
+
+       struct i2c_client *i2c;
+};
+
+#endif
diff --git a/include/linux/leds-s2mu106.h b/include/linux/leds-s2mu106.h
new file mode 100644 (file)
index 0000000..31b37a1
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * leds-s2mu106.h - Flash-led driver for Samsung S2MU106
+ *
+ * Copyright (C) 2018 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.
+ *
+ */
+#ifndef __LEDS_S2MU106_FLASH_H__
+#define __LEDS_S2MU106_FLASH_H__
+#include <linux/leds.h>
+
+#define MASK(width, shift)     (((0x1 << (width)) - 1) << shift)
+
+#define FLED_EN 0
+#define DEBUG_TEST_READ                0
+
+#define S2MU106_CH_MAX 3
+
+#define S2MU106_FLED_PMIC_ID   0xF5
+#define S2MU106_FLED_REV_NO    MASK(4,0)
+
+/* Interrupt register */
+#define S2MU106_FLED_INT1      0x04
+#define S2MU106_FLED_INT2      0x05
+
+#define S2MU106_FLED_INT1_MASK 0x0B
+#define S2MU106_FLED_INT2_MASK 0x0C
+
+/* Status register */
+#define S2MU106_FLED_STATUS1   0x14
+#define S2MU106_FLED_STATUS2   0x15
+
+/* Mask for status register */
+#define S2MU106_CH1_FLASH_ON   MASK(1,7)
+#define S2MU106_CH1_TORCH_ON   MASK(1,6)
+#define S2MU106_CH2_FLASH_ON   MASK(1,5)
+#define S2MU106_CH2_TORCH_ON   MASK(1,4)
+#define S2MU106_CH3_FLASH_ON   MASK(1,3)
+#define S2MU106_CH3_TORCH_ON   MASK(1,2)
+#define S2MU106_FLED_ON_CHECK  MASK(6,2)
+
+/* Channel control */
+#define S2MU106_FLED_CH1_CTRL0 0x53
+#define S2MU106_FLED_CH1_CTRL1 0x54
+#define S2MU106_FLED_CH1_CTRL2 0x55
+
+#define S2MU106_FLED_CH2_CTRL0 0x56
+#define S2MU106_FLED_CH2_CTRL1 0x57
+#define S2MU106_FLED_CH2_CTRL2 0x58
+
+#define S2MU106_FLED_CH3_CTRL0 0x59
+#define S2MU106_FLED_CH3_CTRL1 0x5A
+#define S2MU106_FLED_CH3_CTRL2 0x5B
+
+/* Mask for channel control register */
+#define S2MU106_CHX_OPEN_PROT_EN       MASK(1,7)
+#define S2MU106_CHX_SHORT_PROT_EN      MASK(1,6)
+#define S2MU106_CHX_FLASH_IOUT MASK(5,0)
+#define S2MU106_CHX_TORCH_TMR_MODE     MASK(1,7)
+#define S2MU106_CHX_DIS_TORCH_TMR      MASK(1,6)
+#define S2MU106_CHX_FLASH_TMR_MODE     MASK(1,5)
+#define S2MU106_CHX_TORCH_IOUT MASK(5,0)
+#define S2MU106_CHX_FLASH_TMR_DUR      MASK(4,4)
+#define S2MU106_CHX_TORCH_TMR_DUR      MASK(4,0)
+
+/* Mode control */
+#define S2MU106_FLED_CTRL0     0x5C
+#define S2MU106_FLED_CTRL1     0x5D
+#define S2MU106_FLED_CTRL2     0x5E
+#define S2MU106_FLED_CTRL3     0x5F
+#define S2MU106_FLED_CTRL4     0x60
+#define S2MU106_FLED_CTRL5     0x61
+#define S2MU106_FLED_CTRL6     0x62
+#define S2MU106_FLED_CTRL7     0x63
+
+/* Mask for channel control register */
+#define S2MU106_CHX_FLASH_FLED_EN      MASK(3,3)
+#define S2MU106_CHX_TORCH_FLED_EN      MASK(3,0)
+#define S2MU106_EN_FLED_PRE    MASK(1,5)
+#define S2MU106_FLED_EN        0x5
+
+/* Mask for Mode control register */
+#define S2MU106_FLED_MODE      MASK(2,6)
+#define S2MU106_EN_FLED_PRE    MASK(1,5)
+#define S2MU106_FLED_SOFT_ON_TIME      MASK(3,1)
+#define S2MU106_FLED_REG_RESET MASK(1,0)
+#define S2MU106_CHX_FLASH_FLED_EN      MASK(3,3)
+#define S2MU106_CHX_TORCH_FLED_EN      MASK(3,0)
+#define S2MU106_FLED_ADAPTIVE_MODE_EN  MASK(1,5)
+#define S2MU106_SET_BOOST_VOUT_FLASH   MASK(5,0)
+#define S2MU106_READ_BOOST_VOUT_FLASH  MASK(5,0)
+#define S2MU106_F2C_LC_IBAT    MASK(6,0)
+#define S2MU106_F2C_SYS_MIN_REG        MASK(3,0)
+
+enum cam_flash_mode{
+       CAM_FLASH_MODE_NONE=0,          //CAM2_FLASH_MODE_NONE=0,
+       CAM_FLASH_MODE_OFF,                     //CAM2_FLASH_MODE_OFF,
+       CAM_FLASH_MODE_SINGLE,          //CAM2_FLASH_MODE_SINGLE,
+       CAM_FLASH_MODE_TORCH,           //CAM2_FLASH_MODE_TORCH,
+};
+
+enum s2mu106_fled_mode {
+       S2MU106_FLED_MODE_OFF,
+       S2MU106_FLED_MODE_TORCH,
+       S2MU106_FLED_MODE_FLASH,
+       S2MU106_FLED_MODE_MAX,
+};
+
+struct s2mu106_fled_chan {
+       int id;
+       u32 curr;
+       u32 timer;
+       u8 mode;
+};
+
+struct s2mu106_fled_platform_data {
+       struct s2mu106_fled_chan *channel;
+       int chan_num;
+#if FLED_EN
+       int fled-en1-pin;
+       int fled-en2-pin;
+       int fled-en3-pin;
+       int fled-en4-pin;
+#endif
+       u32 default_current;
+       u32 max_current;
+       u8 default_mode;
+       u32 default_timer;
+};
+
+struct s2mu106_fled_data {
+       struct s2mu106_fled_platform_data *pdata;
+       struct s2mu106_fled_chan channel[S2MU106_CH_MAX];
+       struct led_classdev cdev;
+       struct device *dev;
+
+       int rev_id;
+
+       struct i2c_client *i2c;
+};
+
+int s2mu106_fled_set_mode_ctrl(int chan, enum cam_flash_mode cam_mode);
+int s2mu106_fled_set_curr(int chan, enum cam_flash_mode cam_mode, int curr);
+int s2mu106_fled_get_curr(int chan, enum cam_flash_mode cam_mode);
+
+#endif
diff --git a/include/linux/power/s2mu106_charger.h b/include/linux/power/s2mu106_charger.h
new file mode 100644 (file)
index 0000000..71747f7
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * s2mu106_charger.h - Header of S2MU106 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 S2MU106_CHARGER_H
+#define S2MU106_CHARGER_H
+#include <linux/mfd/samsung/s2mu106.h>
+
+#if defined(CONFIG_MUIC_NOTIFIER)
+#include <linux/muic/muic.h>
+#include <linux/muic/muic_notifier.h>
+#endif /* CONFIG_MUIC_NOTIFIER */
+
+#include <linux/power/s2mu00x_battery.h>
+
+/* define function if need */
+#define ENABLE_MIVR 0
+
+/* define IRQ function if need */
+#define EN_BAT_DET_IRQ 0
+#define EN_CHG1_IRQ_CHGIN 0
+
+/* Test debug log enable */
+#define EN_TEST_READ 1
+
+
+#define HEALTH_DEBOUNCE_CNT 1
+
+
+#define MINVAL(a, b) ((a <= b) ? a : b)
+#define MASK(width, shift)     (((0x1 << (width)) - 1) << shift)
+
+#define ENABLE 1
+#define DISABLE 0
+
+#define S2MU106_CHG_INT1               0x01
+#define S2MU106_CHG_INT2               0x02
+#define S2MU106_CHG_INT3               0x03
+
+#define S2MU106_CHG_INT1M              0x08
+#define S2MU106_CHG_INT2M              0x09
+#define S2MU106_CHG_INT3M              0x0A
+
+#define S2MU106_CHG_STATUS0            0x0E
+#define S2MU106_CHG_STATUS1            0x0F
+#define S2MU106_CHG_STATUS2            0x10
+#define S2MU106_CHG_STATUS3            0x11
+#define S2MU106_CHG_STATUS4            0x12
+#define S2MU106_CHG_STATUS5            0x13
+
+#define S2MU106_CHG_CTRL0              0x18
+#define S2MU106_CHG_CTRL1              0x19
+#define S2MU106_CHG_CTRL2              0x1A
+#define S2MU106_CHG_CTRL3              0x1B
+#define S2MU106_CHG_CTRL4              0x1C
+#define S2MU106_CHG_CTRL5              0x1D
+#define S2MU106_CHG_CTRL6              0x1E
+#define S2MU106_CHG_CTRL7              0x1F
+#define S2MU106_CHG_CTRL8              0x20
+#define S2MU106_CHG_CTRL9              0x21
+#define S2MU106_CHG_CTRL10             0x22
+
+#define S2MU106_CHG_CTRL11             0x23
+#define S2MU106_CHG_CTRL12             0x24
+#define S2MU106_CHG_CTRL13             0x25
+#define S2MU106_CHG_CTRL14             0x26
+#define S2MU106_CHG_CTRL15             0x27
+#define S2MU106_CHG_CTRL16             0x28
+#define S2MU106_CHG_CTRL17             0x29
+#define S2MU106_CHG_CTRL18             0x2A
+#define S2MU106_CHG_CTRL19             0x2B
+#define S2MU106_CHG_CTRL20             0x2C
+
+#define S2MU106_CHG_CTRL21             0x2D
+#define S2MU106_CHG_CTRL22             0x2E
+#define S2MU106_CHG_CTRL23             0x2F
+#define S2MU106_CHG_CTRL24             0x30
+
+
+/* S2MU106_CHG_CTRL0 */
+#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 BUCK_MODE              1
+#define CHG_MODE               3
+#define OTG_BST_MODE           6
+
+/* S2MU106_CHG_CTRL1 */
+#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)
+
+/* S2MU106_CHG_CTRL5 */
+#define SET_VF_VBAT_SHIFT      0
+#define SET_VF_VBAT_WIDTH      7
+#define SET_VF_VBAT_MASK       MASK(SET_VF_VBAT_WIDTH, SET_VF_VBAT_SHIFT)
+
+/* S2MU106_CHG_CTRL7 */
+#define FAST_CHARGING_CURRENT_SHIFT    0
+#define FAST_CHARGING_CURRENT_WIDTH    6
+#define FAST_CHARGING_CURRENT_MASK     MASK(FAST_CHARGING_CURRENT_WIDTH,\
+                                       FAST_CHARGING_CURRENT_SHIFT)
+
+/* S2MU106_CHG_CTRL9 */
+#define BAT_OCP_SHIFT  0
+#define BAT_OCP_WIDTH  3
+#define BAT_OCP_MASK   MASK(BAT_OCP_WIDTH, BAT_OCP_SHIFT)
+
+/* S2MU106_CHG_CTRL10 */
+#define FIRST_TOPOFF_CURRENT_SHIFT     4
+#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    0
+#define SECOND_TOPOFF_CURRENT_WIDTH    4
+#define SECOND_TOPOFF_CURRENT_MASK     MASK(SECOND_TOPOFF_CURRENT_WIDTH,\
+                                       SECOND_TOPOFF_CURRENT_SHIFT)
+
+/* S2MU106_CHG_STATUS0 */
+#define WCIN_STATUS_SHIFT      4
+#define WCIN_STATUS_WIDTH      3
+#define WCIN_STATUS_MASK       MASK(WCIN_STATUS_WIDTH, WCIN_STATUS_SHIFT)
+
+#define CHGIN_STATUS_SHIFT     0
+#define CHGIN_STATUS_WIDTH     3
+#define CHGIN_STATUS_MASK      MASK(CHGIN_STATUS_WIDTH, CHGIN_STATUS_SHIFT)
+
+
+/* S2MU106_CHG_STATUS1 */
+#define CHG_FAULT_STATUS_SHIFT         4
+#define CHG_FAULT_STATUS_WIDTH         4
+#define CHG_FAULT_STATUS_MASK          MASK(CHG_FAULT_STATUS_WIDTH,\
+                                        CHG_FAULT_STATUS_SHIFT)
+
+#define CHG_STATUS_NORMAL      0
+#define CHG_STATUS_WD_SUSPEND  1
+#define CHG_STATUS_WD_RST      2
+#define CHG_STATUS_TSD         3
+#define CHG_STATUS_TFB         4
+#define CHG_STATUS_TO_PRE_CHARGE       6
+#define CHG_STATUS_TO_FAST_CHARGE      7
+
+#define CHG_CV_STATUS_SHIFT    3
+#define CHG_CV_STATUS_MASK             BIT(CHG_Restart_STATUS_SHIFT)
+
+#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)
+
+/* S2MU106_CHG_STATUS2 */
+#define OTG_STATUS_SHIFT       6
+#define OTG_STATUS_WIDTH       2
+#define OTG_STATUS_MASK                MASK(OTG_STATUS_WIDTH, OTG_STATUS_SHIFT)
+
+#define TX_STATUS_SHIFT                4
+#define TX_STATUS_WIDTH                2
+#define TX_STATUS_MASK         MASK(TX_STATUS_WIDTH, TX_STATUS_SHIFT)
+
+/* S2MU106_CHG_STATUS3 */
+#define BAT_STATUS_SHIFT       1
+#define BAT_STATUS_WIDTH       3
+#define BAT_STATUS_MASK                MASK(BAT_STATUS_WIDTH, BAT_STATUS_SHIFT)
+
+#define DET_BAT_STATUS_SHIFT   0
+#define DET_BAT_STATUS_MASK    BIT(DET_BAT_STATUS_SHIFT)
+
+/* S2MU106_CHG_STATUS5 */
+#define ICR_STATUS_SHIFT       2
+#define ICR_STATUS_MASK                BIT(ICR_STATUS_SHIFT)
+
+#define IVR_STATUS_SHIFT       3
+#define IVR_STATUS_MASK                BIT(IVR_STATUS_SHIFT)
+
+/* S2MU106_CHG_CTRL3 */
+#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)
+
+/* S2MU106_CHG_CTRL4 */
+#define SET_CHGIN_IVR_SHIFT    2
+#define SET_CHGIN_IVR_WIDTH    2
+#define SET_CHGIN_IVR_MASK     MASK(SET_CHGIN_IVR_WIDTH,\
+                               SET_CHGIN_IVR_SHIFT)
+
+#define SET_WCIN_IVR_SHIFT     2
+#define SET_WCIN_IVR_WIDTH     2
+#define SET_WCIN_IVR_MASK      MASK(SET_WCIN_IVR_WIDTH,\
+                               SET_WCIN_IVR_SHIFT)
+
+/* S2MU106_CHG_CTRL6 */
+#define COOL_CHARGING_CURRENT_SHIFT    0
+#define COOL_CHARGING_CURRENT_WIDTH    6
+#define COOL_CHARGING_CURRENT_MASK     MASK(COOL_CHARGING_CURRENT_WIDTH,\
+                                       COOL_CHARGING_CURRENT_SHIFT)
+
+/* S2MU106_CHG_CTRL8 */
+#define SET_VSYS_SHIFT 0
+#define SET_VSYS_WIDTH 3
+#define SET_VSYS_MASK  MASK(SET_VSYS_WIDTH, SET_VSYS_SHIFT)
+
+/* S2MU106_CHG_CTRL10 */
+#define SECOND_TOPOFF_CURRENT_SHIFT    0
+#define SECOND_TOPOFF_CURRENT_WIDTH    4
+#define SECOND_TOPOFF_CURRENT_MASK     MASK(SECOND_TOPOFF_CURRENT_WIDTH,\
+                                       SECOND_TOPOFF_CURRENT_SHIFT)
+
+/* S2MU106_CHG_CTRL12 */
+#define SET_EN_WDT_AP_RESET_SHIFT 5
+#define SET_EN_WDT_AP_RESET_MASK BIT(SET_EN_WDT_AP_RESET_SHIFT)
+
+#define SET_EN_WDT_SHIFT 4
+#define SET_EN_WDT_MASK BIT(SET_EN_WDT_SHIFT)
+
+#define WDT_TIME_SHIFT        1
+#define WDT_TIME_WIDTH        3
+#define WDT_TIME_MASK        MASK(WDT_TIME_WIDTH, WDT_TIME_SHIFT)
+
+#define WDT_CLR_SHIFT 0
+#define WDT_CLR_MASK BIT(WDT_CLR_SHIFT)
+
+/* S2MU106_CHG_CTRL13 */
+#define SET_TIME_FC_CHG_SHIFT  3
+#define SET_TIME_FC_CHG_WIDTH  3
+#define SET_TIME_FC_CHG_MASK   MASK(SET_TIME_FC_CHG_WIDTH, SET_TIME_FC_CHG_SHIFT)
+
+/* S2MU106_CHG_CTRL14 */
+#define TOP_OFF_TIME_SHIFT    0
+#define TOP_OFF_TIME_WIDTH    3
+#define TOP_OFF_TIME_MASK    MASK(TOP_OFF_TIME_WIDTH, TOP_OFF_TIME_SHIFT)
+
+#define FAKE_BAT_LEVEL          50
+
+enum {
+       CHIP_ID = 0,
+};
+
+ssize_t s2mu106_chg_show_attrs(struct device *dev,
+               struct device_attribute *attr, char *buf);
+
+ssize_t s2mu106_chg_store_attrs(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count);
+
+#define S2MU106_CHARGER_ATTR(_name)                            \
+{                                                                          \
+       .attr = {.name = #_name, .mode = 0664},     \
+       .show = s2mu106_chg_show_attrs,                     \
+       .store = s2mu106_chg_store_attrs,                       \
+}
+
+enum {
+       CHG_REG = 0,
+       CHG_DATA,
+       CHG_REGS,
+};
+
+enum {
+       S2MU106_TOPOFF_TIMER_500us      = 0x0,
+       S2MU106_TOPOFF_TIMER_5m         = 0x1,
+       S2MU106_TOPOFF_TIMER_10m        = 0x2,
+       S2MU106_TOPOFF_TIMER_30m        = 0x3,
+       S2MU106_TOPOFF_TIMER_50m        = 0x4,
+       S2MU106_TOPOFF_TIMER_70m        = 0x5,
+       S2MU106_TOPOFF_TIMER_90m        = 0x6,
+       S2MU106_TOPOFF_TIMER_DIS        = 0x7,
+};
+
+enum {
+       S2MU106_WDT_TIMER_40s   = 0x1,
+       S2MU106_WDT_TIMER_50s   = 0x2,
+       S2MU106_WDT_TIMER_60s   = 0x3,
+       S2MU106_WDT_TIMER_70s   = 0x4,
+       S2MU106_WDT_TIMER_80s   = 0x5,
+       S2MU106_WDT_TIMER_90s   = 0x6,
+       S2MU106_WDT_TIMER_100s  = 0x7,
+};
+
+enum {
+       S2MU106_FC_CHG_TIMER_4hr        = 0x1,
+       S2MU106_FC_CHG_TIMER_6hr        = 0x2,
+       S2MU106_FC_CHG_TIMER_8hr        = 0x3,
+       S2MU106_FC_CHG_TIMER_10hr       = 0x4,
+       S2MU106_FC_CHG_TIMER_12hr       = 0x5,
+       S2MU106_FC_CHG_TIMER_14hr       = 0x6,
+       S2MU106_FC_CHG_TIMER_16hr       = 0x7,
+};
+
+enum {
+       S2MU106_SET_OTG_OCP_500mA   = 0x0,
+       S2MU106_SET_OTG_OCP_900mA   = 0x1,
+       S2MU106_SET_OTG_OCP_1200mA  = 0x2,
+       S2MU106_SET_OTG_OCP_1500mA  = 0x3,
+};
+
+enum {
+       S2MU106_SET_BAT_OCP_3500mA   = 0x0,
+       S2MU106_SET_BAT_OCP_4000mA   = 0x1,
+       S2MU106_SET_BAT_OCP_4500mA   = 0x2,
+       S2MU106_SET_BAT_OCP_5000mA   = 0x3,
+       S2MU106_SET_BAT_OCP_5500mA   = 0x4,
+       S2MU106_SET_BAT_OCP_6000mA   = 0x5,
+       S2MU106_SET_BAT_OCP_6500mA   = 0x6,
+       S2MU106_SET_BAT_OCP_7000mA   = 0x7,
+};
+
+typedef struct s2mu106_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;
+} s2mu106_charger_platform_data_t;
+
+
+struct s2mu106_charger_data {
+       struct i2c_client       *i2c;
+       struct device *dev;
+       struct s2mu106_platform_data *s2mu106_pdata;
+       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;
+
+       s2mu106_charger_platform_data_t *pdata;
+       int dev_id;
+       int input_current;
+       int charging_current;
+       int topoff_current;
+       int cable_type;
+       bool is_charging;
+       struct mutex charger_mutex;
+
+
+       bool ovp;
+       bool otg_on;
+
+       int unhealth_cnt;
+       int status;
+       int health;
+
+       /* s2mu106 */
+       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_MUIC_NOTIFIER)
+       struct notifier_block cable_check;
+#endif
+};
+
+#endif /*S2MU106_CHARGER_H*/
diff --git a/include/linux/power/s2mu106_fuelgauge.h b/include/linux/power/s2mu106_fuelgauge.h
new file mode 100644 (file)
index 0000000..ff5548d
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * s2mu106_fuelgauge.h - Header of S2MU106 Fuel Gauge
+ *
+ * Copyright (C) 2018 Samsung Electronics, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __S2MU106_FUELGAUGE_H
+#define __S2MU106_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 USE_EXTERNAL_TEMP      1
+
+#define TEMP_COMPEN            1
+#define BATCAP_LEARN   1
+
+#define S2MU106_REG_STATUS             0x00
+#define S2MU106_REG_IRQ                        0x02
+#define S2MU106_REG_RVBAT              0x04
+#define S2MU106_REG_RCUR_CC            0x06
+#define S2MU106_REG_RSOC               0x08
+#define S2MU106_REG_MONOUT             0x0A
+#define S2MU106_REG_MONOUT_SEL         0x0C
+#define S2MU106_REG_RBATCAP            0x0E
+#define S2MU106_REG_BATCAP             0x10
+#define S2MU106_REG_CAPCC              0x3E
+#define S2MU106_REG_RSOC_R             0x2A
+
+#define S2MU106_REG_RZADJ              0x12
+#define S2MU106_REG_RBATZ0             0x16
+#define S2MU106_REG_RBATZ1             0x18
+#define S2MU106_REG_IRQ_LVL            0x1A
+#define S2MU106_REG_START              0x1E
+
+#define BATT_TEMP_CONSTANT             250
+
+/* Use reserved register region 0x48[3:0]
+ * For battery parameter version check
+ */
+#define S2MU106_REG_FG_ID              0x48
+
+#define S2MU106_REG_VM                 0x67
+
+enum {
+       CURRENT_MODE = 0,
+       LOW_SOC_VOLTAGE_MODE, // not used
+       HIGH_SOC_VOLTAGE_MODE,
+       END_MODE,
+};
+
+static char* mode_to_str[] = {
+       "CC_MODE",
+       "VOLTAGE_MODE", // not used
+       "VOLTAGE_MODE",
+       "END",
+};
+
+struct fg_info {
+       /* battery info */
+       int soc;
+       int battery_profile_index;
+
+       int *battery_table3;
+       int *battery_table4;
+       int *soc_arr_val;
+       int *ocv_arr_val;
+       int *batcap;
+       int *accum;
+       int battery_param_ver;
+
+       int battery_table3_cell1[88];
+       int battery_table4_cell1[22];
+       int soc_arr_val_cell1[22];
+       int ocv_arr_val_cell1[22];
+       int batcap_cell1[4];
+       int accum_cell1[2];
+       int battery_param_ver_cell1;
+
+       int battery_table3_cell2[88];
+       int battery_table4_cell2[22];
+       int soc_arr_val_cell2[22];
+       int ocv_arr_val_cell2[22];
+       int batcap_cell2[4];
+       int accum_cell2[2];
+       int battery_param_ver_cell2;
+};
+
+struct s2mu106_platform_data {
+       int fuel_alert_soc;
+       int fg_irq;
+       int fuel_alert_vol;
+
+       unsigned int capacity_full;
+
+       char *fuelgauge_name;
+
+       struct sec_charging_current *charging_current;
+};
+
+struct s2mu106_fuelgauge_data {
+       struct device           *dev;
+       struct i2c_client       *i2c;
+       struct i2c_client       *pmic;
+       struct mutex            fuelgauge_mutex;
+       struct s2mu106_platform_data *pdata;
+       struct power_supply     *psy_fg;
+       /* struct delayed_work isr_work; */
+
+       int cable_type;
+       bool is_charging; /* charging is enabled */
+       int rsoc;
+       int mode;
+       u8 revision;
+
+       /* HW-dedicated fuelgauge info structure
+        * used in individual fuelgauge file only
+        * (ex. dummy_fuelgauge.c)
+        */
+       struct fg_info      info;
+
+       bool is_fuel_alerted;
+       struct wake_lock fuel_alert_wake_lock;
+
+       unsigned int ui_soc;
+
+       struct mutex fg_lock;
+       struct delayed_work isr_work;
+
+       /* register programming */
+       int reg_addr;
+       u8 reg_data[2];
+       u8 reg_OTP_53;
+       u8 reg_OTP_52;
+
+       int low_temp_limit;
+       int temperature;
+
+       int fg_irq;
+       bool probe_done;
+#if (TEMP_COMPEN) || (BATCAP_LEARN)
+       bool bat_charging; /* battery is charging */
+#endif
+#if (TEMP_COMPEN) && (BATCAP_LEARN)
+       int fcc;
+       int rmc;
+#endif
+#if (TEMP_COMPEN)
+       bool vm_status; /* Now voltage mode or not */
+       bool pre_vm_status;
+       bool pre_is_charging;
+       bool pre_bat_charging;
+
+       int socni;
+       int soc0i;
+       int comp_socr; /* 1% unit */
+       int pre_comp_socr; /* 1% unit */
+       int init_start;
+       int soc_r;
+       int avg_curr;
+#endif
+#if (BATCAP_LEARN)
+       bool learn_start;
+       bool cond1_ok;
+       int c1_count;
+       int c2_count;
+       int capcc;
+       int batcap_ocv;
+       int batcap_ocv_fin;
+       int cycle;
+       int soh;
+#endif
+};
+
+#if (BATCAP_LEARN)
+/* cycle, rLOW_EN, rC1_num, rC2_num, rC1_CURR, rWide_lrn_EN, Fast_lrn_EN, Auto_lrn_EN */
+int BAT_L_CON[8] = {20, 0, 10, 10, 500, 0, 0, 1};
+#endif
+
+#endif /* __S2MU106_FUELGAUGE_H */
diff --git a/include/linux/power/s2mu106_pmeter.h b/include/linux/power/s2mu106_pmeter.h
new file mode 100755 (executable)
index 0000000..a2d8bdc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * s2mu106_pmeter.h - Header of S2MU106 Powermeter 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 S2MU106_PMETER_H
+#define S2MU106_PMETER_H
+#include <linux/mfd/samsung/s2mu106.h>
+#include <linux/power/s2mu00x_battery.h>
+
+struct s2mu106_pmeter_data {
+       struct i2c_client       *i2c;
+       struct device *dev;
+       struct s2mu106_platform_data *s2mu106_pdata;
+};
+
+int s2mu106_powermeter_get_vchg_voltage(void);
+int s2mu106_powermeter_get_vchg_current(void);
+
+#endif /*S2MU106_PMETER_H*/