diff --git a/arch/arm64/boot/dts/qcom/sdm450-samsung-a6plte-r4.dts b/arch/arm64/boot/dts/qcom/sdm450-samsung-a6plte-r4.dts
index 130f1ff50d44aa..f0fac06cc0a4dc 100644
--- a/arch/arm64/boot/dts/qcom/sdm450-samsung-a6plte-r4.dts
+++ b/arch/arm64/boot/dts/qcom/sdm450-samsung-a6plte-r4.dts
@@ -22,12 +22,12 @@
i2c2 = &i2c_2;
i2c3 = &i2c_3;
i2c5 = &i2c_5;
- i2c8 = &i2c_8;
i2c11 = &i2c_gpio_1;
i2c12 = &i2c_gpio_2;
i2c13 = &i2c_gpio_3;
i2c14 = &i2c_gpio_4;
i2c15 = &i2c_gpio_5;
+ i2c16 = &i2c_gpio_6;
i2c20 = &cci_i2c0;
i2c21 = &cci_i2c1;
serial0 = &uart_0;
@@ -116,6 +116,15 @@
#size-cells = <0>;
};
+ i2c_gpio_6: i2c-gpio-6 {
+ compatible = "i2c-gpio";
+ sda-gpios = <&tlmm 98 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&tlmm 99 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
reserved-memory {
tzapp_mem@82800000 {
reg = <0x0 0x82800000 0x0 0x2800000>;
@@ -482,7 +491,7 @@
};
};
-&i2c_8 {
+&i2c_gpio_6 {
status = "okay";
usb_extcon: extcon@25 {
@@ -496,6 +505,21 @@
pmic@49 {
compatible = "siliconmitus,sm5708";
reg = <0x49>;
+ interrupt-extended = <&tlmm 60 IRQ_TYPE_EDGE_FALLING>;
+ sm5708,irq-gpio = <&tlmm 60 0x00>;
+ pinctrl-0 = <&charger_int_default>;
+ pinctrl-names = "default";
+
+ charger: sm5708-charger {
+ compatible = "siliconmitus,sm5708-charger";
+ float-voltage = <4350>;
+ input-current = <1500>;
+ charge-current = <1800>;
+ extcon = <&usb_extcon>;
+ pinctrl-0 = <&charger_en_default>;
+ pinctrl-names = "default";
+ enable-gpios = <&tlmm 34 GPIO_ACTIVE_HIGH>;
+ };
};
};
@@ -520,6 +544,15 @@
};
};
+&i2c_gpio_3 {
+ fuelgauge@71 {
+ compatible = "sm5708-battery,a6plte";
+ reg = <0x71>;
+ interrupts-extended = <&tlmm 62 IRQ_TYPE_LEVEL_HIGH>;
+ power-supplies = <&charger>;
+ };
+};
+
&i2c_gpio_4 {
accelerometer@6b {
compatible = "st,lsm6dsl";
@@ -762,6 +795,13 @@
bias-disable;
};
+ charger_en_default: chg-en-pins {
+ pins = "gpio34";
+ function = "gpio";
+ bias-pull-down;
+ output-high;
+ };
+
fg_int_default: fg-int-default-state {
pins = "gpio62";
function = "gpio";
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6fb3768e3d71cb..034b070dd9720b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1421,6 +1421,17 @@ config MFD_SKY81452
This driver can also be built as a module. If so, the module
will be called sky81452.
+config MFD_SM5708
+ bool "Siliconmitus SM5708 IFPM Support"
+ depends on I2C=y
+ select MFD_CORE
+ help
+ Say yes here to support for Siliconmitus SM5708.
+ This is a Power Management IC with USBLDO, RGB(SVC led), Dual Flash, Charger
+ controls on chip.
+ This driver provides common support for accessing the device;
+ additional drivers
+
config MFD_SC27XX_PMIC
tristate "Spreadtrum SC27xx PMICs"
depends on ARCH_SPRD || COMPILE_TEST
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 79495f9f3457b8..995625ed339ae2 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
obj-$(CONFIG_MFD_88PM886_PMIC) += 88pm886.o
obj-$(CONFIG_MFD_ACT8945A) += act8945a.o
obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_SM5708) += sm5708.o
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
diff --git a/drivers/mfd/sm5708.c b/drivers/mfd/sm5708.c
new file mode 100644
index 00000000000000..153c09e552d646
--- /dev/null
+++ b/drivers/mfd/sm5708.c
@@ -0,0 +1,261 @@
+/*
+ * sm5708.c - mfd core driver for the SM5708
+ *
+ * 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 .
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#define MFD_DEV_NAME "sm5708-mfd"
+
+#define SM5708_IRQ(name, regnum) \
+ REGMAP_IRQ_REG(name##_IRQ, regnum - 1, name##_STATUS##regnum)
+
+static const struct regmap_irq sm5708_irqs[] = {
+ SM5708_IRQ(SM5708_VBUSPOK, 1),
+ SM5708_IRQ(SM5708_VBUSUVLO, 1),
+ SM5708_IRQ(SM5708_VBUSOVP, 1),
+ SM5708_IRQ(SM5708_VBUSLIMIT, 1),
+
+ SM5708_IRQ(SM5708_AICL, 2),
+ SM5708_IRQ(SM5708_BATOVP, 2),
+ SM5708_IRQ(SM5708_NOBAT, 2),
+ SM5708_IRQ(SM5708_CHGON, 2),
+ SM5708_IRQ(SM5708_Q4FULLON, 2),
+ SM5708_IRQ(SM5708_TOPOFF, 2),
+ SM5708_IRQ(SM5708_DONE, 2),
+ SM5708_IRQ(SM5708_WDTMROFF, 2),
+
+ SM5708_IRQ(SM5708_THEMREG, 3),
+ SM5708_IRQ(SM5708_THEMSHDN, 3),
+ SM5708_IRQ(SM5708_OTGFAIL, 3),
+ SM5708_IRQ(SM5708_DISLIMIT, 3),
+ SM5708_IRQ(SM5708_PRETMROFF, 3),
+ SM5708_IRQ(SM5708_FASTTMROFF, 3),
+ SM5708_IRQ(SM5708_LOWBATT, 3),
+ SM5708_IRQ(SM5708_nENQ4, 3),
+
+ SM5708_IRQ(SM5708_FLED1SHORT, 4),
+ SM5708_IRQ(SM5708_FLED1OPEN, 4),
+ SM5708_IRQ(SM5708_FLED2SHORT, 4),
+ SM5708_IRQ(SM5708_FLED2OPEN, 4),
+ SM5708_IRQ(SM5708_BOOSTPOK_NG, 4),
+ SM5708_IRQ(SM5708_BOOSTPOK, 4),
+ SM5708_IRQ(SM5708_ABSTMR1OFF, 4),
+ SM5708_IRQ(SM5708_SBPS, 4),
+};
+
+static bool sm5708_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SM5708_REG_INT1 ... SM5708_REG_INT4:
+ case SM5708_REG_STATUS1 ... SM5708_REG_STATUS4:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config sm5708_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = sm5708_volatile_reg,
+ .max_register = SM5708_REG_MAX,
+};
+
+void sm5708_request_mode(struct device *dev, u8 mode_mask, bool enable) {
+ struct sm5708_chip *chip = (struct sm5708_chip*) dev_get_drvdata(dev);
+ u8 new_mode;
+ int boost_output = SM5708_BST_OUT_4v5;
+ int otg_current = SM5708_OTG_CURRENT_500mA;
+ int op_mode = SM5708_MODE_CHG_ON;
+
+ mutex_lock(&chip->mode_mutex);
+
+ if (enable)
+ new_mode = chip->requested_mode | mode_mask;
+ else
+ new_mode = chip->requested_mode & ~mode_mask;
+
+ if (new_mode == chip->requested_mode)
+ goto unlock;
+
+ switch(new_mode & ~SM5708_TORCH) {
+ case SM5708_FLASH:
+ case SM5708_FLASH | SM5708_CHARGE:
+ op_mode = SM5708_MODE_FLASH_BOOST;
+ break;
+ case SM5708_FLASH | SM5708_OTG:
+ op_mode = SM5708_MODE_FLASH_BOOST;
+ otg_current = SM5708_OTG_CURRENT_900mA;
+ break;
+ case SM5708_OTG:
+ otg_current = SM5708_OTG_CURRENT_900mA;
+ boost_output = SM5708_BST_OUT_5v1;
+ op_mode = SM5708_MODE_USB_OTG;
+ break;
+ default:
+ if (new_mode == SM5708_TORCH)
+ op_mode = SM5708_MODE_FLASH_BOOST;
+ }
+
+ if (chip->op_mode == SM5708_MODE_USB_OTG &&
+ op_mode == SM5708_MODE_CHG_ON) {
+ regmap_update_bits(chip->regmap, SM5708_REG_CNTL, 0x7,
+ SM5708_MODE_SUSPEND);
+ msleep(80);
+ }
+
+ regmap_update_bits(chip->regmap, SM5708_REG_FLEDCNTL6, 0xf, boost_output);
+ regmap_update_bits(chip->regmap, SM5708_REG_CHGCNTL5, 0x3 << 2, otg_current << 2);
+ regmap_update_bits(chip->regmap, SM5708_REG_CNTL, 0x7, op_mode);
+
+ chip->op_mode = op_mode;
+ chip->requested_mode = new_mode;
+unlock:
+ mutex_unlock(&chip->mode_mutex);
+}
+EXPORT_SYMBOL(sm5708_request_mode);
+
+static const struct regmap_irq_chip sm5708_irq_chip = {
+ .name = MFD_DEV_NAME,
+ .status_base = SM5708_REG_INT1,
+ .mask_base = SM5708_REG_INTMSK1,
+ .num_regs = 4,
+ .irqs = sm5708_irqs,
+ .num_irqs = ARRAY_SIZE(sm5708_irqs),
+};
+
+static struct mfd_cell sm5708_devs[] = {
+ { .name = "sm5708-charger", .of_compatible = "siliconmitus,sm5708-charger" },
+ { .name = "sm5708-rgb-leds", .of_compatible = "siliconmitus,sm5708-leds" },
+ { .name = "sm5708-fled", .of_compatible = "siliconmitus,sm5708-fled" },
+};
+
+static int sm5708_i2c_probe(struct i2c_client *i2c)
+{
+ struct device_node *np = i2c->dev.of_node;
+ struct sm5708_chip *chip;
+ int ret = 0;
+
+ if (!np)
+ return -EINVAL;
+
+ chip = devm_kzalloc(&i2c->dev, sizeof(struct sm5708_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ /* get irq and related info from device tree */
+ int irq_gpio = of_get_named_gpio(np, "sm5708,irq-gpio", 0);
+ chip->irq = gpio_to_irq(irq_gpio);
+
+ i2c_set_clientdata(i2c, chip);
+ chip->dev = &i2c->dev;
+ chip->i2c = i2c;
+ /* commented out, i2c->irq is 0 */
+ //chip->irq = i2c->irq;
+ mutex_init(&chip->mode_mutex);
+
+ chip->regmap = devm_regmap_init_i2c(i2c,
+ &sm5708_regmap_config);
+
+ ret = regmap_add_irq_chip(chip->regmap, chip->irq,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_SUSPEND,
+ -1, &sm5708_irq_chip, &chip->irq_data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to add IRQ chip: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_mfd_add_devices(chip->dev, -1, sm5708_devs,
+ ARRAY_SIZE(sm5708_devs), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+
+ device_init_wakeup(chip->dev, 0);
+
+ return 0;
+}
+
+static int sm5708_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct sm5708_chip *chip = i2c_get_clientdata(i2c);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(chip->irq);
+
+ disable_irq(chip->irq);
+
+ return 0;
+}
+
+static int sm5708_resume(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct sm5708_chip *chip = i2c_get_clientdata(i2c);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(chip->irq);
+
+ enable_irq(chip->irq);
+
+ return 0;
+}
+
+static void sm5708_shutdown(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct sm5708_chip *chip = i2c_get_clientdata(i2c);
+
+ regmap_update_bits(chip->regmap, SM5708_REG_CNTL, 0x7,
+ SM5708_MODE_CHG_ON);
+}
+
+static SIMPLE_DEV_PM_OPS(sm5708_pm, sm5708_suspend, sm5708_resume);
+
+static const struct of_device_id sm5708_i2c_dt_ids[] = {
+ { .compatible = "siliconmitus,sm5708" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sm5708_i2c_dt_ids);
+
+static struct i2c_driver sm5708_i2c_driver = {
+ .driver = {
+ .name = MFD_DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &sm5708_pm,
+ .of_match_table = sm5708_i2c_dt_ids,
+ .shutdown = sm5708_shutdown,
+ },
+ .probe = sm5708_i2c_probe,
+};
+
+module_i2c_driver(sm5708_i2c_driver);
+
+MODULE_DESCRIPTION("SM5708 multi-function core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 5863b5efaba3dd..cb81699a274125 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -458,6 +458,28 @@ config BATTERY_MAX1721X
Say Y here to enable support for the MAX17211/MAX17215 standalone
battery gas-gauge.
+config FUEL_GAUGE_SM5708
+ tristate "Siliconmitus SM5708 Fuel Gauge support"
+ depends on I2C
+ help
+ SM5708 is fuel-gauge systems for lithium-ion (Li+) batteries
+ in handheld and portable equipment. The SM5708 is configured
+ to operate with a single lithium cell
+
+config CHARGER_SM5708
+ tristate "Siliconmitus SM5708 Charger support"
+ depends on MFD_SM5708
+ help
+ Say Y here to enable support for the SM5708 charger
+
+config FUEL_GAUGE_SM5708
+ tristate "Siliconmitus SM5708 Fuel Gauge support"
+ depends on I2C
+ help
+ SM5708 is fuel-gauge systems for lithium-ion (Li+) batteries
+ in handheld and portable equipment. The SM5708 is configured
+ to operate with a single lithium cell
+
config BATTERY_TWL4030_MADC
tristate "TWL4030 MADC battery driver"
depends on TWL4030_MADC
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 89b1959c8c8d3c..1a70872f184ad6 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -124,3 +124,5 @@ obj-$(CONFIG_BATTERY_QCOM_FG) += qcom_fg.o
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_pmi8998_charger.o
obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o
+obj-$(CONFIG_CHARGER_SM5708) += sm5708_charger.o
+obj-$(CONFIG_FUEL_GAUGE_SM5708) += sm5708_fuelgauge.o
diff --git a/drivers/power/supply/sm5708_charger.c b/drivers/power/supply/sm5708_charger.c
new file mode 100644
index 00000000000000..2eee657ea5e986
--- /dev/null
+++ b/drivers/power/supply/sm5708_charger.c
@@ -0,0 +1,546 @@
+/*
+ * sm5708_charger.c - SM5708 Charger driver
+ *
+ * Copyright (C) 2015 Silicon Mitus,
+ *
+ * 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
+
+enum sm5708_boost_limit {
+ SM5708_CHG_BST_IQ3LIMIT_2_0A,
+ SM5708_CHG_BST_IQ3LIMIT_2_8A,
+ SM5708_CHG_BST_IQ3LIMIT_3_5A,
+ SM5708_CHG_BST_IQ3LIMIT_4_0A,
+};
+
+enum sm5708_manual_reset_time {
+ SM5708_MANUAL_RESET_TIME_7s = 0x1,
+ SM5708_MANUAL_RESET_TIME_8s,
+ SM5708_MANUAL_RESET_TIME_9s,
+};
+
+enum sm5708_watchdog_reset_time {
+ SM5708_WATCHDOG_RESET_TIME_30s,
+ SM5708_WATCHDOG_RESET_TIME_60s,
+ SM5708_WATCHDOG_RESET_TIME_90s,
+ SM5708_WATCHDOG_RESET_TIME_120s,
+};
+
+enum sm5708_topoff_timer {
+ SM5708_TOPOFF_TIMER_10m,
+ SM5708_TOPOFF_TIMER_20m,
+ SM5708_TOPOFF_TIMER_30m,
+ SM5708_TOPOFF_TIMER_45m,
+};
+
+enum sm5708_bob_freq {
+ SM5708_BUCK_BOOST_FREQ_3MHz = 0x0,
+ SM5708_BUCK_BOOST_FREQ_2_4MHz = 0x1,
+ SM5708_BUCK_BOOST_FREQ_1_5MHz = 0x2,
+ SM5708_BUCK_BOOST_FREQ_1_8MHz = 0x3,
+};
+
+
+struct sm5708_charger {
+ struct device *dev;
+ struct device *parent;
+ struct regmap *regmap;
+ struct power_supply *psy;
+ struct extcon_dev *extcon;
+ struct notifier_block extcon_nb;
+
+ struct work_struct otg_fail_work;
+ struct delayed_work otg_work;
+
+ /* for charging operation handling */
+ bool charging_enabled;
+ int health;
+
+ struct gpio_desc *enable_gpio;
+
+ unsigned int max_input_current;
+ unsigned int max_charge_current;
+ unsigned int max_float_voltage;
+};
+
+static enum power_supply_property sm5708_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+};
+
+static int sm5708_get_param(struct sm5708_charger *charger, int *out,
+ bool micro, u32 reg, u8 mask, u8 shift,
+ u32 min, u32 max, u32 step)
+{
+ int ret, regval;
+ ret = regmap_read(charger->regmap, reg, ®val);
+ if (ret < 0)
+ return ret;
+ *out = (((regval >> shift) & mask) * step + min);
+ if (micro)
+ *out *= 1000;
+ return 0;
+}
+
+static int sm5708_set_param(struct sm5708_charger *charger, unsigned int val,
+ u32 reg, u8 mask, u8 shift,
+ u32 min, u32 max, u32 step)
+{
+ if (val < min || val > max)
+ return -EINVAL;
+ val = (val - min) / step;
+ return regmap_update_bits(charger->regmap, reg,
+ mask << shift, val << shift);
+}
+
+#define GETTER_SETTER(param, reg, min, max, step, mask, shift) \
+ static int __maybe_unused \
+ sm5708_get_##param(struct sm5708_charger *charger, int *out, bool uv) \
+ { \
+ return sm5708_get_param(charger, out, uv, reg, mask, \
+ shift, min, max, step); \
+ } \
+ static int __maybe_unused \
+ sm5708_set_##param(struct sm5708_charger *charger, int value) \
+ { \
+ return sm5708_set_param(charger, value, reg, mask, \
+ shift, min, max, step); \
+ }
+
+GETTER_SETTER(input_current, SM5708_REG_VBUSCNTL, 100, 3275, 25, 0xff, 0)
+GETTER_SETTER(float_voltage, SM5708_REG_CHGCNTL3, 3990, 4350, 10, 0x3f, 0)
+GETTER_SETTER(charge_current, SM5708_REG_CHGCNTL2, 100, 3250, 50, 0x3f, 0)
+GETTER_SETTER(topoff_current, SM5708_REG_CHGCNTL4, 100, 475, 25, 0xf, 0)
+GETTER_SETTER(aicl_threshold, SM5708_REG_CHGCNTL6, 4500, 4800, 100, 0x3, 6)
+
+static unsigned char sm5708_get_status(struct sm5708_charger *charger,
+ unsigned char number) // Register number 1-4
+{
+ unsigned int reg_val;
+ int ret;
+
+ if (number < 1 || number > 4)
+ return 0;
+
+ ret = regmap_read(charger->regmap, SM5708_REG_STATUS1 + number - 1, ®_val);
+ if (ret < 0)
+ return 0;
+
+ return reg_val;
+}
+
+static int sm5708_set_topoff_timer(struct sm5708_charger *charger,
+ enum sm5708_topoff_timer topoff_timer)
+{
+ return regmap_update_bits(charger->regmap,
+ SM5708_REG_CHGCNTL7, 0x3 << 3, topoff_timer << 3);
+}
+
+static int sm5708_set_autostop(struct sm5708_charger *charger,
+ bool enable)
+{
+ return regmap_update_bits(charger->regmap, SM5708_REG_CHGCNTL3, 0x1 << 6, enable << 6);
+}
+
+static int sm5708_select_freq(struct sm5708_charger *charger,
+ enum sm5708_bob_freq freq_index)
+{
+ return regmap_update_bits(charger->regmap, SM5708_REG_CHGCNTL4, 0x3 << 4, freq_index << 4);
+}
+
+static int sm5708_set_aicl(struct sm5708_charger *charger, bool enable)
+{
+ return regmap_update_bits(charger->regmap, SM5708_REG_CHGCNTL6, 0x1 << 5, enable << 5);
+}
+
+static int sm5708_set_boost_limit(struct sm5708_charger *charger,
+ enum sm5708_boost_limit index)
+{
+ return regmap_update_bits(charger->regmap, SM5708_REG_CHGCNTL5, 0x3, index);
+}
+
+static int sm5708_set_autoset(struct sm5708_charger *charger, bool enable)
+{
+ return regmap_update_bits(charger->regmap, SM5708_REG_CHGCNTL6, 0x1 << 1, enable << 1);
+}
+
+static void sm5708_set_charging_enabled(struct sm5708_charger *charger, int enabled)
+{
+ charger->charging_enabled = enabled;
+
+ if (charger->enable_gpio)
+ gpiod_set_value(charger->enable_gpio, !enabled);
+
+ sm5708_request_mode(charger->parent, SM5708_CHARGE, enabled);
+}
+
+static int sm5708_get_charger_status(struct sm5708_charger *charger)
+{
+ unsigned char status2 = sm5708_get_status(charger, 2);
+
+ if (status2 & (SM5708_TOPOFF_STATUS2 | SM5708_DONE_STATUS2))
+ return POWER_SUPPLY_STATUS_FULL;
+ else if (status2 & SM5708_CHGON_STATUS2)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else if (sm5708_get_status(charger, 1) & SM5708_VBUSPOK_STATUS1 &&
+ !charger->charging_enabled)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static int sm5708_get_charge_type(struct sm5708_charger *charger)
+{
+ int temp;
+
+ switch (sm5708_get_charger_status(charger)) {
+ case POWER_SUPPLY_STATUS_FULL:
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ sm5708_get_input_current(charger, &temp, false);
+ if (temp > 500)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ return POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ default:
+ break;
+ }
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int sm5708_get_health(struct sm5708_charger *charger)
+{
+ unsigned char status1 = sm5708_get_status(charger, 1);
+
+ if (status1 & SM5708_VBUSPOK_STATUS1)
+ return POWER_SUPPLY_HEALTH_GOOD;
+ else if (status1 & SM5708_VBUSOVP_STATUS1)
+ return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (status1 & SM5708_VBUSUVLO_STATUS1)
+ return POWER_SUPPLY_HEALTH_DEAD;
+ else
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+}
+
+
+static int sm5708_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sm5708_charger *charger = power_supply_get_drvdata(psy);
+
+ switch(psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(sm5708_get_status(charger, 1) & SM5708_VBUSPOK_STATUS1);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !(sm5708_get_status(charger, 2) & SM5708_NOBAT_STATUS2);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = sm5708_get_charger_status(charger);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = sm5708_get_charge_type(charger);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = sm5708_get_health(charger);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = charger->max_charge_current * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = charger->max_input_current * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return sm5708_get_charge_current(charger, &val->intval, true);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return sm5708_get_float_voltage(charger, &val->intval, true);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return sm5708_get_input_current(charger, &val->intval, true);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sm5708_set_property(struct power_supply *psy,
+ enum power_supply_property psp, const union power_supply_propval *val)
+{
+ struct sm5708_charger *charger = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ sm5708_set_charging_enabled(charger, val->intval == POWER_SUPPLY_STATUS_CHARGING);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if ((val->intval / 1000) > charger->max_input_current)
+ return -EINVAL;
+ sm5708_set_input_current(charger, val->intval / 1000);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ if ((val->intval / 1000) > charger->max_charge_current)
+ return -EINVAL;
+ sm5708_set_charge_current(charger, val->intval / 1000);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if ((val->intval / 1000) > charger->max_float_voltage)
+ return -EINVAL;
+ sm5708_set_float_voltage(charger, val->intval / 1000);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static irqreturn_t sm5708_done_irq(int irq, void *data)
+{
+ struct sm5708_charger *charger = data;
+
+ power_supply_changed(charger->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sm5708_vbus_irq(int irq, void *data)
+{
+ struct sm5708_charger *charger = data;
+ int health = sm5708_get_health(charger);
+
+ if (charger->health != health) {
+ charger->health = health;
+ power_supply_changed(charger->psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sm5708_otgfail_irq(int irq, void *data)
+{
+ struct sm5708_charger *charger = data;
+
+ if (sm5708_get_status(charger, 3) & SM5708_OTGFAIL_STATUS3)
+ schedule_work(&charger->otg_fail_work);
+
+ return IRQ_HANDLED;
+}
+
+static void sm5708_otg_fail_work(struct work_struct *work)
+{
+ struct sm5708_charger *charger = container_of(work,
+ struct sm5708_charger, otg_fail_work);
+
+ cancel_delayed_work_sync(&charger->otg_work);
+ sm5708_request_mode(charger->parent, SM5708_OTG, false);
+}
+
+static void sm5708_otg_work(struct work_struct *work)
+{
+ struct sm5708_charger *charger = container_of(work,
+ struct sm5708_charger, otg_work.work);
+
+ if (sm5708_get_status(charger, 1) & SM5708_VBUSPOK_STATUS1) {
+ sm5708_set_input_current(charger, charger->max_input_current);
+ sm5708_set_charge_current(charger, charger->max_charge_current);
+ } else {
+ sm5708_request_mode(charger->parent, SM5708_CHARGE, false);
+ sm5708_request_mode(charger->parent, SM5708_OTG, true);
+ }
+}
+
+static void sm5708_charger_initialize(struct sm5708_charger *charger)
+{
+ sm5708_set_topoff_current(charger, 300);
+ sm5708_set_topoff_timer(charger, SM5708_TOPOFF_TIMER_45m);
+ sm5708_set_autostop(charger, 1);
+ sm5708_set_float_voltage(charger, charger->max_float_voltage);
+ sm5708_set_aicl_threshold(charger, 4500);
+ sm5708_set_aicl(charger, 1);
+ sm5708_set_autoset(charger, 0);
+ sm5708_set_boost_limit(charger, SM5708_CHG_BST_IQ3LIMIT_3_5A);
+ sm5708_select_freq(charger, SM5708_BUCK_BOOST_FREQ_1_5MHz);
+}
+
+static int sm5708_extcon_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct sm5708_charger *charger = container_of(nb,
+ struct sm5708_charger, extcon_nb);
+
+ if (extcon_get_state(charger->extcon, EXTCON_USB_HOST)) {
+ schedule_delayed_work(&charger->otg_work, 200);
+ } else if (extcon_get_state(charger->extcon, EXTCON_USB)) {
+ cancel_delayed_work_sync(&charger->otg_work);
+ sm5708_request_mode(charger->parent, SM5708_OTG, false);
+ sm5708_request_mode(charger->parent, SM5708_CHARGE, false);
+ } else {
+ cancel_delayed_work_sync(&charger->otg_work);
+ sm5708_set_input_current(charger, 500);
+ sm5708_set_charge_current(charger, 500);
+ sm5708_request_mode(charger->parent, SM5708_OTG, false);
+ sm5708_request_mode(charger->parent, SM5708_CHARGE, false);
+ }
+
+ if (extcon_get_state(charger->extcon, EXTCON_CHG_USB_SDP)) {
+ sm5708_set_input_current(charger, 500);
+ sm5708_set_charge_current(charger, 500);
+ } else if (extcon_get_state(charger->extcon, EXTCON_CHG_USB_DCP)) {
+ sm5708_set_input_current(charger, charger->max_input_current);
+ sm5708_set_charge_current(charger, charger->max_charge_current);
+ }
+
+ return NOTIFY_DONE;
+
+}
+
+static int sm5708_parse_dt(struct sm5708_charger *charger)
+{
+ struct device_node *np = charger->dev->of_node;
+ int ret;
+
+ if (of_property_read_u32(np, "float-voltage", &charger->max_float_voltage))
+ charger->max_float_voltage = 4200;
+ if (of_property_read_u32(np, "input-current", &charger->max_input_current))
+ charger->max_input_current = 1500;
+ if (of_property_read_u32(np, "charge-current", &charger->max_charge_current))
+ charger->max_charge_current = 1800;
+
+ charger->enable_gpio = devm_gpiod_get(charger->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(charger->enable_gpio)) {
+ ret = PTR_ERR(charger->enable_gpio);
+ dev_err(charger->dev, "Failed to get enable-gpios: %d\n", ret);
+ return ret;
+ }
+
+ if (of_property_read_bool(np, "extcon")) {
+ charger->extcon = extcon_get_edev_by_phandle(charger->dev, 0);
+ if (IS_ERR(charger->extcon))
+ return PTR_ERR(charger->extcon);
+
+ charger->extcon_nb.notifier_call = sm5708_extcon_notifier;
+
+ ret = devm_extcon_register_notifier_all(charger->dev, charger->extcon,
+ &charger->extcon_nb);
+
+ if (ret < 0)
+ return ret;
+
+ sm5708_extcon_notifier(&charger->extcon_nb, 0, NULL);
+ }
+
+ return 0;
+}
+
+static int sm5708_request_irq(struct sm5708_charger *charger, int irq, irq_handler_t handler)
+{
+ int ret;
+ struct sm5708_chip *chip = (struct sm5708_chip*) dev_get_drvdata(charger->parent);
+ int virq = regmap_irq_get_virq(chip->irq_data, irq);
+ if (virq <= 0)
+ return -EINVAL;
+
+ ret = devm_request_threaded_irq(charger->dev, virq, NULL,
+ handler, IRQF_ONESHOT, "sm5708-charger", charger);
+ if (ret) {
+ dev_err(charger->dev, "failed to request irq %d: %d\n", irq, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static const struct power_supply_desc sm5708_charger_desc = {
+ .name = "sm5708-charger",
+ .type = POWER_SUPPLY_TYPE_USB_DCP,
+ .get_property = sm5708_get_property,
+ .set_property = sm5708_set_property,
+ .properties = sm5708_properties,
+ .num_properties = ARRAY_SIZE(sm5708_properties),
+};
+
+static char *battery_supplied_to[] = {
+ "sm5708-battery",
+};
+
+static int sm5708_charger_probe(struct platform_device *pdev)
+{
+ struct sm5708_charger *charger;
+ struct power_supply_config psy_config = { 0 };
+ int ret = 0;
+
+ charger = devm_kzalloc(&pdev->dev, sizeof(struct sm5708_charger), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(charger))
+ return -ENOMEM;
+
+ charger->dev = &pdev->dev;
+ charger->parent = charger->dev->parent;
+ charger->regmap = dev_get_regmap(charger->dev->parent, NULL);
+
+ INIT_DELAYED_WORK(&charger->otg_work, sm5708_otg_work);
+ INIT_WORK(&charger->otg_fail_work, sm5708_otg_fail_work);
+
+ platform_set_drvdata(pdev, charger);
+ psy_config.drv_data = charger;
+ psy_config.supplied_to = battery_supplied_to;
+ psy_config.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+ ret = sm5708_parse_dt(charger);
+ if (ret < 0)
+ return ret;
+
+ sm5708_charger_initialize(charger);
+
+ charger->psy = devm_power_supply_register(&pdev->dev,
+ &sm5708_charger_desc, &psy_config);
+ if (IS_ERR(charger->psy))
+ return PTR_ERR(charger->psy);
+
+ sm5708_request_irq(charger, SM5708_VBUSPOK_IRQ, sm5708_vbus_irq);
+ sm5708_request_irq(charger, SM5708_VBUSOVP_IRQ, sm5708_vbus_irq);
+ sm5708_request_irq(charger, SM5708_VBUSUVLO_IRQ, sm5708_vbus_irq);
+ sm5708_request_irq(charger, SM5708_TOPOFF_IRQ, sm5708_done_irq);
+ sm5708_request_irq(charger, SM5708_DONE_IRQ, sm5708_done_irq);
+ sm5708_request_irq(charger, SM5708_OTGFAIL_IRQ, sm5708_otgfail_irq);
+
+ return 0;
+}
+
+static const struct of_device_id sm5708_charger_ids[] = {
+ { .compatible = "siliconmitus,sm5708-charger" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sm5708_charger_ids);
+
+static struct platform_driver sm5708_charger_driver = {
+ .driver = {
+ .name = "sm5708-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = sm5708_charger_ids,
+ },
+ .probe = sm5708_charger_probe,
+};
+
+module_platform_driver(sm5708_charger_driver);
+MODULE_DESCRIPTION("SM5708 Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/sm5708_fuelgauge.c b/drivers/power/supply/sm5708_fuelgauge.c
new file mode 100644
index 00000000000000..f27b4a5a8488fb
--- /dev/null
+++ b/drivers/power/supply/sm5708_fuelgauge.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2013 Dongik Sin
+ *
+ * SM5708 battery fuel gauge driver
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Definitions of SM5708 Fuelgauge Registers */
+#define SM5708_REG_DEVICE_ID 0x00
+#define SM5708_REG_CNTL 0x01
+#define SM5708_REG_INTFG 0x02
+#define SM5708_REG_INTFG_MASK 0x03
+#define SM5708_REG_STATUS 0x04
+#define SM5708_REG_SOC 0x05
+#define SM5708_REG_OCV 0x06
+#define SM5708_REG_VOLTAGE 0x07
+#define SM5708_REG_CURRENT 0x08
+#define SM5708_REG_TEMPERATURE 0x09
+#define SM5708_REG_SOC_CYCLE 0x0A
+
+#define SM5708_REG_V_ALARM 0x0C
+#define SM5708_REG_T_ALARM 0x0D
+#define SM5708_REG_SOC_ALARM 0x0E
+#define SM5708_REG_FG_OP_STATUS 0x10
+#define SM5708_REG_TOPOFFSOC 0x12
+#define SM5708_REG_PARAM_CTRL 0x13
+#define SM5708_REG_PARAM_RUN_UPDATE 0x14
+
+#define SM5708_REG_SOC_CYCLE_CFG 0x15
+#define SM5708_CYCLE_HIGH_LIMIT_SHIFT 12
+#define SM5708_CYCLE_LOW_LIMIT_SHIFT 8
+#define SM5708_CYCLE_LIMIT_CNTL_SHIFT 0
+
+#define SM5708_REG_VIT_PERIOD 0x1A
+#define SM5708_REG_MIX_RATE 0x1B
+#define SM5708_REG_MIX_INIT_BLANK 0x1C
+#define SM5708_REG_RESERVED 0x1F
+
+#define SM5708_REG_RCE0 0x20
+#define SM5708_REG_RCE1 0x21
+#define SM5708_REG_RCE2 0x22
+#define SM5708_REG_DTCD 0x23
+#define SM5708_REG_AUTO_RS_MAN 0x24
+#define SM5708_REG_RS_MIX_FACTOR 0x25
+#define SM5708_REG_RS_MAX 0x26
+#define SM5708_REG_RS_MIN 0x27
+#define SM5708_REG_RS_TUNE 0x28
+#define SM5708_REG_RS_MAN 0x29
+
+/* for cal */
+#define SM5708_REG_CURR_CAL 0x2C
+#define SM5708_REG_IOCV_MAN 0x2E
+#define SM5708_REG_END_V_IDX 0x2F
+
+#define SM5708_REG_IOCV_B_L_MIN 0x30
+#define SM5708_REG_IOCV_B_L_MAX 0x35
+#define SM5708_REG_IOCV_B_C_MIN 0x36
+#define SM5708_REG_IOCV_B_C_MAX 0x3B
+#define SM5708_REG_IOCI_B_L_MIN 0x40
+#define SM5708_REG_IOCI_B_L_MAX 0x45
+#define SM5708_REG_IOCI_B_C_MIN 0x46
+#define SM5708_REG_IOCI_B_C_MAX 0x4B
+
+#define SM5708_REG_VOLT_CAL 0x50
+#define SM5708_REG_CURR_OFF 0x51
+#define SM5708_REG_CURR_P_SLOPE 0x52
+#define SM5708_REG_CURR_N_SLOPE 0x53
+#define SM5708_REG_CURRLCAL_0 0x54
+#define SM5708_REG_CURRLCAL_1 0x55
+#define SM5708_REG_CURRLCAL_2 0x56
+
+/* for debug */
+#define SM5708_REG_OCV_STATE 0x80
+#define SM5708_REG_CURRENT_EST 0x85
+#define SM5708_REG_CURRENT_ERR 0x86
+#define SM5708_REG_Q_EST 0x87
+#define SM5708_AUX_STAT 0x94
+
+/* etc */
+#define SM5708_REG_MISC 0x90
+#define SM5708_REG_RESET 0x91
+#define SM5708_FG_INIT_MARK 0xA000
+#define SM5708_FG_PARAM_UNLOCK_CODE 0x3700
+#define SM5708_FG_PARAM_LOCK_CODE 0x0000
+#define SM5708_FG_TABLE_LEN 0xF /*real table length -1*/
+
+/* start reg addr for table */
+#define SM5708_REG_TABLE_START 0xA0
+
+#define SW_RESET_CODE 0x00A6
+#define SW_RESET_OTP_CODE 0x01A6
+#define RS_MAN_CNTL 0x0800
+
+/* control register value */
+#define ENABLE_MIX_MODE 0x8000
+#define ENABLE_TEMP_MEASURE 0x4000
+#define ENABLE_TOPOFF_SOC 0x2000
+#define ENABLE_RS_MAN_MODE 0x0800
+#define ENABLE_MANUAL_OCV 0x0400
+#define ENABLE_MODE_nENQ4 0x0200
+
+#define ENABLE_SOC_ALARM 0x0008
+#define ENABLE_T_H_ALARM 0x0004
+#define ENABLE_T_L_ALARM 0x0002
+#define ENABLE_V_ALARM 0x0001
+
+#define CNTL_REG_DEFAULT_VALUE 0x2008
+#define INIT_CHECK_MASK 0x0010
+#define DISABLE_RE_INIT 0x0010
+#define SM5708_JIG_CONNECTED 0x0001
+#define SM5708_BATTERY_VERSION 0x00F0
+
+#define TOPOFF_SOC_97 0x111
+#define TOPOFF_SOC_96 0x110
+#define TOPOFF_SOC_95 0x101
+#define TOPOFF_SOC_94 0x100
+#define TOPOFF_SOC_93 0x011
+#define TOPOFF_SOC_92 0x010
+#define TOPOFF_SOC_91 0x001
+#define TOPOFF_SOC_90 0x000
+
+#define MASK_L_SOC_INT 0x0008
+#define MASK_H_TEM_INT 0x0004
+#define MASK_L_TEM_INT 0x0002
+#define MASK_L_VOL_INT 0x0001
+
+#define MAXVAL(a, b) (((a) > (b)) ? (a) : (b))
+#define MINVAL(a, b) (((a) < (b)) ? (a) : (b))
+
+#define BULK_START 0x10000
+#define BULK_STOP 0x20000
+#define APPLY_FACTOR(val, fact) ((val) * (fact) / 1000)
+#define DEF_FACTOR(nom, den) (1000 * (nom) / (den))
+
+struct sm5708_battery_data {
+ const u32 *reg_init_seq;
+ size_t reg_init_seq_len;
+ int rs_value[5];
+ int n_temp_poff;
+ int n_temp_poff_offset;
+ int l_temp_poff;
+ int l_temp_poff_offset;
+ int value_v_alarm;
+ int topoff_soc;
+ int top_off;
+ int volt_cal;
+ int p_curr_cal;
+ int n_curr_cal;
+ int temp_std;
+ int fg_temp_volcal_fact;
+ int high_fg_temp_offset_fact;
+ int low_fg_temp_offset_fact;
+ int high_fg_temp_n_cal_fact;
+ int high_fg_temp_p_cal_fact;
+ int low_fg_temp_p_cal_fact;
+ int low_fg_temp_n_cal_fact;
+ int low_temp_p_cal_fact;
+ int low_temp_n_cal_fact;
+ int capacity;
+};
+
+struct sm5708_battery {
+ struct i2c_client *client;
+ struct power_supply *psy;
+ struct device *dev;
+ struct regmap *regmap;
+ const struct sm5708_battery_data *data;
+
+ int last_soc;
+ int last_voltage;
+ int last_ocv;
+ int last_current;
+ int last_temp;
+ int prev_voltage;
+ int iocv_error_count;
+};
+
+static enum power_supply_property sm5708_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+};
+
+static int sm5708_read_ext(struct sm5708_battery *bat, unsigned int reg,
+ int int_mask, int frac_mask, int sign_mask, int mult)
+{
+ int ret;
+ u32 regval;
+
+ ret = regmap_read(bat->regmap, reg, ®val);
+ if (ret < 0)
+ return ret;
+
+ ret = 0;
+
+ if (int_mask != 0) {
+ ret += ((regval & int_mask) >> (ffs(int_mask) - 1)) * mult;
+ }
+ if (frac_mask != 0) {
+ int fshift = ffs(frac_mask) - 1;
+ int mask = frac_mask >> fshift;
+ ret += ((regval >> fshift) & mask) * mult / (mask + 1);
+ }
+ if (regval & sign_mask)
+ ret = -ret;
+ return ret;
+}
+
+static int sm5708_get_ocv(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_OCV,
+ 0x7800, 0x7ff, 0, 1000000);
+}
+
+static int sm5708_get_vbat(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_VOLTAGE,
+ 0x3800, 0x7ff, 0, 1000000);
+}
+
+static int sm5708_get_curr(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_CURRENT,
+ 0x1800, 0x7ff, 0x8000, 1000000);
+}
+
+static int sm5708_get_temperature(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_TEMPERATURE,
+ 0x7f00, 0xf0, 0x8000, 10);
+}
+
+static int sm5708_get_soc_cycle(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_SOC_CYCLE,
+ 0x3ff, 0x0, 0x0, 1);
+}
+
+static int sm5708_get_device_id(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_DEVICE_ID,
+ 0xffff, 0x0, 0x0, 1);
+}
+
+static int sm5708_get_soc(struct sm5708_battery *bat)
+{
+ return sm5708_read_ext(bat, SM5708_REG_SOC,
+ 0xff00, 0xff, 0x0, 1);
+}
+
+static bool sm5708_fg_check_reg_init_need(struct sm5708_battery *bat)
+{
+ int ret = sm5708_read_ext(bat, SM5708_REG_FG_OP_STATUS,
+ 0xffff, 0x0, 0x0, 1);
+
+ if (ret < 0)
+ return false;
+
+ return ((ret & INIT_CHECK_MASK) != DISABLE_RE_INIT);
+}
+
+static bool sm5708_is_charging(struct sm5708_battery *bat)
+{
+ return false;
+}
+
+static void sm5708_vbatocv_check(struct sm5708_battery *bat)
+{
+ int rs_val;
+ bool set_auto = false;
+
+ if ((abs(bat->last_current) < 40) ||
+ (sm5708_is_charging(bat) &&
+ (bat->last_current < (bat->data->top_off)) &&
+ (bat->last_current > (bat->data->top_off/3)) &&
+ (bat->last_soc >= 20))) {
+ if (abs(bat->last_ocv - bat->last_voltage) > 30 &&
+ bat->iocv_error_count < 5) /* 30mV over */
+ bat->iocv_error_count++;
+ } else {
+ bat->iocv_error_count = 0;
+ }
+
+ if (bat->iocv_error_count > 5 &&
+ abs(bat->prev_voltage - bat->last_voltage) > 15) /* 15mV over */
+ bat->iocv_error_count = 0;
+
+ if (bat->iocv_error_count > 5) {
+ rs_val = bat->data->rs_value[0];
+ } else {
+ int voltage = MAXVAL(bat->last_voltage, bat->prev_voltage);
+ int temp_poff, temp_poff_offset, diff;
+ int temp = 20; // TODO
+
+ if (temp > 15) {
+ temp_poff = bat->data->n_temp_poff;
+ temp_poff_offset = bat->data->n_temp_poff_offset;
+ } else {
+ temp_poff = bat->data->l_temp_poff;
+ temp_poff_offset = bat->data->l_temp_poff_offset;
+ }
+
+ diff = temp_poff - voltage;
+
+ set_auto = 0;
+
+ if (diff > 0)
+ rs_val = bat->data->rs_value[0];
+ else if (diff > temp_poff_offset)
+ rs_val = bat->data->rs_value[0] / 2;
+ else
+ set_auto = true;
+ }
+
+ if (set_auto) {
+ /* mode change to mix RS auto mode */
+ regmap_update_bits(bat->regmap, SM5708_REG_CNTL,
+ ENABLE_MIX_MODE | ENABLE_RS_MAN_MODE, ENABLE_MIX_MODE);
+ } else {
+ /* run update set */
+ regmap_write(bat->regmap, SM5708_REG_PARAM_RUN_UPDATE, 1);
+ regmap_write(bat->regmap, SM5708_REG_RS_MAN, rs_val);
+ regmap_write(bat->regmap, SM5708_REG_PARAM_RUN_UPDATE, 0);
+
+ /* mode change */
+ regmap_update_bits(bat->regmap, SM5708_REG_CNTL,
+ ENABLE_MIX_MODE | ENABLE_RS_MAN_MODE,
+ ENABLE_MIX_MODE | ENABLE_RS_MAN_MODE);
+ }
+
+ bat->prev_voltage = bat->last_voltage;
+ bat->prev_voltage = bat->last_voltage;
+}
+
+static int sm5708_cal_carc (struct sm5708_battery *bat)
+{
+ int p_curr_cal = 0, n_curr_cal = 0, p_delta_cal = 0, n_delta_cal = 0;
+ int p_fg_delta_cal = 0, n_fg_delta_cal = 0, curr_offset = 0;
+ int fg_delta_volcal = 0, pn_volt_slope = 0, volt_offset = 0;
+ int temp_gap, fg_temp_gap;
+
+ sm5708_vbatocv_check(bat);
+
+ if (bat->last_current > 0 || (bat->last_current < -2000))
+ regmap_write(bat->regmap, SM5708_REG_RS_MIX_FACTOR, 327);
+ else
+ regmap_write(bat->regmap, SM5708_REG_RS_MIX_FACTOR, 326);
+
+ fg_temp_gap = (bat->last_temp/10) - bat->data->temp_std;
+
+ volt_offset = sm5708_read_ext(bat, SM5708_REG_VOLT_CAL, 0xff, 0, 0, 1);
+
+ fg_delta_volcal = APPLY_FACTOR(fg_temp_gap, bat->data->fg_temp_volcal_fact);
+ pn_volt_slope = (bat->data->volt_cal & 0xFF00) + (fg_delta_volcal << 8);
+
+ regmap_write(bat->regmap, SM5708_REG_VOLT_CAL, pn_volt_slope | volt_offset);
+
+ curr_offset = sm5708_read_ext(bat, SM5708_REG_CURR_OFF, 0x7f, 0, 0x80, 1);
+
+ if (fg_temp_gap < 0)
+ curr_offset += APPLY_FACTOR(-fg_temp_gap, bat->data->low_fg_temp_offset_fact);
+ else
+ curr_offset += APPLY_FACTOR(fg_temp_gap, bat->data->high_fg_temp_offset_fact);
+
+ regmap_write(bat->regmap, SM5708_REG_CURR_OFF,
+ curr_offset < 0 ? -curr_offset | 0x80 : curr_offset);
+
+ n_curr_cal = bat->data->n_curr_cal;
+ p_curr_cal = bat->data->p_curr_cal;
+
+ if (fg_temp_gap > 0) {
+ p_fg_delta_cal = APPLY_FACTOR(fg_temp_gap, bat->data->high_fg_temp_p_cal_fact);
+ n_fg_delta_cal = APPLY_FACTOR(fg_temp_gap, bat->data->high_fg_temp_n_cal_fact);
+ } else if (fg_temp_gap < 0) {
+ fg_temp_gap = -fg_temp_gap;
+ p_fg_delta_cal = APPLY_FACTOR(fg_temp_gap, bat->data->low_fg_temp_p_cal_fact);
+ n_fg_delta_cal = APPLY_FACTOR(fg_temp_gap, bat->data->low_fg_temp_n_cal_fact);
+ }
+ p_curr_cal += p_fg_delta_cal;
+ n_curr_cal += n_fg_delta_cal;
+
+ // TODO: get temperature from battery's thermal zone
+ temp_gap = 25 - bat->data->temp_std;
+ if (temp_gap < 0) {
+ temp_gap = -temp_gap;
+ p_delta_cal = APPLY_FACTOR(temp_gap, bat->data->low_temp_p_cal_fact);
+ n_delta_cal = APPLY_FACTOR(temp_gap, bat->data->low_temp_n_cal_fact);
+ }
+ p_curr_cal += p_delta_cal;
+ n_curr_cal += n_delta_cal;
+
+ regmap_write(bat->regmap, SM5708_REG_CURR_P_SLOPE, p_curr_cal);
+ regmap_write(bat->regmap, SM5708_REG_CURR_N_SLOPE, n_curr_cal);
+
+ return 0;
+}
+
+static int sm5708_calculate_iocv(struct sm5708_battery *bat)
+{
+ bool only_lb = false;
+ int roop_start = 0, roop_max = 0, i = 0;
+ int v_buffer[6] = {0, 0, 0, 0, 0, 0};
+ int i_buffer[6] = {0, 0, 0, 0, 0, 0};
+ int i_vset_margin = 0x67;
+ int lb_v_avg = 0, cb_v_avg = 0, lb_v_set = 0;
+ int cb_v_set = 0;
+ int lb_i_n_v_max = 0, cb_i_n_v_max = 0;
+ int ret = 0;
+
+ regmap_read(bat->regmap, SM5708_REG_END_V_IDX, &ret);
+ if (!(ret & 0x10))
+ only_lb = true;
+
+ for (roop_start = SM5708_REG_IOCV_B_C_MIN, roop_max = 6;
+ roop_start == SM5708_REG_IOCV_B_C_MIN;
+ roop_start = SM5708_REG_IOCV_B_L_MIN, roop_max = MINVAL(6, (ret & 0xf))) {
+ {
+ int v_max = -0xffff, v_min = 0xffff, v_sum = 0, v_ret;
+ int i_max = -0xffff, i_min = 0xffff, i_sum = 0, i_ret;
+ for (i = roop_start; i < roop_start + roop_max; i++) {
+ v_buffer[i-roop_start] = v_ret = sm5708_read_ext(bat, i,
+ 0xffff, 0, 0, 1);
+ i_buffer[i-roop_start] = i_ret = sm5708_read_ext(bat, i + 0x10,
+ 0x3fff, 0, 0x4000, 1);
+
+ v_max = MAXVAL(v_max, v_ret);
+ i_max = MAXVAL(i_max, i_ret);
+ v_min = MINVAL(v_min, v_ret);
+ i_min = MINVAL(i_min, i_ret);
+ v_sum += v_ret;
+ i_sum += i_ret;
+
+ if (abs(i_ret) > i_vset_margin && i_ret <= 0) {
+ if (roop_start == SM5708_REG_IOCV_B_L_MIN)
+ lb_i_n_v_max = MAXVAL(lb_i_n_v_max, v_ret);
+ else
+ cb_i_n_v_max = MAXVAL(cb_i_n_v_max, v_ret);
+ }
+ }
+
+ v_sum -= v_max - v_min;
+ i_sum -= i_max - i_min;
+
+ if (roop_start == SM5708_REG_IOCV_B_L_MIN)
+ lb_v_avg = v_sum / (roop_max - 2);
+ else
+ cb_v_avg = v_sum / (roop_max - 2);
+ }
+
+ /* lb_vset start */
+ if (roop_start == SM5708_REG_IOCV_B_L_MIN) {
+ int i;
+ for (i = 1, lb_v_set = lb_v_avg;
+ i < 4 && abs(i_buffer[roop_max-i]) < i_vset_margin; i++)
+ lb_v_set = MAXVAL(lb_v_set, v_buffer[roop_max-i]);
+
+ lb_v_set = MAXVAL(lb_i_n_v_max, lb_v_set);
+ } else {
+ int i, use_cb = 0;
+ for (i = 1, cb_v_set = cb_v_avg;
+ i < 4 && abs(i_buffer[roop_max-i]) < i_vset_margin; i++) {
+ use_cb = 1;
+ cb_v_set = MAXVAL(cb_v_set, v_buffer[roop_max-i]);
+ }
+
+ cb_v_set = MAXVAL(cb_i_n_v_max, cb_v_set);
+ if (use_cb)
+ return cb_v_set;
+ }
+ }
+
+ return lb_v_set;
+}
+
+
+
+static const u32 a6plte_reg_init[] = {
+ SM5708_REG_RESET, SM5708_FG_INIT_MARK,
+ SM5708_REG_PARAM_CTRL, SM5708_FG_PARAM_UNLOCK_CODE,
+ SM5708_REG_RCE0, 1249,
+ SM5708_REG_RCE1, 998,
+ SM5708_REG_RCE2, 471,
+ SM5708_REG_DTCD, 1,
+ SM5708_REG_AUTO_RS_MAN, 122,
+ SM5708_REG_VIT_PERIOD, 13574,
+ SM5708_REG_PARAM_CTRL, SM5708_FG_PARAM_UNLOCK_CODE | SM5708_FG_TABLE_LEN,
+ BULK_START | SM5708_REG_TABLE_START,
+ 0x1400, 0x1b33, 0x1ccd, 0x1d6b, 0x1d97, 0x1ddb, 0x1e28, 0x1e75,
+ 0x1ecd, 0x1f62, 0x1fa8, 0x1fd3, 0x2064, 0x20e9, 0x225a, 0x2400,
+ 0x0000, 0x0031, 0x00d2, 0x015e, 0x0358, 0x04be, 0x078a, 0x0bbc,
+ 0x0e88, 0x10a5, 0x120b, 0x1371, 0x14d7, 0x16f3, 0x1b6b, 0x1b72,
+ BULK_STOP,
+ SM5708_REG_RS_MIX_FACTOR, 326,
+ SM5708_REG_RS_MAX, 14336,
+ SM5708_REG_RS_MIN, 122,
+ SM5708_REG_MIX_RATE, 1027,
+ SM5708_REG_MIX_INIT_BLANK, 4,
+ SM5708_REG_VOLT_CAL, 0x8000,
+ SM5708_REG_CURR_OFF, 0,
+ SM5708_REG_CURR_P_SLOPE, 138,
+ SM5708_REG_CURR_N_SLOPE, 138,
+ SM5708_REG_MISC, 96,
+ SM5708_REG_TOPOFFSOC, 3,
+ /* INIT_last - control register set */
+ SM5708_REG_CNTL, ENABLE_MIX_MODE | ENABLE_TEMP_MEASURE | ENABLE_MANUAL_OCV,
+ SM5708_REG_PARAM_CTRL, SM5708_FG_PARAM_LOCK_CODE | SM5708_FG_TABLE_LEN,
+};
+
+static struct sm5708_battery_data a6plte_battery_info = {
+ .rs_value = { 0x7a, 0x147, 0x146, 0x3800, 0x7a },
+ .n_temp_poff = 3400,
+ .n_temp_poff_offset = 50,
+ .l_temp_poff = 3350,
+ .l_temp_poff_offset = 50,
+ .value_v_alarm = 3200,
+ .topoff_soc = 3,
+ .top_off = 350,
+ .volt_cal = 0x8000,
+ .p_curr_cal = 138,
+ .n_curr_cal = 138,
+ .temp_std = 25,
+ .fg_temp_volcal_fact = DEF_FACTOR(1, 15),
+ .high_fg_temp_offset_fact = DEF_FACTOR(1, 11),
+ .low_fg_temp_offset_fact = DEF_FACTOR(-1, 8),
+ .high_fg_temp_n_cal_fact = DEF_FACTOR(-1, 11),
+ .high_fg_temp_p_cal_fact = DEF_FACTOR(1, 6),
+ .low_fg_temp_p_cal_fact = DEF_FACTOR(1, 6),
+ .low_fg_temp_n_cal_fact = DEF_FACTOR(1, 9),
+ .low_temp_p_cal_fact = DEF_FACTOR(2, 1),
+ .low_temp_n_cal_fact = DEF_FACTOR(2, 1),
+ .capacity = 3500,
+};
+
+static bool sm5708_fg_reg_init(struct sm5708_battery *bat, int is_surge)
+{
+ int i, j, value;
+ u32 reg = 0;
+ const u32 *seq = bat->data->reg_init_seq;
+ u32 len = bat->data->reg_init_seq_len;
+
+ if (len < 1)
+ return -EINVAL;
+
+ for (i = 0; i < (len - 1);) {
+ reg = seq[i];
+
+ if (reg & BULK_START) {
+ reg &= 0xffff;
+ for (j = i + 1; j < len; j++) {
+ if (seq[j] & BULK_STOP)
+ i = j + 1;
+ else
+ regmap_write(bat->regmap, reg++, seq[j]);
+ }
+ } else {
+ regmap_write(bat->regmap, reg, seq[i + 1]);
+ i += 2;
+ }
+ }
+
+ /* surge reset defence */
+ if (is_surge) {
+ value = (bat->last_ocv << 8) / 125;
+ } else {
+ value = sm5708_calculate_iocv(bat);
+ }
+
+ regmap_write(bat->regmap, SM5708_REG_IOCV_MAN, value);
+
+ msleep(20);
+
+ regmap_write(bat->regmap, SM5708_REG_RESERVED, (1 << 4) & SM5708_BATTERY_VERSION);
+
+ return 1;
+}
+
+static bool sm5708_battery_init(struct sm5708_battery *bat, bool is_surge)
+{
+ int ret;
+
+ ret = sm5708_get_device_id(bat);
+
+ dev_info(bat->dev, "Device ID %x\n", ret);
+
+ regmap_write(bat->regmap, SM5708_REG_SOC_CYCLE_CFG, 0
+ | 3 << SM5708_CYCLE_LIMIT_CNTL_SHIFT
+ | 7 << SM5708_CYCLE_HIGH_LIMIT_SHIFT
+ | 1 << SM5708_CYCLE_LOW_LIMIT_SHIFT);
+
+ if (sm5708_fg_check_reg_init_need(bat)) {
+ sm5708_fg_reg_init(bat, is_surge);
+ }
+
+ return true;
+}
+
+static void sm5708_battery_reset(struct sm5708_battery *bat, bool is_surge)
+{
+ unsigned int code = is_surge ? SW_RESET_OTP_CODE : SW_RESET_CODE;
+ int ret, retries = 3;
+ do {
+ ret = regmap_write(bat->regmap, SM5708_REG_RESET, code);
+ msleep(50);
+ } while (ret < 0 && retries--);
+
+ msleep(800);
+
+ sm5708_battery_init(bat, is_surge);
+}
+
+
+static int sm5708_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sm5708_battery *bat = power_supply_get_drvdata(psy);
+ int curr;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = sm5708_get_vbat(bat);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ val->intval = sm5708_get_ocv(bat);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ val->intval = sm5708_get_soc_cycle(bat);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = sm5708_get_curr(bat);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ val->intval = sm5708_get_temperature(bat);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ bat->last_current = sm5708_get_curr(bat) / 1000;
+ bat->last_voltage = sm5708_get_vbat(bat) / 1000;
+ bat->last_ocv = sm5708_get_ocv(bat) / 1000;
+ bat->last_temp = sm5708_get_temperature(bat);
+ bat->last_soc = sm5708_get_soc(bat);
+
+ sm5708_cal_carc(bat);
+
+ val->intval = clamp(sm5708_get_soc(bat), 0, 100);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = bat->data->capacity * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = bat->data->capacity * 10 * sm5708_get_soc(bat);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ curr = sm5708_get_curr(bat);
+ if (power_supply_am_i_supplied(psy)) {
+ if (sm5708_get_soc(bat) > 95)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (curr > 5000)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct regmap_config sm5708_battery_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+ .max_register = SM5708_REG_TABLE_START + 32,
+};
+
+static const struct power_supply_desc sm5708_battery_desc = {
+ .name = "sm5708-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = sm5708_battery_get_property,
+ .properties = sm5708_battery_properties,
+ .num_properties = ARRAY_SIZE(sm5708_battery_properties),
+};
+
+static int sm5708_battery_probe(struct i2c_client *client)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct sm5708_battery *battery;
+ struct power_supply_config psy_config = { 0 };
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ battery = devm_kzalloc(&client->dev, sizeof(*battery), GFP_KERNEL);
+ if (!battery)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, battery);
+ psy_config.drv_data = battery;
+
+ battery->dev = &client->dev;
+ battery->client = client;
+ battery->regmap = devm_regmap_init_i2c(client,
+ &sm5708_battery_regmap_config);
+ battery->data = (struct sm5708_battery_data*) of_device_get_match_data(battery->dev);
+
+ if (!sm5708_battery_init(battery, false)) {
+ dev_err(&client->dev, "Failed to Initialize battery\n");
+ return -EINVAL;
+ }
+
+ battery->psy = devm_power_supply_register(&client->dev,
+ &sm5708_battery_desc, &psy_config);
+ if (IS_ERR(battery->psy))
+ return PTR_ERR(battery->psy);
+
+ return 0;
+}
+
+static const struct i2c_device_id sm5708_id[] = {
+ { "sm5708-battery" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sm5708_id);
+
+static const struct of_device_id sm5708_of_match[] = {
+ { .compatible = "sm5708-battery,a6plte", .data = &a6plte_battery_info },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sm5708_of_match);
+
+static struct i2c_driver sm5708_fuelgauge_driver = {
+ .driver = {
+ .name = "sm5708-battery",
+ .owner = THIS_MODULE,
+ .of_match_table = sm5708_of_match,
+ },
+ .probe = sm5708_battery_probe,
+ .id_table = sm5708_id,
+};
+
+module_i2c_driver(sm5708_fuelgauge_driver);
+
+MODULE_DESCRIPTION("Siliconmitus SM5708 Fuel Gauge Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/sm5708.h b/include/linux/mfd/sm5708.h
new file mode 100644
index 00000000000000..2a8691fc751fe1
--- /dev/null
+++ b/include/linux/mfd/sm5708.h
@@ -0,0 +1,210 @@
+ /*
+ * sm5708.h - Driver for the SM5708
+ *
+ * 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 .
+ *
+ * SM5708 has Flash, RGB, Charger, Regulator devices.
+ * The devices share the same I2C bus and included in
+ * this mfd driver.
+ */
+
+#ifndef __SM5708_H__
+#define __SM5708_H__
+
+#include
+#include
+#include
+#include
+
+enum sm5708_boost_output {
+ SM5708_BST_OUT_4v0,
+ SM5708_BST_OUT_4v1,
+ SM5708_BST_OUT_4v2,
+ SM5708_BST_OUT_4v3,
+ SM5708_BST_OUT_4v4,
+ SM5708_BST_OUT_4v5,
+ SM5708_BST_OUT_4v6,
+ SM5708_BST_OUT_4v7,
+ SM5708_BST_OUT_4v8,
+ SM5708_BST_OUT_4v9,
+ SM5708_BST_OUT_5v0,
+ SM5708_BST_OUT_5v1,
+};
+
+enum sm5708_otg_current {
+ SM5708_OTG_CURRENT_500mA,
+ SM5708_OTG_CURRENT_700mA,
+ SM5708_OTG_CURRENT_900mA,
+ SM5708_OTG_CURRENT_1500mA,
+};
+
+enum mode_request {
+ SM5708_CHARGE = 1,
+ SM5708_TORCH = 2,
+ SM5708_FLASH = 4,
+ SM5708_OTG = 8,
+};
+
+enum sm5708_mode {
+ SM5708_MODE_SUSPEND = 0,
+ SM5708_MODE_FACTORY = 1,
+ SM5708_MODE_CHG_OFF = 4,
+ SM5708_MODE_CHG_ON = 5,
+ SM5708_MODE_FLASH_BOOST = 6,
+ SM5708_MODE_USB_OTG = 7,
+};
+
+enum sm5708_reg {
+ SM5708_REG_INT1 = 0x00,
+ SM5708_REG_INT2 = 0x01,
+ SM5708_REG_INT3 = 0x02,
+ SM5708_REG_INT4 = 0x03,
+ SM5708_REG_INTMSK1 = 0x04,
+ SM5708_REG_INTMSK2 = 0x05,
+ SM5708_REG_INTMSK3 = 0x06,
+ SM5708_REG_INTMSK4 = 0x07,
+ SM5708_REG_STATUS1 = 0x08,
+ SM5708_REG_STATUS2 = 0x09,
+ SM5708_REG_STATUS3 = 0x0A,
+ SM5708_REG_STATUS4 = 0x0B,
+ SM5708_REG_CNTL = 0x0C,
+ SM5708_REG_VBUSCNTL = 0x0D,
+ SM5708_REG_CHGCNTL1 = 0x0F,
+ SM5708_REG_CHGCNTL2 = 0x10,
+ SM5708_REG_CHGCNTL3 = 0x12,
+ SM5708_REG_CHGCNTL4 = 0x13,
+ SM5708_REG_CHGCNTL5 = 0x14,
+ SM5708_REG_CHGCNTL6 = 0x15,
+ SM5708_REG_CHGCNTL7 = 0x16,
+ SM5708_REG_FLED1CNTL1 = 0x17,
+ SM5708_REG_FLED1CNTL2 = 0x18,
+ SM5708_REG_FLED1CNTL3 = 0x19,
+ SM5708_REG_FLED1CNTL4 = 0x1A,
+ SM5708_REG_FLED2CNTL1 = 0x1B,
+ SM5708_REG_FLED2CNTL2 = 0x1C,
+ SM5708_REG_FLED2CNTL3 = 0x1D,
+ SM5708_REG_FLED2CNTL4 = 0x1E,
+ SM5708_REG_FLEDCNTL5 = 0x1F,
+ SM5708_REG_FLEDCNTL6 = 0x20,
+ SM5708_REG_SBPSCNTL = 0x21,
+ SM5708_REG_CNTLMODEONOFF = 0x22,
+ SM5708_REG_CNTLPWM = 0x23,
+ SM5708_REG_RLEDCURRENT = 0x24,
+ SM5708_REG_GLEDCURRENT = 0x25,
+ SM5708_REG_BLEDCURRENT = 0x26,
+ SM5708_REG_DIMSLPRLEDCNTL = 0x27,
+ SM5708_REG_DIMSLPGLEDCNTL = 0x28,
+ SM5708_REG_DIMSLPBLEDCNTL = 0x29,
+ SM5708_REG_RLEDCNTL1 = 0x2A,
+ SM5708_REG_RLEDCNTL2 = 0x2B,
+ SM5708_REG_RLEDCNTL3 = 0x2C,
+ SM5708_REG_RLEDCNTL4 = 0x2D,
+ SM5708_REG_GLEDCNTL1 = 0x2E,
+ SM5708_REG_GLEDCNTL2 = 0x2F,
+ SM5708_REG_GLEDCNTL3 = 0x30,
+ SM5708_REG_GLEDCNTL4 = 0x31,
+ SM5708_REG_BLEDCNTL1 = 0x32,
+ SM5708_REG_BLEDCNTL2 = 0x33,
+ SM5708_REG_BLEDCNTL3 = 0x34,
+ SM5708_REG_BLEDCNTL4 = 0x35,
+ SM5708_REG_HAPTICCNTL = 0x36,
+ SM5708_REG_DEVICEID = 0x37,
+ SM5708_REG_FACTORY = 0x3E,
+
+ SM5708_REG_MAX,
+};
+
+enum sm5708_irq {
+ SM5708_VBUSPOK_IRQ,
+ SM5708_VBUSUVLO_IRQ,
+ SM5708_VBUSOVP_IRQ,
+ SM5708_VBUSLIMIT_IRQ,
+
+ SM5708_AICL_IRQ,
+ SM5708_BATOVP_IRQ,
+ SM5708_NOBAT_IRQ,
+ SM5708_CHGON_IRQ,
+ SM5708_Q4FULLON_IRQ,
+ SM5708_TOPOFF_IRQ,
+ SM5708_DONE_IRQ,
+ SM5708_WDTMROFF_IRQ,
+
+ SM5708_THEMREG_IRQ,
+ SM5708_THEMSHDN_IRQ,
+ SM5708_OTGFAIL_IRQ,
+ SM5708_DISLIMIT_IRQ,
+ SM5708_PRETMROFF_IRQ,
+ SM5708_FASTTMROFF_IRQ,
+ SM5708_LOWBATT_IRQ,
+ SM5708_nENQ4_IRQ,
+
+ SM5708_FLED1SHORT_IRQ,
+ SM5708_FLED1OPEN_IRQ,
+ SM5708_FLED2SHORT_IRQ,
+ SM5708_FLED2OPEN_IRQ,
+ SM5708_BOOSTPOK_NG_IRQ,
+ SM5708_BOOSTPOK_IRQ,
+ SM5708_ABSTMR1OFF_IRQ,
+ SM5708_SBPS_IRQ,
+
+ SM5708_MAX_IRQ,
+};
+
+#define SM5708_VBUSPOK_STATUS1 BIT(0)
+#define SM5708_VBUSUVLO_STATUS1 BIT(1)
+#define SM5708_VBUSOVP_STATUS1 BIT(2)
+#define SM5708_VBUSLIMIT_STATUS1 BIT(3)
+
+#define SM5708_AICL_STATUS2 BIT(0)
+#define SM5708_BATOVP_STATUS2 BIT(1)
+#define SM5708_NOBAT_STATUS2 BIT(2)
+#define SM5708_CHGON_STATUS2 BIT(3)
+#define SM5708_Q4FULLON_STATUS2 BIT(4)
+#define SM5708_TOPOFF_STATUS2 BIT(5)
+#define SM5708_DONE_STATUS2 BIT(6)
+#define SM5708_WDTMROFF_STATUS2 BIT(7)
+
+#define SM5708_THEMREG_STATUS3 BIT(0)
+#define SM5708_THEMSHDN_STATUS3 BIT(1)
+#define SM5708_OTGFAIL_STATUS3 BIT(2)
+#define SM5708_DISLIMIT_STATUS3 BIT(3)
+#define SM5708_PRETMROFF_STATUS3 BIT(4)
+#define SM5708_FASTTMROFF_STATUS3 BIT(5)
+#define SM5708_LOWBATT_STATUS3 BIT(6)
+#define SM5708_nENQ4_STATUS3 BIT(7)
+
+#define SM5708_FLED1SHORT_STATUS4 BIT(0)
+#define SM5708_FLED1OPEN_STATUS4 BIT(1)
+#define SM5708_FLED2SHORT_STATUS4 BIT(2)
+#define SM5708_FLED2OPEN_STATUS4 BIT(3)
+#define SM5708_BOOSTPOK_NG_STATUS4 BIT(4)
+#define SM5708_BOOSTPOK_STATUS4 BIT(5)
+#define SM5708_ABSTMR1OFF_STATUS4 BIT(6)
+#define SM5708_SBPS_STATUS4 BIT(7)
+
+struct sm5708_chip {
+ struct device *dev;
+ struct i2c_client *i2c;
+ int irq;
+
+ struct regmap *regmap;
+ struct regmap_irq_chip_data *irq_data;
+ struct mutex mode_mutex;
+ u8 requested_mode;
+ u8 op_mode;
+};
+
+extern void sm5708_request_mode(struct device *dev, u8 mode_mask, bool enable);
+
+#endif /* __SM5708_H__ */