[RAMEN9610-12039][9610] drivers: power: add s2mcs02 charger for code sync
authorKeunho Hwang <keunho.hwang@samsung.com>
Mon, 11 Feb 2019 06:18:10 +0000 (15:18 +0900)
committerCosmin Tanislav <demonsingur@gmail.com>
Mon, 22 Apr 2024 17:23:15 +0000 (20:23 +0300)
Add s2mcs02 charger driver for code sync.

Change-Id: Ifb977b368d96262f85666eabbb401f83e6a54b7c
Signed-off-by: Keunho Hwang <keunho.hwang@samsung.com>
drivers/power/supply/Kconfig
drivers/power/supply/Makefile
drivers/power/supply/s2mcs02_charger.c [new file with mode: 0755]
include/linux/power/s2mcs02_charger.h [new file with mode: 0755]

index 7a8fd96862c0de80d7075cdfb2aca00d429d0097..4e2ffa4a3ffc3c05f28771ca4ada44988d992eb1 100644 (file)
@@ -649,4 +649,18 @@ config CHARGER_S2MU106
       S2MU106 incluse pmic, led driver.
       You have to define MFD_S2MU106
 
+config SMALL_CHARGER
+       tristate "Small charger support"
+       depends on I2C
+       help
+         Say Y here to enable support for Small Charger.
+
+config CHARGER_S2MCS02
+       tristate "S2MCS02 small charger support"
+       depends on SMALL_CHARGER
+       help
+         Say Y here to enable support for the S2MCS02 charger.
+
+
+
 endif # POWER_SUPPLY
index 04ea023b3a461b3c066a69f5e2e5d49ebdb54187..78b70489714cde7a9e80088c2a0f2326ac1cef8b 100644 (file)
@@ -87,3 +87,4 @@ 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
+obj-$(CONFIG_CHARGER_S2MCS02)   += s2mcs02_charger.o
diff --git a/drivers/power/supply/s2mcs02_charger.c b/drivers/power/supply/s2mcs02_charger.c
new file mode 100755 (executable)
index 0000000..072b44b
--- /dev/null
@@ -0,0 +1,700 @@
+/*
+ * s2mcs02_charger.c - S2MCS02 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.
+ *
+ */
+
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/power/s2mu00x_battery.h>
+#include <linux/power/s2mcs02_charger.h>
+
+#define DEBUG
+
+#define ENABLE 1
+#define DISABLE 0
+
+#define S2MCS02_SYSFS_SUPPORT 0
+
+static char *s2mcs02_supplied_to[] = {
+       "s2mu00x-battery",
+};
+
+static enum power_supply_property s2mcs02_charger_props[] = {
+       POWER_SUPPLY_PROP_HEALTH,                               /* status */
+       POWER_SUPPLY_PROP_ONLINE,                               /* buck enable/disable */
+       POWER_SUPPLY_PROP_CURRENT_MAX,                  /* input current */
+       POWER_SUPPLY_PROP_CURRENT_NOW,                  /* charge current */
+};
+
+static void s2mcs02_set_charger_state(
+       struct s2mcs02_charger_data *charger, int enable);
+
+static int s2mcs02_read_reg(struct i2c_client *client, u8 reg, u8 *dest)
+{
+       struct s2mcs02_charger_data *charger = i2c_get_clientdata(client);
+       int ret = 0;
+
+       mutex_lock(&charger->io_lock);
+       ret = i2c_smbus_read_byte_data(client, reg);
+       mutex_unlock(&charger->io_lock);
+
+       if (ret < 0) {
+               pr_err("%s: can't read reg(0x%x), ret(%d)\n", __func__, reg, ret);
+               return ret;
+       }
+
+       reg &= 0xFF;
+       *dest = ret;
+
+       return 0;
+}
+
+static int s2mcs02_write_reg(struct i2c_client *client, u8 reg, u8 data)
+{
+       struct s2mcs02_charger_data *charger = i2c_get_clientdata(client);
+       int ret = 0;
+
+       mutex_lock(&charger->io_lock);
+       ret = i2c_smbus_write_byte_data(client, reg, data);
+       mutex_unlock(&charger->io_lock);
+
+       if (ret < 0)
+               pr_err("%s: can't write reg(0x%x), ret(%d)\n", __func__, reg, ret);
+
+       return ret;
+}
+
+static int s2mcs02_update_reg(struct i2c_client *client, u8 reg, u8 val, u8 mask)
+{
+       struct s2mcs02_charger_data *charger = i2c_get_clientdata(client);
+       int ret = 0;
+
+       mutex_lock(&charger->io_lock);
+       ret = i2c_smbus_read_byte_data(client, reg);
+
+       if (ret < 0)
+               pr_err("%s: can't update reg(0x%x), ret(%d)\n", __func__, reg, ret);
+       else {
+               u8 old_val = ret & 0xFF;
+               u8 new_val = (val & mask) | (old_val & (~mask));
+
+               ret = i2c_smbus_write_byte_data(client, reg, new_val);
+       }
+       mutex_unlock(&charger->io_lock);
+
+       return ret;
+}
+
+static void s2mcs02_charger_test_read(
+       struct s2mcs02_charger_data *charger)
+{
+       u8 data = 0;
+       u32 addr = 0;
+       char str[1024] = {0,};
+
+       for (addr = 0x04; addr <= 0x09; addr++) {
+               s2mcs02_read_reg(charger->i2c, addr, &data);
+               sprintf(str + strlen(str), "[0x%02x]0x%02x, ", addr, data);
+       }
+       s2mcs02_read_reg(charger->i2c, 0xE, &data);
+       sprintf(str + strlen(str), "[0xE]0x%02x, ", data);
+       s2mcs02_read_reg(charger->i2c, 0xF, &data);
+       sprintf(str + strlen(str), "[0xF]0x%02x, ", data);
+       s2mcs02_read_reg(charger->i2c, 0x4A, &data);
+       sprintf(str + strlen(str), "[0x4A]0x%02x, ", data);
+       s2mcs02_read_reg(charger->i2c, 0x52, &data);
+       sprintf(str + strlen(str), "[0x52]0x%02x, ", data);
+       pr_info("%s: S2MCS02 : %s\n", __func__, str);
+
+       str[0] = '\0';
+       for (addr = 0x54; addr <= 0x5B; addr++) {
+               s2mcs02_read_reg(charger->i2c, addr, &data);
+               sprintf(str + strlen(str), "[0x%02x]0x%02x, ", addr, data);
+       }
+       pr_info("%s: S2MCS02 : %s\n", __func__, str);
+
+       str[0] = '\0';
+       for (addr = 0x5D; addr <= 0x63; addr++) {
+               s2mcs02_read_reg(charger->i2c, addr, &data);
+               sprintf(str + strlen(str), "[0x%02x]0x%02x, ", addr, data);
+       }
+       pr_info("%s: S2MCS02 : %s\n", __func__, str);
+}
+
+static int s2mcs02_get_charger_state(struct s2mcs02_charger_data *charger)
+{
+       u8 reg_data;
+
+       s2mcs02_read_reg(charger->i2c, S2MCS02_CHG_STATUS0, &reg_data);
+       if (reg_data & CHGIN_OK_STATUS_MASK)
+               return POWER_SUPPLY_STATUS_CHARGING;
+       return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int s2mcs02_get_charger_health(struct s2mcs02_charger_data *charger)
+{
+       u8 reg_data;
+
+       s2mcs02_read_reg(charger->i2c, S2MCS02_CHG_STATUS0, &reg_data);
+       if (!(reg_data & CHGINOV_OK_STATUS_MASK)) {
+                       pr_info("%s: VIN overvoltage\n", __func__);
+                       return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+       } else if (!(reg_data & CHGINUV_OK_STATUS_MASK)) {
+               pr_info("%s: VIN undervoltage\n", __func__);
+               return POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
+       } else
+               return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static void s2mcs02_set_charger_state(
+       struct s2mcs02_charger_data *charger, int enable)
+{
+       pr_info("%s: BUCK_EN(%s)\n", __func__, enable > 0 ? "ENABLE" : "DISABLE");
+
+       if (enable)
+               s2mcs02_update_reg(charger->i2c,
+                       S2MCS02_CHG_SC_CTRL13, EN_SMALL_CHG_MASK, EN_SMALL_CHG_MASK);
+       else
+               s2mcs02_update_reg(charger->i2c, S2MCS02_CHG_SC_CTRL13, 0, EN_SMALL_CHG_MASK);
+
+       s2mcs02_charger_test_read(charger);
+}
+
+static int s2mcs02_get_charge_current(struct s2mcs02_charger_data *charger)
+{
+       u8 reg_data;
+       int charge_current;
+
+       s2mcs02_read_reg(charger->i2c, S2MCS02_CHG_SC_CTRL3, &reg_data);
+       charge_current = (((reg_data * 12) + 15)/100) * 100;
+
+       return charge_current;
+}
+
+static void s2mcs02_set_charge_current(struct s2mcs02_charger_data *charger,
+       int charge_current)
+{
+       u8 reg_data;
+
+       if (!charge_current) {
+               reg_data = 0x42;
+       } else {
+               charge_current = (charge_current < 800) ? 800 : \
+                       (charge_current > 2500) ? 2500 : charge_current;
+                       reg_data = (charge_current - 2) / 12;
+       }
+
+       s2mcs02_write_reg(charger->i2c, S2MCS02_CHG_SC_CTRL3, reg_data);
+       pr_info("%s: charge_current(%d) : 0x%x\n", __func__, charge_current, reg_data);
+}
+
+static int s2mcs02_get_input_current(struct s2mcs02_charger_data *charger)
+{
+       u8 reg_data;
+       int input_current;
+
+       s2mcs02_read_reg(charger->i2c, S2MCS02_CHG_SC_CTRL14, &reg_data);
+       input_current = ((reg_data - 0x10) * 125 + 3000) / 10;
+
+       return input_current;
+}
+
+static void s2mcs02_set_input_current(struct s2mcs02_charger_data *charger,
+       int input_current)
+{
+       u8 reg_data;
+
+       if (input_current < 300)
+               reg_data = 0x10;
+       else if (input_current > 1500)
+               reg_data = 0x70;
+       else
+               reg_data = ((input_current * 10) - 3000) / 125 + 0x10;
+
+       s2mcs02_write_reg(charger->i2c, S2MCS02_CHG_SC_CTRL14, reg_data);
+       pr_info("%s: input_current(%d) : 0x%x\n", __func__, input_current, reg_data);
+}
+
+static void s2mcs02_charger_initialize(struct s2mcs02_charger_data *charger)
+{
+       u8 reg_data;
+
+       pr_info("%s:\n", __func__);
+       s2mcs02_read_reg(charger->i2c, S2MCS02_PMIC_REV, &reg_data);
+
+       if (reg_data == 0x00) {
+               s2mcs02_update_reg(charger->i2c, 0x45, BIT(5), BIT(5));
+               s2mcs02_update_reg(charger->i2c, 0x4E, BIT(7), BIT(7));
+               s2mcs02_write_reg(charger->i2c, 0x50, 0x1F);
+               s2mcs02_update_reg(charger->i2c, 0x57, 0x0C, 0x0C);
+               s2mcs02_update_reg(charger->i2c, 0x5A, 0x06, 0x06);
+       }
+
+#if 0
+       /* interrupt mask */
+       s2mcs02_update_reg(charger->i2c, S2MCS02_CHG_INT1_MASK, BATCVD_INT_M_MASK, BATCVD_INT_M_MASK);
+       s2mcs02_update_reg(charger->i2c, S2MCS02_CHG_INT2_MASK, BUCK_ILIM_INT_M_MASK, BUCK_ILIM_INT_M_MASK);
+#endif
+       /* mask all interrupt */
+       s2mcs02_write_reg(charger->i2c, S2MCS02_CHG_INT1_MASK, 0xFF);
+       s2mcs02_write_reg(charger->i2c, S2MCS02_CHG_INT2_MASK, 0xFF);
+       s2mcs02_write_reg(charger->i2c, S2MCS02_CHG_INT3_MASK, 0xFF);
+
+       /* Set CV_FLG to 4.7V */
+       s2mcs02_update_reg(charger->i2c, S2MCS02_CHG_SC_CTRL9, 0x28, CV_FLG_MASK);
+}
+
+static irqreturn_t s2mcs02_irq_handler(int irq, void *data)
+{
+       u8 data_int_1 = 0;
+       u8 data_int_2 = 0;
+       struct s2mcs02_charger_data *charger = data;
+
+       s2mcs02_read_reg(charger->i2c, S2MCS02_CHG_INT1, &data_int_1);
+       s2mcs02_read_reg(charger->i2c, S2MCS02_CHG_INT2, &data_int_2);
+
+       pr_info("%s: INT 1 : 0x%02x , INT 2 : 0x%02x\n", __func__, data_int_1, data_int_2);
+
+       return IRQ_HANDLED;
+}
+
+static int s2mcs02_chg_get_property(struct power_supply *psy,
+       enum power_supply_property psp, union power_supply_propval *val)
+{
+       struct s2mcs02_charger_data *charger =
+               power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_HEALTH:
+               if (charger->cable_type == POWER_SUPPLY_TYPE_HV_MAINS) {
+                       s2mcs02_charger_test_read(charger);
+                       val->intval = s2mcs02_get_charger_health(charger);
+               } else
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = s2mcs02_get_charger_state(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = s2mcs02_get_charge_current(charger);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               val->intval = s2mcs02_get_input_current(charger);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mcs02_chg_set_property(struct power_supply *psy,
+       enum power_supply_property psp, const union power_supply_propval *val)
+{
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+               pr_info("%s: POWER_SUPPLY_PROP_CHARGING_ENABLED, val->intval = %d.\n", __func__, val->intval);
+               charger->is_charging =
+                       (val->intval == S2MU00X_BAT_CHG_MODE_CHARGING) ? ENABLE : DISABLE;
+               s2mcs02_set_charger_state(charger, charger->is_charging);
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               charger->charging_current = val->intval;
+               s2mcs02_set_charge_current(charger, charger->charging_current);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+               charger->siop_level = val->intval;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               charger->cable_type = val->intval;
+               break;
+       case POWER_SUPPLY_PROP_STATUS:
+       case POWER_SUPPLY_PROP_CURRENT_FULL:
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               charger->input_current = val->intval;
+               s2mcs02_set_input_current(charger, charger->input_current);
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               return -ENODATA;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int s2mcs02_charger_parse_dt(struct device *dev,
+       sec_charger_platform_data_t *pdata)
+{
+       struct device_node *np = of_find_node_by_name(NULL, "s2mcs02-charger");
+       int ret = 0;
+
+       if (!np) {
+               pr_err("%s: np is NULL\n", __func__);
+               return -1;
+       } else {
+               ret = of_get_named_gpio_flags(np, "s2mcs02-charger,irq-gpio",
+                       0, NULL);
+               if (ret < 0) {
+                       pr_err("%s: s2mcs02-charger,irq-gpio is empty\n", __func__);
+                       pdata->irq_gpio = 0;
+               } else {
+                       pdata->irq_gpio = ret;
+                       pr_info("%s: irq-gpio = %d\n", __func__, pdata->irq_gpio);
+               }
+       }
+       return 0;
+}
+
+#if S2MCS02_SYSFS_SUPPORT
+static ssize_t s2mcs02_store_addr(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+       int x;
+
+       if (sscanf(buf, "0x%x\n", &x) == 1)
+               charger->addr = x;
+
+       return count;
+}
+
+static ssize_t s2mcs02_show_addr(struct device *dev,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+
+       return sprintf(buf, "0x%x\n", charger->addr);
+}
+
+static ssize_t s2mcs02_store_size(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+       int x;
+
+       if (sscanf(buf, "%d\n", &x) == 1)
+               charger->size = x;
+
+       return count;
+}
+
+static ssize_t s2mcs02_show_size(struct device *dev,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+
+       return sprintf(buf, "0x%x\n", charger->size);
+}
+
+static ssize_t s2mcs02_store_data(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+       int x;
+
+       if (sscanf(buf, "0x%x", &x) == 1) {
+               u8 data = x;
+               if (s2mcs02_write_reg(charger->i2c, charger->addr, data) < 0) {
+                       dev_info(charger->dev,
+                                       "%s: addr: 0x%x write fail\n", __func__, charger->addr);
+               }
+       }
+       return count;
+}
+
+static ssize_t s2mcs02_show_data(struct device *dev,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct s2mcs02_charger_data *charger = power_supply_get_drvdata(psy);
+       u8 data;
+       int i, count = 0;;
+       if (charger->size == 0)
+               charger->size = 1;
+
+       for (i = 0; i < charger->size; i++) {
+               if (s2mcs02_read_reg(charger->i2c, charger->addr+i, &data) < 0) {
+                       dev_info(charger->dev,
+                                       "%s: read fail\n", __func__);
+                       count += sprintf(buf+count, "addr: 0x%x read fail\n", charger->addr+i);
+                       continue;
+               }
+               count += sprintf(buf+count, "addr: 0x%x, data: 0x%x\n", charger->addr+i, data);
+       }
+       return count;
+}
+
+static DEVICE_ATTR(addr, 0644, s2mcs02_show_addr, s2mcs02_store_addr);
+static DEVICE_ATTR(size, 0644, s2mcs02_show_size, s2mcs02_store_size);
+static DEVICE_ATTR(data, 0644, s2mcs02_show_data, s2mcs02_store_data);
+
+static struct attribute *s2mcs02_attributes[] = {
+       &dev_attr_addr.attr,
+       &dev_attr_size.attr,
+       &dev_attr_data.attr,
+       NULL
+};
+
+static const struct attribute_group s2mcs02_attr_group = {
+       .attrs = s2mcs02_attributes,
+};
+#endif
+
+static int s2mcs02_charger_probe(struct i2c_client *client,
+       const struct i2c_device_id *id)
+{
+       struct device_node *of_node = client->dev.of_node;
+       struct s2mcs02_charger_data *charger;
+       sec_charger_platform_data_t *pdata = client->dev.platform_data;
+       struct power_supply_config psy_cfg = {};
+       int ret = 0;
+
+       pr_info("%s: S2MCS02 Charger Driver Loading\n", __func__);
+
+       charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+       if (!charger)
+               return -ENOMEM;
+
+       if (of_node) {
+               pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+               if (!pdata) {
+                       pr_err("%s: Failed to allocate memory\n", __func__);
+                       return -ENOMEM;
+               }
+               ret = s2mcs02_charger_parse_dt(&client->dev, pdata);
+               if (ret < 0)
+                       goto err_parse_dt;
+       } else {
+               pdata = client->dev.platform_data;
+       }
+
+       charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+       if (!charger) {
+               pr_err("%s: Failed to allocate memory\n", __func__);
+               ret = -ENOMEM;
+               goto err_nomem;
+       }
+
+       mutex_init(&charger->io_lock);
+       charger->dev = &client->dev;
+       ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+               I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK);
+       if (!ret) {
+               ret = i2c_get_functionality(client->adapter);
+               dev_err(charger->dev, "I2C functionality is not supported.\n");
+               ret = -ENOSYS;
+               goto err_i2cfunc_not_support;
+       }
+       charger->i2c = client;
+       charger->pdata = pdata;
+       i2c_set_clientdata(client, charger);
+
+       charger->psy_chg_desc.name                      = "s2mcs02-charger";
+       charger->psy_chg_desc.type                      = POWER_SUPPLY_TYPE_UNKNOWN;
+       charger->psy_chg_desc.get_property      = s2mcs02_chg_get_property;
+       charger->psy_chg_desc.set_property      = s2mcs02_chg_set_property;
+       charger->psy_chg_desc.properties                = s2mcs02_charger_props;
+       charger->psy_chg_desc.num_properties    = ARRAY_SIZE(s2mcs02_charger_props);
+
+       charger->cable_type = POWER_SUPPLY_TYPE_BATTERY;
+
+       s2mcs02_charger_initialize(charger);
+
+       psy_cfg.drv_data = charger;
+       psy_cfg.supplied_to = s2mcs02_supplied_to;
+       psy_cfg.num_supplicants = ARRAY_SIZE(s2mcs02_supplied_to);
+
+       charger->psy_chg = power_supply_register(charger->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->wqueue =
+               create_singlethread_workqueue(dev_name(charger->dev));
+       if (!charger->wqueue) {
+               pr_err("%s: Fail to Create Workqueue\n", __func__);
+               ret = -1;
+               goto err_create_wqueue;
+       }
+
+       if (pdata->irq_gpio) {
+               charger->chg_irq = gpio_to_irq(pdata->irq_gpio);
+
+               ret = request_threaded_irq(charger->chg_irq, NULL,
+                       s2mcs02_irq_handler,
+                       IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+                       "s2mcs02-irq", charger);
+               if (ret < 0) {
+                       pr_err("%s: Failed to Request IRQ(%d)\n", __func__, ret);
+                       goto err_req_irq;
+               }
+
+               ret = enable_irq_wake(charger->chg_irq);
+               if (ret < 0)
+                       pr_err("%s: Failed to Enable Wakeup Source(%d)\n",
+                               __func__, ret);
+       }
+#if S2MCS02_SYSFS_SUPPORT
+       ret = sysfs_create_group(&charger->psy_chg.dev->kobj, &s2mcs02_attr_group);
+       if (ret) {
+               dev_info(&client->dev,
+                       "%s: sysfs_create_group failed\n", __func__);
+       }
+#endif
+
+       s2mcs02_charger_test_read(charger);
+       s2mcs02_get_charge_current(charger);
+
+       pr_info("%s: S2MCS02 Charger Driver Loaded\n", __func__);
+
+       return 0;
+
+err_req_irq:
+err_create_wqueue:
+       power_supply_unregister(charger->psy_chg);
+err_power_supply_register:
+err_i2cfunc_not_support:
+       mutex_destroy(&charger->io_lock);
+       kfree(charger);
+err_nomem:
+err_parse_dt:
+       kfree(pdata);
+
+       return ret;
+}
+
+static int s2mcs02_charger_remove(struct i2c_client *client)
+{
+       struct s2mcs02_charger_data *charger = i2c_get_clientdata(client);
+
+       free_irq(charger->chg_irq, NULL);
+       destroy_workqueue(charger->wqueue);
+       power_supply_unregister(charger->psy_chg);
+       mutex_destroy(&charger->io_lock);
+       kfree(charger->pdata);
+       kfree(charger);
+
+       return 0;
+}
+
+static void s2mcs02_charger_shutdown(struct i2c_client *client)
+{
+}
+
+static const struct i2c_device_id s2mcs02_charger_id_table[] = {
+       {"s2mcs02-charger", 0},
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, s2mcs02_id_table);
+
+#if defined(CONFIG_PM)
+static int s2mcs02_charger_suspend(struct device *dev)
+{
+       struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+       struct s2mcs02_charger_data *charger = i2c_get_clientdata(i2c);
+
+       if (charger->chg_irq) {
+               if (device_may_wakeup(dev))
+                       enable_irq_wake(charger->chg_irq);
+               disable_irq(charger->chg_irq);
+       }
+
+       return 0;
+}
+
+static int s2mcs02_charger_resume(struct device *dev)
+{
+       struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+       struct s2mcs02_charger_data *charger = i2c_get_clientdata(i2c);
+
+       if (charger->chg_irq) {
+               if (device_may_wakeup(dev))
+                       disable_irq_wake(charger->chg_irq);
+               enable_irq(charger->chg_irq);
+       }
+
+       return 0;
+}
+#else
+#define s2mcs02_charger_suspend                NULL
+#define s2mcs02_charger_resume         NULL
+#endif /* CONFIG_PM */
+
+const struct dev_pm_ops s2mcs02_pm = {
+       .suspend = s2mcs02_charger_suspend,
+       .resume = s2mcs02_charger_resume,
+};
+
+#ifdef CONFIG_OF
+static struct of_device_id s2mcs02_charger_match_table[] = {
+       {.compatible = "samsung,s2mcs02-charger"},
+       {},
+};
+#else
+#define s2mcs02_charger_match_table NULL
+#endif
+
+static struct i2c_driver s2mcs02_charger_driver = {
+       .driver = {
+               .name   = "s2mcs02-charger",
+               .owner  = THIS_MODULE,
+               .of_match_table = s2mcs02_charger_match_table,
+#if defined(CONFIG_PM)
+               .pm = &s2mcs02_pm,
+#endif /* CONFIG_PM */
+       },
+       .probe          = s2mcs02_charger_probe,
+       .remove         = s2mcs02_charger_remove,
+       .shutdown       = s2mcs02_charger_shutdown,
+       .id_table       = s2mcs02_charger_id_table,
+};
+
+static int __init s2mcs02_charger_init(void)
+{
+       pr_info("%s:\n", __func__);
+       return i2c_add_driver(&s2mcs02_charger_driver);
+}
+
+static void __exit s2mcs02_charger_exit(void)
+{
+       pr_info("%s:\n", __func__);
+       i2c_del_driver(&s2mcs02_charger_driver);
+}
+
+module_init(s2mcs02_charger_init);
+module_exit(s2mcs02_charger_exit);
+
+MODULE_DESCRIPTION("Samsung S2MCS02 Charger Driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/s2mcs02_charger.h b/include/linux/power/s2mcs02_charger.h
new file mode 100755 (executable)
index 0000000..0873c5c
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * s2mcs02-charger.h - Header of S2MCS02 small charger IC
+ *
+ * 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.
+ *
+ */
+
+#ifndef __S2MCS02_CHARGER_H__
+#define __S2MCS02_CHARGER_H__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#define MASK(width, shift)      (((0x1 << (width)) - 1) << shift)
+
+/*---------------------------------------- */
+/* Registers */
+/*---------------------------------------- */
+#define S2MCS02_CHG_INT1                       0x1
+#define S2MCS02_CHG_INT2                       0x2
+#define S2MCS02_CHG_INT3                       0x3
+#define S2MCS02_CHG_INT1_MASK          0x4
+#define S2MCS02_CHG_INT2_MASK          0x5
+#define S2MCS02_CHG_INT3_MASK          0x6
+#define S2MCS02_CHG_STATUS0                    0x7
+#define S2MCS02_CHG_STATUS1                    0x8
+#define S2MCS02_CHG_STATUS2                    0x9
+#define S2MCS02_CHG_SC_CTRL0           0x0E
+#define S2MCS02_CHG_SC_CTRL1           0x0F
+#define S2MCS02_CHG_SC_CTRL2           0x4A
+#define S2MCS02_CHG_SC_CTRL3           0x52
+#define S2MCS02_CHG_SC_CTRL4           0x54
+#define S2MCS02_CHG_SC_CTRL5           0x55
+#define S2MCS02_CHG_SC_CTRL6           0x56
+#define S2MCS02_CHG_SC_CTRL7           0x57
+#define S2MCS02_CHG_SC_CTRL8           0x58
+#define S2MCS02_CHG_SC_CTRL9           0x59
+#define S2MCS02_CHG_SC_CTRL10          0x5A
+#define S2MCS02_CHG_SC_CTRL11          0x5B
+#define S2MCS02_CHG_SC_CTRL12          0x5D
+#define S2MCS02_PMIC_ID                                0x5E
+#define S2MCS02_PMIC_REV                       0x5F
+#define S2MCS02_CHG_SC_CTRL13          0x60
+#define S2MCS02_CHG_SC_CTRL14          0x61
+#define S2MCS02_CHG_SC_CTRL15          0x62
+#define S2MCS02_CHG_SC_CTRL16          0x63
+
+/* S2MCS02_CHG_INT1_MASK */
+#define BATCVD_INT_M_SHIFT     4
+#define BATCVD_INT_M_MASK      BIT(BATCVD_INT_M_SHIFT)
+
+/* S2MCS02_CHG_INT2_MASK */
+#define BUCK_ILIM_INT_M_SHIFT  5
+#define BUCK_ILIM_INT_M_MASK   BIT(BUCK_ILIM_INT_M_SHIFT)
+
+/* S2MCS02_CHG_STATUS0                 0x5 */
+#define CHGIN_OK_SHIFT 7
+#define CHGIN_OK_STATUS_MASK   BIT(CHGIN_OK_SHIFT)
+#define CHGINOV_OK_SHIFT 6
+#define CHGINOV_OK_STATUS_MASK BIT(CHGINOV_OK_SHIFT)
+#define CHGINUV_OK_SHIFT 5
+#define CHGINUV_OK_STATUS_MASK BIT(CHGINUV_OK_SHIFT)
+#define BATCVD_SHIFT 4
+#define BATCVD_STATUS_MASK BIT(BATCVD_SHIFT)
+#define BATUV_SHIFT 3
+#define BATUV_STATUS_MASK BIT(BATUV_SHIFT)
+#define TSD_SHIFT 2
+#define TSD_STATUS_MASK BIT(TSD_SHIFT)
+#define CIN2BAT_SHIFT 1
+#define CIN2BAT_STATUS_MASK BIT(CIN2BAT_SHIFT)
+
+/* S2MCS02_CHG_STATUS1                 0x6 */
+#define ENSW_SHIFT 6
+#define ENSW_STATUS_MASK       BIT(ENSW_SHIFT)
+#define BUCK_ILIM_SHIFT 5
+#define BUCK_ILIM_STATUS_MASK  BIT(BUCK_ILIM_SHIFT)
+#define BATOVP_SHIFT 4
+#define BATOVP_STATUS_MASK     BIT(BATOVP_SHIFT)
+#define BATCV_SHIFT 3
+#define BATCV_STATUS_MASK      BIT(BATCV_SHIFT)
+#define ENI2C_EN_SHIFT 1
+#define ENI2C_EN_STATUS_MASK   BIT(ENI2C_EN_SHIFT)
+#define SS_DOWN_SHIFT 0
+#define SS_DOWN_STATUS_MASK    BIT(SS_DOWN_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL0                        0x0A */
+#define EN_TMR_SHIFT                   3
+#define EN_TMR_MASK                            BIT(EN_TMR_SHIFT)
+#define SET_FCHG_TIME_SHIFT            0
+#define SET_FCHG_TIME_WIDTH            3
+#define SET_FCHG_TIME_MASK             MASK(SET_FCHG_TIME_WIDTH, SET_FCHG_TIME_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL1                        0x0B */
+#define WDT_CLR_SHIFT                  4
+#define WDT_CLR_WIDTH                  2
+#define WDT_CLR_MASK                   MASK(WDT_CLR_WIDTH, WDT_CLR_SHIFT)
+#define EN_WDT_SHIFT                   3
+#define EN_WDT_MASK                            BIT(EN_WDT_SHIFT)
+#define SET_WDT_SHIFT                  0
+#define SET_WDT_WIDTH                  3
+#define SET_WDT_MASK                   MASK(SET_WDT_WIDTH, SET_WDT_SHIFT)
+
+/* S2MCS02_CHG_CHG_SC_CTRL2                    0x4A */
+#define EN_TSD_SHIFT                   3
+#define EN_TSD_MASK                            BIT(EN_TSD_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL3                        0x53 */
+#define CHG_CURRENT_SHIFT              7
+#define CHG_CURRENT_MASK               BIT(CHG_CURRENT_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL4                        0x54 */
+#define SS_FOLLOW_TIME_SHIFT           4
+#define SS_FOLLOW_TIME_WIDTH           2
+#define SS_FOLLOW_TIME_MASK                    MASK(SS_FOLLOW_TIME_WIDTH, SS_FOLLOW_TIME_SHIFT)
+#define SS_DOWN_TIME_SHIFT             1
+#define SS_DOWN_TIME_WIDTH             3
+#define SS_DOWN_TIME_MASK                      MASK(SS_DOWN_TIME_WIDTH, SS_DOWN_TIME_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL5                        0x55 */
+#define ENI2C_SHIFT                    7
+#define ENI2C_MASK                             BIT(ENI2C_SHIFT)
+#define SS_DOWN_STEP_SHIFT             1
+#define SS_DOWN_STEP_WIDTH             3
+#define SS_DOWN_STEP_MASK              MASK(SS_DOWN_STEP_WIDTH, SS_DOWN_STEP_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL6                        0x56 */
+#define SET_BAT_UVLO_SHIFT             7
+#define SET_BAT_UVLO_WIDTH             4
+#define SET_BAT_UVLO_MASK              MASK(SET_BAT_UVLO_WIDTH, SET_BAT_UVLO_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL7                0x57 */
+#define OVP_SHIFT              7
+#define OVP_WIDTH              4
+#define OVP_MASK               MASK(OVP_WIDTH, OVP_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL8                        0x58 */
+#define SOFT_DN_DISABLE_SHIFT                  0
+#define SOFT_DN_DISABLE_MASK                   BIT(SOFT_DN_DISABLE_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL9                        0x59 */
+#define CV_FLG_SHIFT           0
+#define CV_FLG_WIDTH           6
+#define CV_FLG_MASK            MASK(CV_FLG_WIDTH, CV_FLG_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL10                       0x5A */
+#define CIN2BAT_OFF_SHIFT              7
+#define CIN2BAT_OFF_MASK               BIT(CIN2BAT_OFF_SHIFT)
+#define BYP_CP_EN_SHIFT                        5
+#define BYP_CP_EN_MASK                 BIT(BYP_CP_EN_SHIFT)
+#define PN_OFF_SHIFT                   1
+#define PN_OFF_MASK                            BIT(PN_OFF_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL11                       0x5B */
+#define BYP_SW_OFF_SHIFT               2
+#define BYP_SW_OFF_MASK                        BIT(BYP_SW_OFF_SHIFT)
+#define BYP_STBY_SHIFT                 1
+#define BYP_STBY_MASK                  BIT(BYP_STBY_SHIFT)
+#define BYP_EN_SHIFT                   0
+#define BYP_EN_MASK                            BIT(BYP_EN_SHIFT)
+
+/* S2MCS02_CHG_SC_CTRL13                       0x60 */
+#define EN_SMALL_CHG_SHIFT             7
+#define EN_SMALL_CHG_MASK              BIT(EN_SMALL_CHG_SHIFT)
+
+
+/*---------------------------------------- */
+/* Bit Fields */
+/*---------------------------------------- */
+
+
+struct sec_charger_platform_data {
+       bool (*chg_gpio_init)(void);
+
+       /* charging current for type (0: not use) */
+       s2mu00x_charging_current_t *charging_current;
+
+       /* wirelss charger */
+       char *wireless_charger_name;
+       int wireless_cc_cv;
+
+       int vbus_ctrl_gpio;
+       int chg_gpio_en;
+       /* float voltage (mV) */
+       int chg_float_voltage;
+       int irq_gpio;
+       int chg_irq;
+       unsigned long chg_irq_attr;
+
+       /* otg_en setting */
+       //int otg_en;
+
+       /* OVP/UVLO check */
+       //sec_battery_ovp_uvlo_t ovp_uvlo_check_type;
+       /* 1st full check */
+       //sec_battery_full_charged_t full_check_type;
+       /* 2nd full check */
+       //sec_battery_full_charged_t full_check_type_2nd;
+
+       //sec_charger_functions_t chg_functions_setting;
+};
+
+#define sec_charger_platform_data_t \
+       struct sec_charger_platform_data
+
+struct s2mcs02_charger_data {
+       struct device           *dev;
+       struct i2c_client       *i2c;
+       struct mutex            io_lock;
+
+       sec_charger_platform_data_t *pdata;
+
+       struct power_supply     *psy_chg;
+       struct power_supply_desc psy_chg_desc;
+       struct workqueue_struct *wqueue;
+       struct delayed_work     isr_work;
+
+       struct pinctrl *i2c_pinctrl;
+       struct pinctrl_state *i2c_gpio_state_active;
+       struct pinctrl_state *i2c_gpio_state_suspend;
+
+       unsigned int siop_level;
+       unsigned int chg_irq;
+       unsigned int is_charging;
+       unsigned int charging_type;
+       unsigned int cable_type;
+       unsigned int charging_current_max;
+       unsigned int charging_current;
+       unsigned int input_current;
+
+       u8 addr;
+       int size;
+};
+#endif /* __S2MCS02_CHARGER_H__ */