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