From 13895fb7ff68a6fc6e009b99f7c7abfc2a8d04c7 Mon Sep 17 00:00:00 2001 From: Keunho Hwang Date: Mon, 11 Feb 2019 15:18:10 +0900 Subject: [PATCH] [RAMEN9610-12039][9610] drivers: power: add s2mcs02 charger for code sync Add s2mcs02 charger driver for code sync. Change-Id: Ifb977b368d96262f85666eabbb401f83e6a54b7c Signed-off-by: Keunho Hwang --- drivers/power/supply/Kconfig | 14 + drivers/power/supply/Makefile | 1 + drivers/power/supply/s2mcs02_charger.c | 700 +++++++++++++++++++++++++ include/linux/power/s2mcs02_charger.h | 253 +++++++++ 4 files changed, 968 insertions(+) create mode 100755 drivers/power/supply/s2mcs02_charger.c create mode 100755 include/linux/power/s2mcs02_charger.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 13f7fdbe7237..3c60c0c30f61 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -648,4 +648,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 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 04ea023b3a46..78b70489714c 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -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 index 000000000000..072b44bf843f --- /dev/null +++ b/drivers/power/supply/s2mcs02_charger.c @@ -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 +#include +#include +#include + +#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, ®_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, ®_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, ®_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, ®_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, ®_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 index 000000000000..0873c5cfa168 --- /dev/null +++ b/include/linux/power/s2mcs02_charger.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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__ */ -- 2.20.1