From: Keunho Hwang Date: Fri, 11 May 2018 01:19:41 +0000 (+0900) Subject: [9610] drivers/include/dts: s2mu004 mfd/chg/fg bring-up X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=5bdb634c681927665e0fd88ed6a277df00a2cb88;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [9610] drivers/include/dts: s2mu004 mfd/chg/fg bring-up Change-Id: Id05565a585bd71f9830a14e4616dd1eb1ed65fbb Signed-off-by: Keunho Hwang --- diff --git a/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts b/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts index 7455bf6bf54e..bfdc6ed6adc3 100644 --- a/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts +++ b/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts @@ -203,3 +203,79 @@ 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>; + }; +}; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index fc5e4fef89d2..7ed65424eb5e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1002,6 +1002,16 @@ config MFD_SEC_CORE additional drivers must be enabled in order to use the functionality of the device +config MFD_S2MU004 + bool "Samsung Electronics S2MU004 IF PMIC Series Support" + depends on I2C=y + select MFD_CORE + help + Support for the Samsung Electronics MFD series. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device + config MFD_SI476X_CORE tristate "Silicon Laboratories 4761/64/68 AM/FM radio." depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 8703ff17998e..052f7b821ab7 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -200,6 +200,7 @@ obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o obj-$(CONFIG_MFD_RK808) += rk808.o obj-$(CONFIG_MFD_RN5T618) += rn5t618.o obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o +obj-$(CONFIG_MFD_S2MU004) += s2mu004_core.o s2mu004_irq.o obj-$(CONFIG_MFD_SYSCON) += syscon.o obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o obj-$(CONFIG_MFD_VEXPRESS_SYSREG) += vexpress-sysreg.o diff --git a/drivers/mfd/s2mu004_core.c b/drivers/mfd/s2mu004_core.c new file mode 100644 index 000000000000..0fc8cd9b481e --- /dev/null +++ b/drivers/mfd/s2mu004_core.c @@ -0,0 +1,512 @@ +/* + * s2mu004.c - mfd core driver for the s2mu004 + * + * Copyright (C) 2015 Samsung Electronics + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max77843.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_OF) +#include +#include +#endif /* CONFIG_OF */ + +static struct mfd_cell s2mu004_devs[] = { +#if defined(CONFIG_MUIC_S2MU004) + { .name = "s2mu004-muic", }, +#endif /* CONFIG_MUIC_S2MU004 */ +#if defined(CONFIG_AFC_S2MU004) + { .name = "s2mu004-afc", }, +#endif /* CONFIG_AFC_S2MU004 */ +#if defined(CONFIG_REGULATOR_S2MU004) + { .name = "s2mu004-safeout", }, +#endif /* CONFIG_REGULATOR_S2MU004 */ +#if defined(CONFIG_CHARGER_S2MU004) + { .name = "s2mu004-charger", }, +#endif +#if defined(CONFIG_BATTERY_S2MU00X) + { .name = "s2mu00x-battery", }, +#endif +#if defined(CONFIG_LEDS_S2MU004_RGB) + { .name = "leds-s2mu004-rgb", }, +#endif /* CONFIG_LEDS_S2MU004_RGB */ +}; + +int s2mu004_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&s2mu004->i2c_lock); + if (ret < 0) { + pr_err("%s:%s reg(0x%x), ret(%d)\n", MFD_DEV_NAME, + __func__, reg, ret); + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(s2mu004_read_reg); + +int s2mu004_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2mu004->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2mu004_bulk_read); + +int s2mu004_read_word(struct i2c_client *i2c, u8 reg) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_read_word_data(i2c, reg); + mutex_unlock(&s2mu004->i2c_lock); + if (ret < 0) + return ret; + + return ret; +} +EXPORT_SYMBOL_GPL(s2mu004_read_word); + +int s2mu004_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&s2mu004->i2c_lock); + if (ret < 0) + pr_err("%s:%s reg(0x%x), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(s2mu004_write_reg); + +int s2mu004_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2mu004->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2mu004_bulk_write); + +int s2mu004_write_word(struct i2c_client *i2c, u8 reg, u16 value) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_write_word_data(i2c, reg, value); + mutex_unlock(&s2mu004->i2c_lock); + if (ret < 0) + return ret; + return 0; +} +EXPORT_SYMBOL_GPL(s2mu004_write_word); + +int s2mu004_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int ret; + u8 old_val, new_val; + + mutex_lock(&s2mu004->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + old_val = ret & 0xff; + new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&s2mu004->i2c_lock); + return ret; +} +EXPORT_SYMBOL_GPL(s2mu004_update_reg); + +#if defined(CONFIG_OF) +static int of_s2mu004_dt(struct device *dev, + struct s2mu004_platform_data *pdata) +{ + struct device_node *np_s2mu004 = dev->of_node; + int ret = 0; + + if (!np_s2mu004) + return -EINVAL; + + ret = of_get_named_gpio(np_s2mu004, "s2mu004,irq-gpio", 0); + if (ret < 0) + return ret; + else + pdata->irq_gpio = ret; + + pdata->wakeup = of_property_read_bool(np_s2mu004, "s2mu004,wakeup"); + + pr_debug("%s: irq-gpio: %u\n", __func__, pdata->irq_gpio); + + return 0; +} +#else +static int of_s2mu004_dt(struct device *dev, + struct max77834_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int s2mu004_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + struct s2mu004_dev *s2mu004; + struct s2mu004_platform_data *pdata = i2c->dev.platform_data; + + int ret = 0; + u8 temp = 0; + + dev_info(&i2c->dev, "%s start\n", __func__); + + s2mu004 = kzalloc(sizeof(struct s2mu004_dev), GFP_KERNEL); + if (!s2mu004) { + dev_err(&i2c->dev, "%s: Failed to alloc mem for s2mu004\n", + __func__); + return -ENOMEM; + } + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, + sizeof(struct s2mu004_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&i2c->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err; + } + + ret = of_s2mu004_dt(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err; + } + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + s2mu004->dev = &i2c->dev; + s2mu004->i2c = i2c; + s2mu004->irq = i2c->irq; + if (pdata) { + s2mu004->pdata = pdata; + + pdata->irq_base = irq_alloc_descs(-1, 0, S2MU004_IRQ_NR, -1); + if (pdata->irq_base < 0) { + pr_err("%s:%s irq_alloc_descs Fail! ret(%d)\n", + MFD_DEV_NAME, __func__, pdata->irq_base); + ret = -EINVAL; + goto err; + } else + s2mu004->irq_base = pdata->irq_base; + + s2mu004->irq_gpio = pdata->irq_gpio; + s2mu004->wakeup = pdata->wakeup; + } else { + ret = -EINVAL; + goto err; + } + mutex_init(&s2mu004->i2c_lock); + + i2c_set_clientdata(i2c, s2mu004); + + s2mu004_read_reg(s2mu004->i2c, S2MU004_REG_REV_ID, &temp); + if (temp < 0) + pr_err("[s2mu004 mfd] %s : i2c read error\n", __func__); + + s2mu004->pmic_ver = temp & 0x0F; + pr_err("[s2mu004 mfd] %s : ver=0x%x\n", __func__, s2mu004->pmic_ver); + + ret = s2mu004_irq_init(s2mu004); + + if (ret < 0) + goto err_irq_init; + + ret = mfd_add_devices(s2mu004->dev, -1, s2mu004_devs, + ARRAY_SIZE(s2mu004_devs), NULL, 0, NULL); + if (ret < 0) + goto err_mfd; + + device_init_wakeup(s2mu004->dev, pdata->wakeup); + + return ret; + +err_mfd: + mutex_destroy(&s2mu004->i2c_lock); + mfd_remove_devices(s2mu004->dev); +err_irq_init: + i2c_unregister_device(s2mu004->i2c); +err: + kfree(s2mu004); + i2c_set_clientdata(i2c, NULL); + + return ret; +} + +static int s2mu004_i2c_remove(struct i2c_client *i2c) +{ + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + + mfd_remove_devices(s2mu004->dev); + i2c_unregister_device(s2mu004->i2c); + kfree(s2mu004); + i2c_set_clientdata(i2c, NULL); + + return 0; +} + +static const struct i2c_device_id s2mu004_i2c_id[] = { + { MFD_DEV_NAME, TYPE_S2MU004 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, s2mu004_i2c_id); + +#if defined(CONFIG_OF) +static struct of_device_id s2mu004_i2c_dt_ids[] = { + {.compatible = "samsung,s2mu004mfd"}, + { }, +}; +#endif /* CONFIG_OF */ + +#if defined(CONFIG_PM) +static int s2mu004_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + enable_irq_wake(s2mu004->irq); + + disable_irq(s2mu004->irq); + + return 0; +} + +static int s2mu004_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + + pr_debug("%s:%s\n", MFD_DEV_NAME, __func__); + + if (device_may_wakeup(dev)) + disable_irq_wake(s2mu004->irq); + + enable_irq(s2mu004->irq); + + return 0; +} +#else +#define s2mu004_suspend NULL +#define s2mu004_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_HIBERNATION +#if false +u8 s2mu004_dumpaddr_pmic[] = { +#if 0 + s2mu004_LED_REG_IFLASH, + s2mu004_LED_REG_IFLASH1, + s2mu004_LED_REG_IFLASH2, + s2mu004_LED_REG_ITORCH, + s2mu004_LED_REG_ITORCHTORCHTIMER, + s2mu004_LED_REG_FLASH_TIMER, + s2mu004_LED_REG_FLASH_EN, + s2mu004_LED_REG_MAX_FLASH1, + s2mu004_LED_REG_MAX_FLASH2, + s2mu004_LED_REG_VOUT_CNTL, + s2mu004_LED_REG_VOUT_FLASH, + s2mu004_LED_REG_VOUT_FLASH1, + s2mu004_LED_REG_FLASH_INT_STATUS, +#endif + s2mu004_PMIC_REG_PMICID1, + s2mu004_PMIC_REG_PMICREV, + s2mu004_PMIC_REG_MAINCTRL1, + s2mu004_PMIC_REG_MCONFIG, +}; +#endif + +u8 s2mu004_dumpaddr_muic[] = { + s2mu004_MUIC_REG_INTMASK1, + s2mu004_MUIC_REG_INTMASK2, + s2mu004_MUIC_REG_INTMASK3, + s2mu004_MUIC_REG_CDETCTRL1, + s2mu004_MUIC_REG_CDETCTRL2, + s2mu004_MUIC_REG_CTRL1, + s2mu004_MUIC_REG_CTRL2, + s2mu004_MUIC_REG_CTRL3, +}; + +#if false +u8 s2mu004_dumpaddr_haptic[] = { + s2mu004_HAPTIC_REG_CONFIG1, + s2mu004_HAPTIC_REG_CONFIG2, + s2mu004_HAPTIC_REG_CONFIG_CHNL, + s2mu004_HAPTIC_REG_CONFG_CYC1, + s2mu004_HAPTIC_REG_CONFG_CYC2, + s2mu004_HAPTIC_REG_CONFIG_PER1, + s2mu004_HAPTIC_REG_CONFIG_PER2, + s2mu004_HAPTIC_REG_CONFIG_PER3, + s2mu004_HAPTIC_REG_CONFIG_PER4, + s2mu004_HAPTIC_REG_CONFIG_DUTY1, + s2mu004_HAPTIC_REG_CONFIG_DUTY2, + s2mu004_HAPTIC_REG_CONFIG_PWM1, + s2mu004_HAPTIC_REG_CONFIG_PWM2, + s2mu004_HAPTIC_REG_CONFIG_PWM3, + s2mu004_HAPTIC_REG_CONFIG_PWM4, +}; +#endif + +u8 s2mu004_dumpaddr_led[] = { + s2mu004_RGBLED_REG_LEDEN, + s2mu004_RGBLED_REG_LED0BRT, + s2mu004_RGBLED_REG_LED1BRT, + s2mu004_RGBLED_REG_LED2BRT, + s2mu004_RGBLED_REG_LED3BRT, + s2mu004_RGBLED_REG_LEDBLNK, + s2mu004_RGBLED_REG_LEDRMP, +}; + +static int s2mu004_freeze(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_pmic); i++) + s2mu004_read_reg(i2c, s2mu004_dumpaddr_pmic[i], + &s2mu004->reg_pmic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_muic); i++) + s2mu004_read_reg(i2c, s2mu004_dumpaddr_muic[i], + &s2mu004->reg_muic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_led); i++) + s2mu004_read_reg(i2c, s2mu004_dumpaddr_led[i], + &s2mu004->reg_led_dump[i]); + + disable_irq(s2mu004->irq); + + return 0; +} + +static int s2mu004_restore(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s2mu004_dev *s2mu004 = i2c_get_clientdata(i2c); + int i; + + enable_irq(s2mu004->irq); + + for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_pmic); i++) + s2mu004_write_reg(i2c, s2mu004_dumpaddr_pmic[i], + s2mu004->reg_pmic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_muic); i++) + s2mu004_write_reg(i2c, s2mu004_dumpaddr_muic[i], + s2mu004->reg_muic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(s2mu004_dumpaddr_led); i++) + s2mu004_write_reg(i2c, s2mu004_dumpaddr_led[i], + s2mu004->reg_led_dump[i]); + + return 0; +} +#endif + +const struct dev_pm_ops s2mu004_pm = { + .suspend = s2mu004_suspend, + .resume = s2mu004_resume, +#ifdef CONFIG_HIBERNATION + .freeze = s2mu004_freeze, + .thaw = s2mu004_restore, + .restore = s2mu004_restore, +#endif +}; + +static struct i2c_driver s2mu004_i2c_driver = { + .driver = { + .name = MFD_DEV_NAME, + .owner = THIS_MODULE, +#if defined(CONFIG_PM) + .pm = &s2mu004_pm, +#endif /* CONFIG_PM */ +#if defined(CONFIG_OF) + .of_match_table = s2mu004_i2c_dt_ids, +#endif /* CONFIG_OF */ + }, + .probe = s2mu004_i2c_probe, + .remove = s2mu004_i2c_remove, + .id_table = s2mu004_i2c_id, +}; + +static int __init s2mu004_i2c_init(void) +{ + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + return i2c_add_driver(&s2mu004_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(s2mu004_i2c_init); + +static void __exit s2mu004_i2c_exit(void) +{ + i2c_del_driver(&s2mu004_i2c_driver); +} +module_exit(s2mu004_i2c_exit); + +MODULE_DESCRIPTION("s2mu004 multi-function core driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/s2mu004_irq.c b/drivers/mfd/s2mu004_irq.c new file mode 100644 index 000000000000..d725b8c1fe18 --- /dev/null +++ b/drivers/mfd/s2mu004_irq.c @@ -0,0 +1,313 @@ +/* + * s2mu004-irq.c - Interrupt controller support for s2mu004 + * + * Copyright (C) 2015 Samsung Electronics Co.Ltd + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on s2mu004-irq.c + */ + +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 5ab90c1f3f7c..f6cd93a340b4 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -610,4 +610,20 @@ config CHARGER_RT9455 help Say Y to enable support for Richtek RT9455 battery charger. +config FUELGAUGE_S2MU004 + tristate "s2mu004 fuel gauge driver" + default n + depends on (MFD_S2MU004) && I2C + help + say y to include support + for s.lsi s2mu004 fuel gauge driver. + +config CHARGER_S2MU004 + tristate "S2MU004 charger support" + depends on (MFD_S2MU004) && I2C + help + Say Y here to enable support for the S2MU004 charger + S2MU004 incluse pmic, led driver. + You have to define MFD_S2MU004 + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index aae4e4a8bbb3..dddc844e2a69 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -82,3 +82,5 @@ obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o +obj-$(CONFIG_FUELGAUGE_S2MU004) += s2mu004_fuelgauge.o +obj-$(CONFIG_CHARGER_S2MU004) += s2mu004_charger.o diff --git a/drivers/power/supply/s2mu004_charger.c b/drivers/power/supply/s2mu004_charger.c new file mode 100644 index 000000000000..1ce826da779e --- /dev/null +++ b/drivers/power/supply/s2mu004_charger.c @@ -0,0 +1,1247 @@ +/* + * s2mu004_charger.c - S2MU004 Charger Driver + * + * Copyright (C) 2016 Samsung Electronics Co.Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include +#include +#include +//#include + +#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"); diff --git a/drivers/power/supply/s2mu004_fuelgauge.c b/drivers/power/supply/s2mu004_fuelgauge.c new file mode 100644 index 000000000000..6019fa5a60c5 --- /dev/null +++ b/drivers/power/supply/s2mu004_fuelgauge.c @@ -0,0 +1,1585 @@ +/* + * s2mu004_fuelgauge.c + * Samsung S2MU004 Fuel Gauge Driver + * + * Copyright (C) 2015 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define DEBUG + +#define SINGLE_BYTE 1 +#define TABLE_SIZE 22 +#define FAKE_TEMP 1 + +#include +#include + +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"); diff --git a/include/linux/mfd/samsung/s2mu004-private.h b/include/linux/mfd/samsung/s2mu004-private.h new file mode 100644 index 000000000000..a29ec841bc5e --- /dev/null +++ b/include/linux/mfd/samsung/s2mu004-private.h @@ -0,0 +1,283 @@ +/* + * s2mu004-private.h - Voltage regulator driver for the s2mu004 + * + * Copyright (C) 2016 Samsung Electronics Co.Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __LINUX_MFD_S2MU004_PRIV_H +#define __LINUX_MFD_S2MU004_PRIV_H + +#include + +//#include +//#include + +#define S2MU004_I2C_ADDR (0x7A) +#define S2MU004_REG_INVALID (0xff) + +enum s2mu004_reg { + /* Slave addr = 0x7A */ + S2MU004_REG_SC_INT1, + S2MU004_REG_SC_INT2, + S2MU004_REG_SC_INT1_MASK, + S2MU004_REG_SC_INT2_MASK, + S2MU004_REG_AFC_INT, + S2MU004_REG_AFC_INT_MASK, + S2MU004_REG_MUIC_INT1, + S2MU004_REG_MUIC_INT2, + S2MU004_REG_MUIC_INT1_MASK, + S2MU004_REG_MUIC_INT2_MASK, + + S2MU004_REG_SC_STATUS0, + S2MU004_REG_SC_STATUS1, + S2MU004_REG_SC_STATUS2, + S2MU004_REG_SC_STATUS3, + S2MU004_REG_SC_STATUS4, + S2MU004_REG_SC_STATUS5, + S2MU004_REG_SC_CTRL0, + S2MU004_REG_SC_CTRL1, + S2MU004_REG_SC_CTRL2, + S2MU004_REG_SC_CTRL3, + S2MU004_REG_SC_CTRL4, + S2MU004_REG_SC_CTRL5, + S2MU004_REG_SC_CTRL6, + S2MU004_REG_SC_CTRL7, + S2MU004_REG_SC_CTRL8, + S2MU004_REG_SC_CTRL9, + S2MU004_REG_SC_CTRL10, + S2MU004_REG_SC_CTRL11, + S2MU004_REG_SC_CTRL12, + S2MU004_REG_SC_CTRL13, + S2MU004_REG_SC_CTRL14, + S2MU004_REG_SC_CTRL15, + S2MU004_REG_SC_CTRL16, + S2MU004_REG_SC_CTRL17, + S2MU004_REG_SC_CTRL18, + S2MU004_REG_SC_TEST0, + S2MU004_REG_SC_TEST1, + S2MU004_REG_SC_TEST2, + S2MU004_REG_SC_TEST3, + S2MU004_REG_SC_TEST4, + S2MU004_REG_SC_TEST5, + S2MU004_REG_SC_TEST6, + S2MU004_REG_SC_TEST7, + S2MU004_REG_SC_TEST8, + S2MU004_REG_SC_RSVD2B, + S2MU004_REG_SC_TEST10, + + S2MU004_REG_LED_EN = 0x39, + S2MU004_REG_LED1_CURRENT, + S2MU004_REG_LED2_CURRENT, + S2MU004_REG_LED3_CURRENT, + S2MU004_REG_LED4_CURRENT, + S2MU004_REG_LED1_RAMP, + S2MU004_REG_LED1_DUR, + S2MU004_REG_LED2_RAMP, + S2MU004_REG_LED2_DUR, + S2MU004_REG_LED3_RAMP, + S2MU004_REG_LED3_DUR, + S2MU004_REG_LED4_RAMP, + S2MU004_REG_LED4_DUR, + S2MU004_REG_LED_TEST0, + S2MU004_REG_LED_CTRL0, + + S2MU004_REG_AFC_STATUS = 0x48, + S2MU004_REG_AFC_CTRL1, + S2MU004_REG_AFC_CTRL2, + S2MU004_REG_TX_BYTE1, + S2MU004_REG_RX_BYTE1, + S2MU004_REG_RX_BYTE2, + S2MU004_REG_RX_BYTE3, + S2MU004_REG_RX_BYTE4, + S2MU004_REG_RX_BYTE5, + S2MU004_REG_RX_BYTE6, + S2MU004_REG_RX_BYTE7, + S2MU004_REG_RX_BYTE8, + S2MU004_REG_RX_BYTE9, + S2MU004_REG_RX_BYTE10, + S2MU004_REG_RX_BYTE11, + S2MU004_REG_RX_BYTE12, + S2MU004_REG_RX_BYTE13, + S2MU004_REG_RX_BYTE14, + S2MU004_REG_RX_BYTE15, + S2MU004_REG_RX_BYTE16, + + S2MU004_REG_AFC_LOGIC_CTRL2 = 0x5F, + + S2MU004_REG_MUIC_ADC = 0x61, + S2MU004_REG_MUIC_DEVICE_TYPE1, + S2MU004_REG_MUIC_DEVICE_TYPE2, + S2MU004_REG_MUIC_DEVICE_TYPE3, + S2MU004_REG_MUIC_BUTTON1, + S2MU004_REG_MUIC_BUTTON2, + S2MU004_REG_MUIC_RESET, + S2MU004_REG_MUIC_CHG_TYPE, + S2MU004_REG_MUIC_DEVICE_APPLE, + S2MU004_REG_MUIC_BCD_RESCAN, + S2MU004_REG_MUIC_TEST1, + S2MU004_REG_MUIC_TEST2, + S2MU004_REG_MUIC_TEST3, + S2MU004_REG_MUIC_TEST4, + + S2MU004_REG_COMMON_CFG1, + S2MU004_REG_COMMON_CFG2, + S2MU004_REG_MRSTB, + S2MU004_REG_PWRSEL_CTRL0, + S2MU004_REG_RSVD73, + S2MU004_REG_SELFDIS_CFG1, + S2MU004_REG_SELFDIS_CFG2, + S2MU004_REG_SELFDIS_CFG3, + S2MU004_REG_RSVD77, + + S2MU004_REG_REV_ID = 0x82, + + S2MU004_REG_MUIC_RID_CTRL = 0xCC, + S2MU004_REG_MUIC_CTRL1 = 0xC7, + S2MU004_REG_MUIC_TIMER_SET1, + S2MU004_REG_MUIC_TIMER_SET2, + S2MU004_REG_MUIC_SW_CTRL, + S2MU004_REG_MUIC_TIMER_SET3, + S2MU004_REG_MUIC_CTRL2, + S2MU004_REG_MUIC_CTRL3, + S2MU004_REG_CHARGER_DET_OTP = 0xCE, + + S2MU004_REG_LDOADC_VSETL = 0xD4, + S2MU004_REG_LDOADC_VSETH, + S2MU004_REG_AFC_OTP6 = 0xDA, + + S2MU004_REG_END, +}; + +enum s2mu004_irq_source { + CHG_INT1 = 0, + CHG_INT2, + AFC_INT, + MUIC_INT1, + MUIC_INT2, + + S2MU004_IRQ_GROUP_NR, +}; + +#define MUIC_MAX_INT MUIC_INT2 +#define S2MU004_NUM_IRQ_MUIC_REGS (MUIC_MAX_INT - MUIC_INT1 + 1) + +enum s2mu004_irq { + + S2MU004_CHG1_IRQ_SYS, + S2MU004_CHG1_IRQ_Poor_CHG, + S2MU004_CHG1_IRQ_CHG_Fault, + S2MU004_CHG1_IRQ_CHG_RSTART, + S2MU004_CHG1_IRQ_DONE, + S2MU004_CHG1_IRQ_TOP_OFF, + S2MU004_CHG1_IRQ_WCIN, + S2MU004_CHG1_IRQ_CHGIN, + + S2MU004_CHG2_IRQ_ICR, + S2MU004_CHG2_IRQ_IVR, + S2MU004_CHG2_IRQ_AICL, + S2MU004_CHG2_IRQ_TX_Fault, + S2MU004_CHG2_IRQ_OTG_Fault, + S2MU004_CHG2_IRQ_DET_BAT, + S2MU004_CHG2_IRQ_BAT, + + S2MU004_AFC_IRQ_VbADC, + S2MU004_AFC_IRQ_VDNMon, + S2MU004_AFC_IRQ_DNRes, + S2MU004_AFC_IRQ_MPNack, + S2MU004_AFC_IRQ_MRxBufOw, + S2MU004_AFC_IRQ_MRxTrf, + S2MU004_AFC_IRQ_MRxPerr, + S2MU004_AFC_IRQ_MRxRdy, + + S2MU004_MUIC_IRQ1_ATTATCH, + S2MU004_MUIC_IRQ1_DETACH, + S2MU004_MUIC_IRQ1_KP, + S2MU004_MUIC_IRQ1_LKP, + S2MU004_MUIC_IRQ1_LKR, + S2MU004_MUIC_IRQ1_RID_CHG, + + S2MU004_MUIC_IRQ2_VBUS_ON, + S2MU004_MUIC_IRQ2_RSVD_ATTACH, + S2MU004_MUIC_IRQ2_ADC_CHANGE, + S2MU004_MUIC_IRQ2_STUCK, + S2MU004_MUIC_IRQ2_STUCKRCV, + S2MU004_MUIC_IRQ2_MHDL, + S2MU004_MUIC_IRQ2_AV_CHARGE, + S2MU004_MUIC_IRQ2_VBUS_OFF, + S2MU004_IRQ_NR, +}; + +struct s2mu004_dev { + struct device *dev; + struct i2c_client *i2c; /* Slave addr = 0x7A */ + struct mutex i2c_lock; + + int type; + + int irq; + int irq_base; + int irq_gpio; + bool wakeup; + struct mutex irqlock; + int irq_masks_cur[S2MU004_IRQ_GROUP_NR]; + int irq_masks_cache[S2MU004_IRQ_GROUP_NR]; + +#ifdef CONFIG_HIBERNATION + /* For hibernation */ + u8 reg_pmic_dump[S2MU004_PMIC_REG_END]; + u8 reg_muic_dump[S2MU004_MUIC_REG_END]; + u8 reg_led_dump[S2MU004_LED_REG_END]; +#endif + + /* pmic VER/REV register */ + u8 pmic_rev; /* pmic Rev */ + u8 pmic_ver; /* pmic version */ + + struct s2mu004_platform_data *pdata; +}; + +enum s2mu004_types { + TYPE_S2MU004, +}; + +extern int s2mu004_irq_init(struct s2mu004_dev *s2mu004); +extern void s2mu004_irq_exit(struct s2mu004_dev *s2mu004); + +/* s2mu004 shared i2c API function */ +extern int s2mu004_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int s2mu004_bulk_read(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int s2mu004_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int s2mu004_bulk_write(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int s2mu004_write_word(struct i2c_client *i2c, u8 reg, u16 value); +extern int s2mu004_read_word(struct i2c_client *i2c, u8 reg); + +extern int s2mu004_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + +/* s2mu004 check muic path fucntion */ +extern bool is_muic_usb_path_ap_usb(void); +extern bool is_muic_usb_path_cp_usb(void); + +/* s2mu004 Debug. ft */ +extern void s2mu004_muic_read_register(struct i2c_client *i2c); + +/* for charger api */ +extern void s2mu004_hv_muic_charger_init(void); + +#endif /* __LINUX_MFD_S2MU004_PRIV_H */ + diff --git a/include/linux/mfd/samsung/s2mu004.h b/include/linux/mfd/samsung/s2mu004.h new file mode 100644 index 000000000000..0025dc61263c --- /dev/null +++ b/include/linux/mfd/samsung/s2mu004.h @@ -0,0 +1,78 @@ +/* + * s2mu004.h - Driver for the s2mu004 + * + * Copyright (C) 2016 Samsung Electronics Co.Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * This driver is based on max8997.h + * + * s2mu004 has Flash LED, SVC LED, Haptic, MUIC devices. + * The devices share the same I2C bus and included in + * this mfd driver. + */ + +#ifndef __S2MU004_H__ +#define __S2MU004_H__ +#include +#include + +#define MFD_DEV_NAME "s2mu004" +#define M2SH(m) ((m) & 0x0F ? ((m) & 0x03 ? ((m) & 0x01 ? 0 : 1) : ((m) & 0x04 ? 2 : 3)) : \ + ((m) & 0x30 ? ((m) & 0x10 ? 4 : 5) : ((m) & 0x40 ? 6 : 7))) + +#ifdef CONFIG_VIBETONZ +#if false +struct s2mu004_haptic_platform_data { + u16 max_timeout; + u16 duty; + u16 period; + u16 reg2; + char *regulator_name; + unsigned int pwm_id; + + void (*init_hw)(void); + void (*motor_en)(bool); +}; +#endif +#endif + +struct s2mu004_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct s2mu004_platform_data { + /* IRQ */ + int irq_base; + int irq_gpio; + bool wakeup; + int num_regulators; + struct s2mu004_regulator_data *regulators; +#ifdef CONFIG_VIBETONZ + /* haptic motor data */ + /* struct s2mu004_haptic_platform_data *haptic_data; */ +#endif + struct mfd_cell *sub_devices; + int num_subdevs; +}; + +struct s2mu004 { + struct regmap *regmap; +}; + +#endif /* __S2MU004_H__ */ + diff --git a/include/linux/power/s2mu004_charger.h b/include/linux/power/s2mu004_charger.h new file mode 100644 index 000000000000..1433e886f4ff --- /dev/null +++ b/include/linux/power/s2mu004_charger.h @@ -0,0 +1,443 @@ +/* + * s2mu004_charger.h - Header of S2MU004 Charger Driver + * + * Copyright (C) 2016 Samsung Electronics Co.Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef S2MU004_CHARGER_H +#define S2MU004_CHARGER_H +#include +#include + +#if defined(CONFIG_MUIC_NOTIFIER) +#include +#include +#endif /* CONFIG_MUIC_NOTIFIER */ + +//#include +#include + + +#define MASK(width, shift) (((0x1 << (width)) - 1) << shift) + +#define S2MU004_CHG_STATUS0 0x0A +#define S2MU004_CHG_STATUS1 0x0B +#define S2MU004_CHG_STATUS2 0x0C +#define S2MU004_CHG_STATUS3 0x0D +#define S2MU004_CHG_STATUS4 0x0E +#define S2MU004_CHG_STATUS5 0x0F +#define S2MU004_CHG_CTRL0 0x10 +#define S2MU004_CHG_CTRL1 0x11 +#define S2MU004_CHG_CTRL2 0x12 +#define S2MU004_CHG_CTRL3 0x13 +#define S2MU004_CHG_CTRL4 0x14 +#define S2MU004_CHG_CTRL5 0x15 +#define S2MU004_CHG_CTRL6 0x16 +#define S2MU004_CHG_CTRL7 0x17 +#define S2MU004_CHG_CTRL8 0x18 +#define S2MU004_CHG_CTRL9 0x19 +#define S2MU004_CHG_CTRL10 0x1A +#define S2MU004_CHG_CTRL11 0x1B +#define S2MU004_CHG_CTRL12 0x1C +#define S2MU004_CHG_CTRL13 0x1D +#define S2MU004_CHG_CTRL14 0x1E +#define S2MU004_CHG_CTRL15 0x1F +#define S2MU004_CHG_CTRL16 0x20 +#define S2MU004_CHG_CTRL17 0x21 +#define S2MU004_CHG_CTRL18 0x22 +#define S2MU004_CHG_CTRL19 0x23 +#define S2MU004_CHG_CTRL20 0x24 + +#define S2MU004_PWRSEL_CTRL0 0x72 +#define PWRSEL_CTRL0_SHIFT 7 +#define PWRSEL_CTRL0_WIDTH 1 +#define PWRSEL_CTRL0_MASK MASK(PWRSEL_CTRL0_WIDTH, PWRSEL_CTRL0_SHIFT) + +#define EN_JIG_REG_AP_SHIFT 7 +#define EN_JIG_REG_AP_WIDTH 1 +#define EN_JIG_REG_AP_MASK MASK(EN_JIG_REG_AP_WIDTH, EN_JIG_REG_AP_SHIFT) + +/* S2MU004_SC_INT_MASK */ +#define Poor_CHG_INT_SHIFT 1 +#define Poor_CHG_INT_MASK BIT(Poor_CHG_INT_SHIFT) + +/* S2MU004_CHG_STATUS0 */ +#define FG_SOC_STATUS_SHIFT 0 +#define FG_SOC_STATUS_WIDTH 2 +#define FG_SOC_STATUS_MASK MASK(FG_SOC_STATUS_WIDTH, FG_SOC_STATUS_SHIFT) + +#define WCIN_STATUS_SHIFT 2 +#define WCIN_STATUS_WIDTH 3 +#define WCIN_STATUS_MASK MASK(WCIN_STATUS_WIDTH, WCIN_STATUS_SHIFT) + +#define WCIN_M_SHIFT 6 +#define WCIN_M_MASK BIT(WCIN_M_SHIFT) + +#define CHGIN_STATUS_SHIFT 5 +#define CHGIN_STATUS_WIDTH 3 +#define CHGIN_STATUS_MASK MASK(CHGIN_STATUS_WIDTH, CHGIN_STATUS_SHIFT) + +#define VBUS_OVP_MASK 0xE0 +#define VBUS_OVP_SHIFT 5 + +/* S2MU004_CHG_STATUS1 */ +#define SELF_DISCHG_STATUS_SHIFT 7 +#define SELF_DISCHG_STATUS_MASK BIT(SELF_DISCHG_STATUS_SHIFT) + +#define CHG_FAULT_STATUS_SHIFT 3 +#define CHG_FAULT_STATUS_WIDTH 4 +#define CHG_FAULT_STATUS_MASK MASK(CHG_FAULT_STATUS_WIDTH,\ + CHG_FAULT_STATUS_SHIFT) + +#define CHG_STATUS_PRE_CHARGE 1 +#define CHG_STATUS_FAST_CHARGE 2 +#define CHG_STATUS_WD_SUSPEND 3 +#define CHG_STATUS_WD_RST 4 +#define CHG_STATUS_TSD 5 +#define CHG_STATUS_TFB 6 + +#define CHG_Restart_STATUS_SHIFT 2 +#define CHG_Restart_STATUS_MASK BIT(CHG_Restart_STATUS_SHIFT) + +#define TOP_OFF_STATUS_SHIFT 1 +#define TOP_OFF_STATUS_MASK BIT(TOP_OFF_STATUS_SHIFT) + +#define DONE_STATUS_SHIFT 0 +#define DONE_STATUS_MASK BIT(DONE_STATUS_SHIFT) + +/* S2MU004_CHG_STATUS2 */ +#define OTG_STATUS_SHIFT 5 +#define OTG_STATUS_WIDTH 3 +#define OTG_STATUS_MASK MASK(OTG_STATUS_WIDTH, OTG_STATUS_SHIFT) + +#define TX_STATUS_SHIFT 2 +#define TX_STATUS_WIDTH 3 +#define TX_STATUS_MASK MASK(TX_STATUS_WIDTH, TX_STATUS_SHIFT) + +#define SYS_STATUS_SHIFT 2 +#define SYS_STATUS_WIDTH 3 +#define SYS_STATUS_MASK MASK(SYS_STATUS_WIDTH, SYS_STATUS_SHIFT) + +/* S2MU004_CHG_STATUS3 */ +#define DET_BAT_STATUS_SHIFT 0 +#define DET_BAT_STATUS_MASK BIT(DET_BAT_STATUS_SHIFT) + +#define BAT_STATUS_SHIFT 1 +#define BAT_STATUS_WIDTH 2 +#define BAT_STATUS_MASK MASK(BAT_STATUS_WIDTH, BAT_STATUS_SHIFT) + +#define AICL_STATUS_SHIFT 4 +#define AICL_STATUS_WIDTH 2 +#define AICL_STATUS_MASK MASK(AICL_STATUS_WIDTH, AICL_STATUS_SHIFT) + +#define ICR_STATUS_SHIFT 6 +#define ICR_STATUS_MASK BIT(ICR_STATUS_SHIFT) + +#define IVR_STATUS_SHIFT 7 +#define IVR_STATUS_MASK BIT(IVR_STATUS_SHIFT) + +/* S2MU004_CHG_CTRL0 */ +#define EN_CHG_SHIFT 7 +#define EN_CHG_MASK BIT(EN_CHG_SHIFT) + +#define REG_MODE_SHIFT 0 +#define REG_MODE_WIDTH 4 +#define REG_MODE_MASK MASK(REG_MODE_WIDTH, REG_MODE_SHIFT) + +#define CHARGER_OFF_MODE 0 +#define CHG_MODE 3 +#define BUCK_MODE 1 +#define OTG_BST_MODE 6 + +/* S2MU004_CHG_CTRL1 */ + +/* S2MU004_CHG_CTRL2 */ +#define INPUT_CURRENT_LIMIT_SHIFT 0 +#define INPUT_CURRENT_LIMIT_WIDTH 7 +#define INPUT_CURRENT_LIMIT_MASK MASK(INPUT_CURRENT_LIMIT_WIDTH,\ + INPUT_CURRENT_LIMIT_SHIFT) + +/* S2MU004_CHG_CTRL4 */ +#define OTG_OCP_SW_ON_SHIFT 5 +#define OTG_OCP_SW_ON_MASK BIT(OTG_OCP_SW_ON_SHIFT) + +#define OTG_OCP_SW_OFF_SHIFT 4 +#define OTG_OCP_SW_OFF_MASK BIT(OTG_OCP_SW_OFF_SHIFT) + +#define SET_OTG_OCP_SHIFT 2 +#define SET_OTG_OCP_WIDTH 2 +#define SET_OTG_OCP_MASK MASK(SET_OTG_OCP_WIDTH, SET_OTG_OCP_SHIFT) + +/* S2MU004_CHG_CTRL5 */ +#define SET_CHG_2L_DROP_SHIFT 4 +#define SET_CHG_2L_DROP_WIDTH 2 +#define SET_CHG_2L_DROP_MASK MASK(SET_CHG_2L_DROP_WIDTH,\ + SET_CHG_2L_DROP_SHIFT) + +#define SET_CHG_3L_DROP_SHIFT 6 +#define SET_CHG_3L_DROP_WIDTH 2 +#define SET_CHG_3L_DROP_MASK MASK(SET_CHG_3L_DROP_WIDTH,\ + SET_CHG_3L_DROP_SHIFT) + +/* S2MU004_CHG_CTRL6 */ +#define SET_VF_VBAT_SHIFT 0 +#define SET_VF_VBAT_WIDTH 6 +#define SET_VF_VBAT_MASK MASK(SET_VF_VBAT_WIDTH, SET_VF_VBAT_SHIFT) + +/* S2MU004_CHG_CTRL7 */ +#define SET_VF_VBYP_SHIFT 5 +#define SET_VF_VBYP_WIDTH 2 +#define SET_VF_VBYP_MASK MASK(SET_VF_VBYP_WIDTH, SET_VF_VBYP_SHIFT) +#define SET_VSYS_SHIFT 0 +#define SET_VSYS_WIDTH 3 +#define SET_VSYS_MASK MASK(SET_VSYS_WIDTH, SET_VSYS_SHIFT) + +/* S2MU004_CHG_CTRL8 */ +#define COOL_CHARGING_CURRENT_SHIFT 0 +#define COOL_CHARGING_CURRENT_WIDTH 7 +#define COOL_CHARGING_CURRENT_MASK MASK(COOL_CHARGING_CURRENT_WIDTH,\ + COOL_CHARGING_CURRENT_SHIFT) + +/* S2MU004_CHG_CTRL9 */ +#define FAST_CHARGING_CURRENT_SHIFT 0 +#define FAST_CHARGING_CURRENT_WIDTH 7 +#define FAST_CHARGING_CURRENT_MASK MASK(FAST_CHARGING_CURRENT_WIDTH,\ + FAST_CHARGING_CURRENT_SHIFT) + +/* S2MU004_CHG_CTRL11 */ +#define FIRST_TOPOFF_CURRENT_SHIFT 0 +#define FIRST_TOPOFF_CURRENT_WIDTH 4 +#define FIRST_TOPOFF_CURRENT_MASK MASK(FIRST_TOPOFF_CURRENT_WIDTH,\ + FIRST_TOPOFF_CURRENT_SHIFT) + +#define SECOND_TOPOFF_CURRENT_SHIFT 4 +#define SECOND_TOPOFF_CURRENT_WIDTH 4 +#define SECOND_TOPOFF_CURRENT_MASK MASK(SECOND_TOPOFF_CURRENT_WIDTH,\ + SECOND_TOPOFF_CURRENT_SHIFT) + +/* S2MU004_CHG_CTRL12 */ +#define SET_OSC_BUCK_SHIFT 0 +#define SET_OSC_BUCK_WIDTH 3 +#define SET_OSC_BUCK_MASK MASK(SET_OSC_BUCK_WIDTH,\ + SET_OSC_BUCK_SHIFT) + +#define SET_OSC_BUCK_3L_SHIFT 3 +#define SET_OSC_BUCK_3L_WIDTH 3 +#define SET_OSC_BUCK_3L_MASK MASK(SET_OSC_BUCK_3L_WIDTH,\ + SET_OSC_BUCK_3L_SHIFT) + +enum { + S2MU004_OSC_BUCK_FRQ_500kHz = 0x0, + S2MU004_OSC_BUCK_FRQ_750kHz = 0x1, + S2MU004_OSC_BUCK_FRQ_1MHz = 0x2, + S2MU004_OSC_BUCK_FRQ_1P25MHz = 0x3, + S2MU004_OSC_BUCK_FRQ_1P5MHz = 0x4, + S2MU004_OSC_BUCK_FRQ_1P75MHz = 0x5, + S2MU004_OSC_BUCK_FRQ_2MHz = 0x6, + S2MU004_OSC_BUCK_FRQ_2P25MHz = 0x7, +}; + +/* S2MU004_CHG_CTRL13 */ +#define SET_IVR_Recovery_SHIFT 5 +#define SET_IVR_Recovery_MASK BIT(SET_IVR_Recovery_SHIFT) + +#define SET_EN_WDT_SHIFT 1 +#define SET_EN_WDT_MASK BIT(SET_EN_WDT_SHIFT) + +#define SET_EN_WDT_AP_RESET_SHIFT 0 +#define SET_EN_WDT_AP_RESET_MASK BIT(SET_EN_WDT_AP_RESET_SHIFT) + +/* S2MU004_CHG_CTRL14 */ +#define WDT_CLR_SHIFT 0 +#define WDT_CLR_MASK BIT(WDT_CLR_SHIFT) + +/* S2MU004_CHG_CTRL15 */ +#define SET_OSC_BST_SHIFT 5 +#define SET_OSC_BST_WIDTH 3 +#define SET_OSC_BST_MASK MASK(SET_OSC_BST_WIDTH, SET_OSC_BST_SHIFT) + +/* S2MU004_CHG_CTRL16 */ +#define SET_TIME_CHG_SHIFT 3 +#define SET_TIME_CHG_WIDTH 3 +#define SET_TIME_CHG_MASK MASK(SET_TIME_CHG_WIDTH, SET_TIME_CHG_SHIFT) + +/* S2MU004_CHG_CTRL17 */ +#define TOP_OFF_TIME_SHIFT 3 +#define TOP_OFF_TIME_WIDTH 3 +#define TOP_OFF_TIME_MASK MASK(TOP_OFF_TIME_WIDTH, TOP_OFF_TIME_SHIFT) + +#define WDT_TIME_SHIFT 0 +#define WDT_TIME_WIDTH 3 +#define WDT_TIME_MASK MASK(WDT_TIME_WIDTH, WDT_TIME_SHIFT) + +/* S2MU004_CHG_CTRL18 */ +#define CHGIN_ON_SHIFT 2 +#define CHGIN_ON_WIDTH 2 +#define CHGIN_ON_MASK MASK(CHGIN_ON_WIDTH, CHGIN_ON_SHIFT) + +/* S2MU005_REG_SELFDIS_CFG1 */ +#define FC_SELF_DISCHG_SHIFT 3 +#define FC_SELF_DISCHG_MASK BIT(FC_SELF_DISCHG_SHIFT) + +/* S2MU004_REG_SELFDIS_CFG3 */ +#define SELF_DISCHG_MODE_SHIFT 7 +#define SELF_DISCHG_MODE_MASK BIT(SELF_DISCHG_MODE_SHIFT) + +#define FAKE_BAT_LEVEL 50 + +enum { + CHIP_ID = 0, +}; + +ssize_t s2mu004_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t s2mu004_chg_store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#define S2MU004_CHARGER_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = s2mu004_chg_show_attrs, \ + .store = s2mu004_chg_store_attrs, \ +} + +enum { + CHG_REG = 0, + CHG_DATA, + CHG_REGS, +}; + +enum { + S2MU004_TOPOFF_TIMER_500us = 0x0, + S2MU004_TOPOFF_TIMER_5m = 0x1, + S2MU004_TOPOFF_TIMER_10m = 0x2, + S2MU004_TOPOFF_TIMER_30m = 0x3, + S2MU004_TOPOFF_TIMER_50m = 0x4, + S2MU004_TOPOFF_TIMER_70m = 0x5, + S2MU004_TOPOFF_TIMER_90m = 0x6, + S2MU004_TOPOFF_TIMER_DIS = 0x7, +}; + +enum { + S2MU004_WDT_TIMER_40s = 0x1, + S2MU004_WDT_TIMER_50s = 0x2, + S2MU004_WDT_TIMER_60s = 0x3, + S2MU004_WDT_TIMER_70s = 0x4, + S2MU004_WDT_TIMER_80s = 0x5, + S2MU004_WDT_TIMER_90s = 0x6, + S2MU004_WDT_TIMER_100s = 0x7, +}; + +enum { + S2MU004_FC_CHG_TIMER_4hr = 0x1, + S2MU004_FC_CHG_TIMER_6hr = 0x2, + S2MU004_FC_CHG_TIMER_8hr = 0x3, + S2MU004_FC_CHG_TIMER_10hr = 0x4, + S2MU004_FC_CHG_TIMER_12hr = 0x5, + S2MU004_FC_CHG_TIMER_14hr = 0x6, + S2MU004_FC_CHG_TIMER_16hr = 0x7, +}; + +enum { + S2MU004_SET_OTG_OCP_500mA = 0x0, + S2MU004_SET_OTG_OCP_900mA = 0x1, + S2MU004_SET_OTG_OCP_1200mA = 0x2, + S2MU004_SET_OTG_OCP_1500mA = 0x3, +}; + +typedef struct s2mu004_charger_platform_data { + s2mu00x_charging_current_t *charging_current_table; + s2mu00x_charging_current_t *charging_current; + int chg_float_voltage; + char *charger_name; + char *fuelgauge_name; + bool chg_eoc_dualpath; + int recharge_vcell; + uint32_t is_1MHz_switching:1; + int chg_switching_freq; +} s2mu004_charger_platform_data_t; + + +struct s2mu004_charger_data { + struct i2c_client *i2c; + struct device *dev; + struct s2mu004_platform_data *s2mu004_pdata; + struct delayed_work charger_work; + struct delayed_work otg_vbus_work; + + struct workqueue_struct *charger_wqueue; + struct power_supply *psy_chg; + struct power_supply_desc psy_chg_desc; + struct power_supply *psy_otg; + struct power_supply_desc psy_otg_desc; + + s2mu004_charger_platform_data_t *pdata; + int dev_id; + int input_current; + int charging_current; + int topoff_current; + int siop_level; + int cable_type; + int battery_cable_type; + bool is_charging; + struct mutex charger_mutex; + bool noti_check; + + /* register programming */ + int reg_addr; + int reg_data; + + bool full_charged; + bool ovp; + bool otg_on; + + int unhealth_cnt; + bool battery_valid; + int status; + int health; + + struct delayed_work get_capacity; + struct delayed_work afc_current_down; + struct delayed_work polling_work; + + /* s2mu004 */ + int irq_det_bat; + int irq_chg; + int irq_chgin; + int irq_chg_fault; + int irq_vbus; + int irq_rst; + int irq_done; + int irq_sys; + int irq_event; + + int charge_mode; + +#if defined(CONFIG_FUELGAUGE_S2MU004) + int voltage_now; + int voltage_avg; + int voltage_ocv; + unsigned int capacity; +#endif + +#if defined(CONFIG_MUIC_NOTIFIER) + struct notifier_block cable_check; +#endif +}; + +#endif /*S2MU004_CHARGER_H*/ diff --git a/include/linux/power/s2mu004_fuelgauge.h b/include/linux/power/s2mu004_fuelgauge.h new file mode 100644 index 000000000000..717909b7b5c2 --- /dev/null +++ b/include/linux/power/s2mu004_fuelgauge.h @@ -0,0 +1,159 @@ +/* + * s2mu004_fuelgauge.h + * Samsung S2MU004 Fuel Gauge Header + * + * Copyright (C) 2015 Samsung Electronics, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __S2MU004_FUELGAUGE_H +#define __S2MU004_FUELGAUGE_H __FILE__ + +#if defined(ANDROID_ALARM_ACTIVATED) +#include +#endif + +#include +#include + +/* 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 */