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__ */