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