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
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
--- /dev/null
+/*
+ * 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");
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"
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
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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");
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
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
--- /dev/null
+/*
+ * 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, ®_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, ®_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, ®);
+ 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, ®);
+ 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");
--- /dev/null
+/*
+ * 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, ®_OTP_53);
+ s2mu106_read_reg_byte(fuelgauge->i2c, 0x52, ®_OTP_52);
+ s2mu106_read_reg_byte(fuelgauge->i2c, 0x1E, ®_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, ®_OTP_53);
+ s2mu106_read_reg_byte(fuelgauge->i2c, 0x52, ®_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");
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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*/
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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*/